Back to Repositories

Testing Target Resolution Implementation in parcel-bundler

This test suite validates the TargetResolver functionality in Parcel, focusing on how it resolves build targets and handles various target configurations from package.json and CLI options.

Test Coverage Overview

The test suite provides comprehensive coverage of target resolution scenarios including:

  • Common package.json targets (main, module, browser)
  • Custom target configurations
  • Target distDir resolution
  • Invalid target validation
  • Scope hoisting requirements
  • Output format inference

Implementation Analysis

The testing approach focuses on validating the TargetResolver class using Jest’s describe/it pattern. Tests verify target resolution logic through various fixtures that represent different package.json configurations and CLI options. The implementation handles complex scenarios like format inference, path normalization, and validation of target specifications.

Technical Details

Key technical components include:

  • Jest test framework with beforeEach/afterEach hooks
  • Temporary directory management via tempy
  • Mock API implementation for resolver dependencies
  • File system utilities from @parcel/test-utils
  • Path normalization and validation utilities

Best Practices Demonstrated

The test suite demonstrates several testing best practices:

  • Comprehensive fixture-based testing
  • Proper error case validation
  • Isolation of test cases
  • Clean setup and teardown
  • Detailed assertion messages
  • Edge case coverage

parcel-bundler/parcel

packages/core/core/test/TargetRequest.test.js

            
// @flow strict-local

import assert from 'assert';
import path from 'path';
import tempy from 'tempy';
import {inputFS as fs} from '@parcel/test-utils';
import {md} from '@parcel/diagnostic';
import {normalizeSeparators} from '@parcel/utils';
import {TargetResolver} from '../src/requests/TargetRequest';
import {DEFAULT_OPTIONS as _DEFAULT_OPTIONS, relative} from './test-utils';

const DEFAULT_OPTIONS = {
  ..._DEFAULT_OPTIONS,
  defaultTargetOptions: {
    ..._DEFAULT_OPTIONS.defaultTargetOptions,
    sourceMaps: true,
  },
};

const COMMON_TARGETS_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/common-targets',
);

const COMMON_TARGETS_IGNORE_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/common-targets-ignore',
);

const CUSTOM_TARGETS_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/custom-targets',
);

const CUSTOM_TARGETS_DISTDIR_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/custom-targets-distdir',
);

const INVALID_TARGETS_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/invalid-targets',
);

const INVALID_ENGINES_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/invalid-engines',
);

const INVALID_DISTPATH_FIXTURE_PATH = path.join(
  __dirname,
  'fixtures/invalid-distpath',
);

const DEFAULT_DISTPATH_FIXTURE_PATHS = {
  none: path.join(__dirname, 'fixtures/targets-default-distdir-none'),
  one: path.join(__dirname, 'fixtures/targets-default-distdir-one'),
  two: path.join(__dirname, 'fixtures/targets-default-distdir-two'),
};

const CONTEXT_FIXTURE_PATH = path.join(__dirname, 'fixtures/context');

describe('TargetResolver', () => {
  let cacheDir;
  beforeEach(() => {
    cacheDir = tempy.directory();
  });

  afterEach(() => {
    return fs.rimraf(cacheDir);
  });

  let api = {
    invalidateOnFileCreate() {},
    invalidateOnFileUpdate() {},
    invalidateOnConfigKeyChange() {},
    invalidateOnFileDelete() {},
    invalidateOnEnvChange() {},
    invalidateOnOptionChange() {},
    invalidateOnStartup() {},
    invalidateOnBuild() {},
    getInvalidations() {
      return [];
    },
    runRequest() {
      throw new Error('Not implemented');
    },
    storeResult() {},
    canSkipSubrequest() {
      return false;
    },
    getPreviousResult() {},
    getRequestResult() {},
    getSubRequests() {
      return [];
    },
    getInvalidSubRequests() {
      return [];
    },
  };

  it('resolves exactly specified targets', async () => {
    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      targets: {
        customA: {
          context: 'browser',
          distDir: 'customA',
        },
        customB: {
          distDir: 'customB',
          distEntry: 'b.js',
          engines: {
            node: '>= 8.0.0',
          },
        },
      },
    });

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'customA',
          publicUrl: '/',
          distDir: normalizeSeparators(path.resolve('customA')),
          env: {
            id: '1d40417b63734b32',
            context: 'browser',
            includeNodeModules: true,
            engines: {
              browsers: ['> 0.25%'],
            },
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
        },
        {
          name: 'customB',
          publicUrl: '/',
          distEntry: 'b.js',
          distDir: normalizeSeparators(path.resolve('customB')),
          env: {
            id: '928f0d1c941b2e57',
            context: 'node',
            includeNodeModules: false,
            engines: {
              node: '>= 8.0.0',
            },
            outputFormat: 'commonjs',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
        },
      ],
    );
  });

  it('resolves common targets from package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'main',
          distDir: 'fixtures/common-targets/dist/main',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'b552bd32da37fa8b',
            context: 'node',
            engines: {
              node: '>= 8.0.0',
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(COMMON_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 11,
              line: 2,
            },
            end: {
              column: 31,
              line: 2,
            },
          },
        },
        {
          name: 'module',
          distDir: 'fixtures/common-targets/dist/module',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: '8804e4eb97e2703e',
            context: 'browser',
            engines: {
              browsers: ['last 1 version'],
            },
            includeNodeModules: false,
            outputFormat: 'esmodule',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {
              inlineSources: true,
            },
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(COMMON_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 13,
              line: 3,
            },
            end: {
              column: 35,
              line: 3,
            },
          },
        },
        {
          name: 'browser',
          distDir: 'fixtures/common-targets/dist/browser',
          distEntry: 'index.js',
          publicUrl: '/assets',
          env: {
            id: 'a7ed3e73c53f1923',
            context: 'browser',
            engines: {
              browsers: ['last 1 version'],
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(COMMON_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 14,
              line: 4,
            },
            end: {
              column: 37,
              line: 4,
            },
          },
        },
      ],
    );
  });

  it('allows ignoring common targets from package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_IGNORE_FIXTURE_PATH),
      [
        {
          name: 'app',
          distDir: relative(
            path.join(COMMON_TARGETS_IGNORE_FIXTURE_PATH, 'dist'),
          ),
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'f7c9644283a8698f',
            context: 'node',
            engines: {
              node: '>= 8.0.0',
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: undefined,
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(COMMON_TARGETS_IGNORE_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 10,
              line: 3,
            },
            end: {
              column: 25,
              line: 3,
            },
          },
        },
      ],
    );
  });

  it('resolves custom targets from package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    assert.deepEqual(
      await targetResolver.resolve(CUSTOM_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'main',
          distDir: 'fixtures/custom-targets/dist/main',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'b552bd32da37fa8b',
            context: 'node',
            engines: {
              node: '>= 8.0.0',
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 11,
              line: 2,
            },
            end: {
              column: 31,
              line: 2,
            },
          },
        },
        {
          name: 'browserModern',
          distDir: 'fixtures/custom-targets/dist/browserModern',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: '1f28e9ceaf633d83',
            context: 'browser',
            engines: {
              browsers: ['last 1 version'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 20,
              line: 3,
            },
            end: {
              column: 49,
              line: 3,
            },
          },
        },
        {
          name: 'browserLegacy',
          distDir: 'fixtures/custom-targets/dist/browserLegacy',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: '767bf6e6b675c4f3',
            context: 'browser',
            engines: {
              browsers: ['ie11'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 20,
              line: 4,
            },
            end: {
              column: 49,
              line: 4,
            },
          },
        },
      ],
    );
  });

  it('should not optimize libraries by default', async () => {
    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      mode: 'production',
      defaultTargetOptions: {
        ...DEFAULT_OPTIONS.defaultTargetOptions,
        shouldOptimize: true,
      },
    });

    assert.deepEqual(
      await targetResolver.resolve(CUSTOM_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'main',
          distDir: 'fixtures/custom-targets/dist/main',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'b552bd32da37fa8b',
            context: 'node',
            engines: {
              node: '>= 8.0.0',
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 11,
              line: 2,
            },
            end: {
              column: 31,
              line: 2,
            },
          },
        },
        {
          name: 'browserModern',
          distDir: 'fixtures/custom-targets/dist/browserModern',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'ed7c0e65adee71c9',
            context: 'browser',
            engines: {
              browsers: ['last 1 version'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: true,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 20,
              line: 3,
            },
            end: {
              column: 49,
              line: 3,
            },
          },
        },
        {
          name: 'browserLegacy',
          distDir: 'fixtures/custom-targets/dist/browserLegacy',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'f7692543e59e4c0a',
            context: 'browser',
            engines: {
              browsers: ['ie11'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: true,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 20,
              line: 4,
            },
            end: {
              column: 49,
              line: 4,
            },
          },
        },
      ],
    );
  });

  it('resolves explicit distDir for custom targets from package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    assert.deepEqual(
      await targetResolver.resolve(CUSTOM_TARGETS_DISTDIR_FIXTURE_PATH),
      [
        {
          name: 'app',
          distDir: 'fixtures/custom-targets-distdir/www',
          distEntry: undefined,
          publicUrl: 'www',
          env: {
            id: 'ddb6ac7c9a3a9178',
            context: 'browser',
            engines: {
              browsers: '> 0.25%',
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(CUSTOM_TARGETS_DISTDIR_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              line: 3,
              column: 5,
            },
            end: {
              line: 3,
              column: 10,
            },
          },
        },
      ],
    );
  });

  it('skips targets with custom entry source for default entry', async () => {
    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      targets: {
        customA: {
          context: 'browser',
          distDir: 'customA',
          source: 'customA/index.js',
        },
        customB: {
          distDir: 'customB',
        },
      },
    });

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'customB',
          distDir: normalizeSeparators(path.resolve('customB')),
          publicUrl: '/',
          env: {
            id: '1d40417b63734b32',
            context: 'browser',
            engines: {
              browsers: ['> 0.25%'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
        },
      ],
    );
  });

  it('skips other targets with custom entry', async () => {
    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      targets: {
        customA: {
          context: 'browser',
          distDir: 'customA',
          source: 'customA/index.js',
        },
        customB: {
          distDir: 'customB',
        },
      },
    });

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH, 'customA'),
      [
        {
          name: 'customA',
          distDir: normalizeSeparators(path.resolve('customA')),
          publicUrl: '/',
          env: {
            id: '1d40417b63734b32',
            context: 'browser',
            engines: {
              browsers: ['> 0.25%'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          source: 'customA/index.js',
        },
      ],
    );
  });

  it('resolves main target with context from package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    assert.deepEqual(await targetResolver.resolve(CONTEXT_FIXTURE_PATH), [
      {
        name: 'main',
        distDir: 'fixtures/context/dist/main',
        distEntry: 'index.js',
        publicUrl: '/',
        env: {
          id: '6aafdb9eaa4a3812',
          context: 'node',
          engines: {
            browsers: [
              'last 1 Chrome version',
              'last 1 Safari version',
              'last 1 Firefox version',
              'last 1 Edge version',
            ],
          },
          includeNodeModules: false,
          isLibrary: true,
          outputFormat: 'commonjs',
          shouldOptimize: false,
          shouldScopeHoist: true,
          sourceMap: {},
          loc: undefined,
          sourceType: 'module',
        },
        loc: {
          filePath: relative(path.join(CONTEXT_FIXTURE_PATH, 'package.json')),
          start: {
            column: 11,
            line: 2,
          },
          end: {
            column: 31,
            line: 2,
          },
        },
      },
    ]);
  });

  it('errors when the main target contains a non-js extension', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/application-targets');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message: 'Unexpected output file type .html in target "main"',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  end: {
                    column: 27,
                    line: 2,
                  },
                  message: 'File extension must be .js, .mjs, or .cjs',
                  start: {
                    column: 11,
                    line: 2,
                  },
                },
              ],
            },
          ],
          hints: [
            'The "main" field is meant for libraries. If you meant to output a .html file, either remove the "main" field or choose a different target name.',
          ],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('errors when the main target uses the global output format', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/main-global');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message:
            'The "global" output format is not supported in the "main" target.',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  message: undefined,
                  end: {
                    column: 30,
                    line: 5,
                  },
                  start: {
                    column: 23,
                    line: 5,
                  },
                },
              ],
            },
          ],
          hints: [
            'The "main" field is meant for libraries. The outputFormat must be either "commonjs" or "esmodule". Either change or remove the declared outputFormat.',
          ],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('errors when the main target uses the esmodule output format without a .mjs extension or "type": "module" field', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/main-mjs');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message:
            'Output format "esmodule" cannot be used in the "main" target without a .mjs extension or "type": "module" field.',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  message: 'Declared output format defined here',
                  end: {
                    column: 32,
                    line: 5,
                  },
                  start: {
                    column: 23,
                    line: 5,
                  },
                },
                {
                  message: 'Inferred output format defined here',
                  end: {
                    column: 25,
                    line: 2,
                  },
                  start: {
                    column: 11,
                    line: 2,
                  },
                },
              ],
            },
          ],
          hints: [
            'Either change the output file extension to .mjs, add "type": "module" to package.json, or remove the declared outputFormat.',
          ],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('errors when the inferred output format does not match the declared one in common targets', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/main-format-mismatch');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message:
            'Declared output format "esmodule" does not match expected output format "commonjs".',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  message: 'Declared output format defined here',
                  end: {
                    column: 32,
                    line: 5,
                  },
                  start: {
                    column: 23,
                    line: 5,
                  },
                },
                {
                  message: 'Inferred output format defined here',
                  end: {
                    column: 26,
                    line: 2,
                  },
                  start: {
                    column: 11,
                    line: 2,
                  },
                },
              ],
            },
          ],
          hints: [
            'Either remove the target\'s declared "outputFormat" or change the extension to .mjs or .js.',
          ],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('errors when the inferred output format does not match the declared one in custom targets', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/custom-format-mismatch');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message:
            'Declared output format "commonjs" does not match expected output format "esmodule".',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  message: 'Declared output format defined here',
                  end: {
                    column: 32,
                    line: 5,
                  },
                  start: {
                    column: 23,
                    line: 5,
                  },
                },
                {
                  message: 'Inferred output format defined here',
                  end: {
                    column: 26,
                    line: 2,
                  },
                  start: {
                    column: 11,
                    line: 2,
                  },
                },
              ],
            },
          ],
          hints: [
            'Either remove the target\'s declared "outputFormat" or change the extension to .cjs or .js.',
          ],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('errors when a common library target turns scope hoisting off', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/library-scopehoist');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message: 'Scope hoisting cannot be disabled for library targets.',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  message: undefined,
                  end: {
                    column: 25,
                    line: 5,
                  },
                  start: {
                    column: 21,
                    line: 5,
                  },
                },
              ],
            },
          ],
          hints: [
            'The "main" target is meant for libraries. Either remove the "scopeHoist" option, or use a different target name.',
          ],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('errors when a custom library target turns scope hoisting off', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/library-custom-scopehoist');
    let code = await fs.readFile(path.join(fixture, 'package.json'), 'utf8');

    // $FlowFixMe
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message: 'Scope hoisting cannot be disabled for library targets.',
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  message: undefined,
                  end: {
                    column: 25,
                    line: 6,
                  },
                  start: {
                    column: 21,
                    line: 6,
                  },
                },
                {
                  message: undefined,
                  end: {
                    column: 23,
                    line: 5,
                  },
                  start: {
                    column: 20,
                    line: 5,
                  },
                },
              ],
            },
          ],
          hints: ['Either remove the "scopeHoist" or "isLibrary" option.'],
          documentationURL:
            'https://parceljs.org/features/targets/#library-targets',
        },
      ],
    });
  });

  it('should infer output format for custom targets by extension', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/custom-format-infer-ext');

    assert.deepEqual(await targetResolver.resolve(fixture), [
      {
        name: 'test',
        distDir: relative(path.join(fixture, 'dist')),
        distEntry: 'index.mjs',
        publicUrl: '/',
        env: {
          id: '439701173a9199ea',
          context: 'browser',
          engines: {
            browsers: [
              'last 1 Chrome version',
              'last 1 Safari version',
              'last 1 Firefox version',
              'last 1 Edge version',
            ],
          },
          includeNodeModules: true,
          outputFormat: 'esmodule',
          isLibrary: false,
          shouldOptimize: false,
          shouldScopeHoist: false,
          sourceMap: {},
          loc: undefined,
          sourceType: 'module',
        },
        loc: {
          filePath: relative(path.join(fixture, 'package.json')),
          start: {
            column: 11,
            line: 2,
          },
          end: {
            column: 27,
            line: 2,
          },
        },
      },
    ]);
  });

  it('should infer output format for custom targets by "type": "module" field', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let fixture = path.join(__dirname, 'fixtures/custom-format-infer-type');

    assert.deepEqual(await targetResolver.resolve(fixture), [
      {
        name: 'test',
        distDir: relative(path.join(fixture, 'dist')),
        distEntry: 'index.js',
        publicUrl: '/',
        env: {
          id: '439701173a9199ea',
          context: 'browser',
          engines: {
            browsers: [
              'last 1 Chrome version',
              'last 1 Safari version',
              'last 1 Firefox version',
              'last 1 Edge version',
            ],
          },
          includeNodeModules: true,
          outputFormat: 'esmodule',
          isLibrary: false,
          shouldOptimize: false,
          shouldScopeHoist: false,
          sourceMap: {},
          loc: undefined,
          sourceType: 'module',
        },
        loc: {
          filePath: relative(path.join(fixture, 'package.json')),
          start: {
            column: 11,
            line: 3,
          },
          end: {
            column: 26,
            line: 3,
          },
        },
      },
    ]);
  });

  it('resolves a subset of package.json targets when given a list of names', async () => {
    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      targets: ['main', 'browser'],
    });

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'main',
          distDir: 'fixtures/common-targets/dist/main',
          distEntry: 'index.js',
          publicUrl: '/',
          env: {
            id: 'b552bd32da37fa8b',
            context: 'node',
            engines: {
              node: '>= 8.0.0',
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(COMMON_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 11,
              line: 2,
            },
            end: {
              column: 31,
              line: 2,
            },
          },
        },
        {
          name: 'browser',
          distDir: 'fixtures/common-targets/dist/browser',
          distEntry: 'index.js',
          publicUrl: '/assets',
          env: {
            id: 'a7ed3e73c53f1923',
            context: 'browser',
            engines: {
              browsers: ['last 1 version'],
            },
            includeNodeModules: false,
            outputFormat: 'commonjs',
            isLibrary: true,
            shouldOptimize: false,
            shouldScopeHoist: true,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(COMMON_TARGETS_FIXTURE_PATH, 'package.json'),
            ),
            start: {
              column: 14,
              line: 4,
            },
            end: {
              column: 37,
              line: 4,
            },
          },
        },
      ],
    );
  });

  it('generates a default target in serve mode', async () => {
    let serveDistDir = path.join(DEFAULT_OPTIONS.cacheDir, 'dist');

    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      serveOptions: {distDir: serveDistDir, port: 1234},
    });

    assert.deepEqual(
      await targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH),
      [
        {
          name: 'default',
          distDir: '.parcel-cache/dist',
          publicUrl: '/',
          env: {
            id: 'd6ea1d42532a7575',
            context: 'browser',
            engines: {
              browsers: [
                'last 1 Chrome version',
                'last 1 Safari version',
                'last 1 Firefox version',
                'last 1 Edge version',
              ],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
        },
      ],
    );
  });

  it('generates the correct distDir with no explicit targets', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);

    assert.deepEqual(
      await targetResolver.resolve(DEFAULT_DISTPATH_FIXTURE_PATHS.none),
      [
        {
          name: 'default',
          distDir: relative(
            path.join(DEFAULT_DISTPATH_FIXTURE_PATHS.none, 'dist'),
          ),
          publicUrl: '/',
          env: {
            id: 'a9c07d094d038c73',
            context: 'browser',
            engines: {
              browsers: ['Chrome 80'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
        },
      ],
    );
  });

  it('generates the correct distDir with one explicit target', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);

    assert.deepEqual(
      await targetResolver.resolve(DEFAULT_DISTPATH_FIXTURE_PATHS.one),
      [
        {
          name: 'browserModern',
          distDir: relative(
            path.join(DEFAULT_DISTPATH_FIXTURE_PATHS.one, 'dist'),
          ),
          distEntry: undefined,
          publicUrl: '/',
          env: {
            id: 'a9c07d094d038c73',
            context: 'browser',
            engines: {
              browsers: ['Chrome 80'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(DEFAULT_DISTPATH_FIXTURE_PATHS.one, 'package.json'),
            ),
            start: {
              line: 3,
              column: 5,
            },
            end: {
              line: 3,
              column: 20,
            },
          },
        },
      ],
    );
  });

  it('generates the correct distDirs with two explicit targets', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);

    assert.deepEqual(
      await targetResolver.resolve(DEFAULT_DISTPATH_FIXTURE_PATHS.two),
      [
        {
          name: 'browserModern',
          distDir: relative(
            path.join(
              DEFAULT_DISTPATH_FIXTURE_PATHS.two,
              'dist',
              'browserModern',
            ),
          ),
          distEntry: undefined,
          publicUrl: '/',
          env: {
            id: '1f28e9ceaf633d83',
            context: 'browser',
            engines: {
              browsers: ['last 1 version'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(DEFAULT_DISTPATH_FIXTURE_PATHS.two, 'package.json'),
            ),
            start: {
              line: 3,
              column: 5,
            },
            end: {
              line: 3,
              column: 20,
            },
          },
        },
        {
          name: 'browserLegacy',
          distDir: relative(
            path.join(
              DEFAULT_DISTPATH_FIXTURE_PATHS.two,
              'dist',
              'browserLegacy',
            ),
          ),
          distEntry: undefined,
          publicUrl: '/',
          env: {
            id: '824e113c03cab3c8',
            context: 'browser',
            engines: {
              browsers: ['IE 11'],
            },
            includeNodeModules: true,
            outputFormat: 'global',
            isLibrary: false,
            shouldOptimize: false,
            shouldScopeHoist: false,
            sourceMap: {},
            loc: undefined,
            sourceType: 'module',
          },
          loc: {
            filePath: relative(
              path.join(DEFAULT_DISTPATH_FIXTURE_PATHS.two, 'package.json'),
            ),
            start: {
              line: 10,
              column: 5,
            },
            end: {
              line: 10,
              column: 20,
            },
          },
        },
      ],
    );
  });

  it('rejects invalid or unknown fields', async () => {
    let code =
      '{\n' +
      '\t"targets": {\n' +
      '\t\t"main": {\n' +
      '\t\t\t"includeNodeModules": [\n' +
      '\t\t\t\t"react",\n' +
      '\t\t\t\ttrue\n' +
      '\t\t\t],\n' +
      '\t\t\t"context": "nodes",\n' +
      '\t\t\t"outputFormat": "module",\n' +
      '\t\t\t"sourceMap": {\n' +
      '\t\t\t\t"sourceRoot": "asd",\n' +
      '\t\t\t\t"inline": "false",\n' +
      '\t\t\t\t"verbose": true\n' +
      '\t\t\t},\n' +
      '\t\t\t"engines": {\n' +
      '\t\t\t\t"node": "12",\n' +
      '\t\t\t\t"browser": "Chrome 70"\n' +
      '\t\t\t}\n' +
      '\t\t}\n' +
      '\t}\n' +
      '}';
    let targetResolver = new TargetResolver(api, {
      ...DEFAULT_OPTIONS,
      ...JSON.parse(code),
    });

    // $FlowFixMe assert.rejects is Node 10+
    await assert.rejects(
      () => targetResolver.resolve(COMMON_TARGETS_FIXTURE_PATH),
      {
        message: 'Invalid target descriptor for target "main"',
        diagnostics: [
          {
            message: 'Invalid target descriptor for target "main"',
            origin: '@parcel/core',
            codeFrames: [
              {
                filePath: undefined,
                language: 'json',
                code,
                codeHighlights: [
                  {
                    start: {line: 6, column: 5},
                    end: {line: 6, column: 8},
                    message: 'Expected a wildcard or filepath',
                  },
                  {
                    start: {line: 8, column: 15},
                    end: {line: 8, column: 21},
                    message: 'Did you mean "node"?',
                  },
                  {
                    start: {line: 9, column: 20},
                    end: {line: 9, column: 27},
                    message: 'Did you mean "esmodule"?',
                  },
                  {
                    start: {line: 12, column: 15},
                    end: {line: 12, column: 21},
                    message: 'Expected type boolean',
                  },
                  {
                    start: {line: 13, column: 5},
                    end: {line: 13, column: 13},
                    message: 'Possible values: "inlineSources"',
                  },
                  {
                    start: {line: 17, column: 5},
                    end: {line: 17, column: 13},
                    message: 'Did you mean "browsers"?',
                  },
                ],
              },
            ],
          },
        ],
      },
    );
  });

  it('rejects invalid or unknown fields in package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let code = await fs.readFileSync(
      path.join(INVALID_TARGETS_FIXTURE_PATH, 'package.json'),
      'utf8',
    );
    // $FlowFixMe assert.rejects is Node 10+
    await assert.rejects(
      () => targetResolver.resolve(INVALID_TARGETS_FIXTURE_PATH),
      {
        diagnostics: [
          {
            message: 'Invalid target descriptor for target "module"',
            origin: '@parcel/core',
            codeFrames: [
              {
                filePath: path.join(
                  INVALID_TARGETS_FIXTURE_PATH,
                  'package.json',
                ),
                language: 'json',
                code,
                codeHighlights: [
                  {
                    start: {line: 9, column: 29},
                    end: {line: 9, column: 35},
                    message: 'Expected type boolean',
                  },
                  {
                    start: {line: 11, column: 7},
                    end: {line: 11, column: 17},
                    message: 'Did you mean "publicUrl"?',
                  },
                ],
              },
            ],
          },
        ],
      },
    );
  });

  it('rejects invalid engines in package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let code = await fs.readFileSync(
      path.join(INVALID_ENGINES_FIXTURE_PATH, 'package.json'),
      'utf8',
    );
    // $FlowFixMe assert.rejects is Node 10+
    await assert.rejects(
      () => targetResolver.resolve(INVALID_ENGINES_FIXTURE_PATH),
      {
        diagnostics: [
          {
            message: 'Invalid engines in package.json',
            origin: '@parcel/core',
            codeFrames: [
              {
                filePath: path.join(
                  INVALID_ENGINES_FIXTURE_PATH,
                  'package.json',
                ),
                language: 'json',
                code,
                codeHighlights: [
                  {
                    end: {
                      column: 13,
                      line: 8,
                    },
                    message: 'Did you mean "browsers"?',
                    start: {
                      column: 5,
                      line: 8,
                    },
                  },
                  {
                    end: {
                      column: 5,
                      line: 7,
                    },
                    message: 'Expected type string',
                    start: {
                      column: 13,
                      line: 5,
                    },
                  },
                ],
              },
            ],
          },
        ],
      },
    );
  });

  it('rejects target distpath in package.json', async () => {
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let code = await fs.readFileSync(
      path.join(INVALID_DISTPATH_FIXTURE_PATH, 'package.json'),
      'utf8',
    );
    // $FlowFixMe assert.rejects is Node 10+
    await assert.rejects(
      () => targetResolver.resolve(INVALID_DISTPATH_FIXTURE_PATH),
      {
        diagnostics: [
          {
            message: 'Invalid distPath for target "legacy"',
            origin: '@parcel/core',
            codeFrames: [
              {
                filePath: path.join(
                  INVALID_DISTPATH_FIXTURE_PATH,
                  'package.json',
                ),
                language: 'json',
                code,
                codeHighlights: [
                  {
                    end: {
                      column: 13,
                      line: 2,
                    },
                    message: 'Expected type string',
                    start: {
                      column: 13,
                      line: 2,
                    },
                  },
                ],
              },
            ],
          },
        ],
      },
    );
  });

  it('rejects duplicate target paths', async () => {
    let fixture = path.join(__dirname, 'fixtures/duplicate-targets');
    let targetResolver = new TargetResolver(api, DEFAULT_OPTIONS);
    let code = await fs.readFileSync(
      path.join(fixture, 'package.json'),
      'utf8',
    );
    // $FlowFixMe assert.rejects is Node 10+
    await assert.rejects(() => targetResolver.resolve(fixture), {
      diagnostics: [
        {
          message: md`Multiple targets have the same destination path "${path.normalize(
            'dist/index.js',
          )}"`,
          origin: '@parcel/core',
          codeFrames: [
            {
              filePath: path.join(fixture, 'package.json'),
              language: 'json',
              code,
              codeHighlights: [
                {
                  end: {
                    column: 25,
                    line: 2,
                  },
                  message: undefined,
                  start: {
                    column: 11,
                    line: 2,
                  },
                },
                {
                  end: {
                    column: 27,
                    line: 3,
                  },
                  message: undefined,
                  start: {
                    column: 13,
                    line: 3,
                  },
                },
              ],
            },
          ],
          hints: [
            'Try removing the duplicate targets, or changing the destination paths.',
          ],
        },
      ],
    });
  });
});