refactor(ivy): ngcc - move the dependency resolving stuff around (#29643)
For UMD/RequireJS support we will need to have multiple `DependencyHost` implementations. This commit prepares the ground for that. PR Close #29643
This commit is contained in:

committed by
Andrew Kushnir

parent
5ced8fbbd5
commit
1fd2cc6340
@ -1,231 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as mockFs from 'mock-fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {DependencyHost} from '../../src/packages/dependency_host';
|
||||
import {ModuleResolver} from '../../src/packages/module_resolver';
|
||||
|
||||
const _ = AbsoluteFsPath.from;
|
||||
|
||||
describe('DependencyHost', () => {
|
||||
let host: DependencyHost;
|
||||
beforeEach(() => host = new DependencyHost(new ModuleResolver()));
|
||||
|
||||
describe('getDependencies()', () => {
|
||||
beforeEach(createMockFileSystem);
|
||||
afterEach(restoreRealFileSystem);
|
||||
|
||||
it('should not generate a TS AST if the source does not contain any imports or re-exports',
|
||||
() => {
|
||||
spyOn(ts, 'createSourceFile');
|
||||
host.computeDependencies(_('/no/imports/or/re-exports/index.js'));
|
||||
expect(ts.createSourceFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should resolve all the external imports of the source file', () => {
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/external/imports/index.js'));
|
||||
expect(dependencies.size).toBe(2);
|
||||
expect(missing.size).toBe(0);
|
||||
expect(deepImports.size).toBe(0);
|
||||
expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true);
|
||||
expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should resolve all the external re-exports of the source file', () => {
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/external/re-exports/index.js'));
|
||||
expect(dependencies.size).toBe(2);
|
||||
expect(missing.size).toBe(0);
|
||||
expect(deepImports.size).toBe(0);
|
||||
expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true);
|
||||
expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should capture missing external imports', () => {
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/external/imports-missing/index.js'));
|
||||
|
||||
expect(dependencies.size).toBe(1);
|
||||
expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true);
|
||||
expect(missing.size).toBe(1);
|
||||
expect(missing.has('missing')).toBe(true);
|
||||
expect(deepImports.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should not register deep imports as missing', () => {
|
||||
// This scenario verifies the behavior of the dependency analysis when an external import
|
||||
// is found that does not map to an entry-point but still exists on disk, i.e. a deep import.
|
||||
// Such deep imports are captured for diagnostics purposes.
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/external/deep-import/index.js'));
|
||||
|
||||
expect(dependencies.size).toBe(0);
|
||||
expect(missing.size).toBe(0);
|
||||
expect(deepImports.size).toBe(1);
|
||||
expect(deepImports.has('/node_modules/lib-1/deep/import')).toBe(true);
|
||||
});
|
||||
|
||||
it('should recurse into internal dependencies', () => {
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/internal/outer/index.js'));
|
||||
|
||||
expect(dependencies.size).toBe(1);
|
||||
expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true);
|
||||
expect(missing.size).toBe(0);
|
||||
expect(deepImports.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should handle circular internal dependencies', () => {
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/internal/circular-a/index.js'));
|
||||
expect(dependencies.size).toBe(2);
|
||||
expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true);
|
||||
expect(dependencies.has(_('/node_modules/lib-1/sub-1'))).toBe(true);
|
||||
expect(missing.size).toBe(0);
|
||||
expect(deepImports.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should support `paths` alias mappings when resolving modules', () => {
|
||||
host = new DependencyHost(new ModuleResolver({
|
||||
baseUrl: '/dist',
|
||||
paths: {
|
||||
'@app/*': ['*'],
|
||||
'@lib/*/test': ['lib/*/test'],
|
||||
}
|
||||
}));
|
||||
const {dependencies, missing, deepImports} =
|
||||
host.computeDependencies(_('/path-alias/index.js'));
|
||||
expect(dependencies.size).toBe(4);
|
||||
expect(dependencies.has(_('/dist/components'))).toBe(true);
|
||||
expect(dependencies.has(_('/dist/shared'))).toBe(true);
|
||||
expect(dependencies.has(_('/dist/lib/shared/test'))).toBe(true);
|
||||
expect(dependencies.has(_('/node_modules/lib-1'))).toBe(true);
|
||||
expect(missing.size).toBe(0);
|
||||
expect(deepImports.size).toBe(0);
|
||||
});
|
||||
|
||||
function createMockFileSystem() {
|
||||
mockFs({
|
||||
'/no/imports/or/re-exports/index.js': '// some text but no import-like statements',
|
||||
'/no/imports/or/re-exports/package.json': '{"esm2015": "./index.js"}',
|
||||
'/no/imports/or/re-exports/index.metadata.json': 'MOCK METADATA',
|
||||
'/external/imports/index.js': `import {X} from 'lib-1';\nimport {Y} from 'lib-1/sub-1';`,
|
||||
'/external/imports/package.json': '{"esm2015": "./index.js"}',
|
||||
'/external/imports/index.metadata.json': 'MOCK METADATA',
|
||||
'/external/re-exports/index.js': `export {X} from 'lib-1';\nexport {Y} from 'lib-1/sub-1';`,
|
||||
'/external/re-exports/package.json': '{"esm2015": "./index.js"}',
|
||||
'/external/re-exports/index.metadata.json': 'MOCK METADATA',
|
||||
'/external/imports-missing/index.js':
|
||||
`import {X} from 'lib-1';\nimport {Y} from 'missing';`,
|
||||
'/external/imports-missing/package.json': '{"esm2015": "./index.js"}',
|
||||
'/external/imports-missing/index.metadata.json': 'MOCK METADATA',
|
||||
'/external/deep-import/index.js': `import {Y} from 'lib-1/deep/import';`,
|
||||
'/external/deep-import/package.json': '{"esm2015": "./index.js"}',
|
||||
'/external/deep-import/index.metadata.json': 'MOCK METADATA',
|
||||
'/internal/outer/index.js': `import {X} from '../inner';`,
|
||||
'/internal/outer/package.json': '{"esm2015": "./index.js"}',
|
||||
'/internal/outer/index.metadata.json': 'MOCK METADATA',
|
||||
'/internal/inner/index.js': `import {Y} from 'lib-1/sub-1'; export declare class X {}`,
|
||||
'/internal/circular-a/index.js':
|
||||
`import {B} from '../circular-b'; import {X} from '../circular-b'; export {Y} from 'lib-1/sub-1';`,
|
||||
'/internal/circular-b/index.js':
|
||||
`import {A} from '../circular-a'; import {Y} from '../circular-a'; export {X} from 'lib-1';`,
|
||||
'/internal/circular-a/package.json': '{"esm2015": "./index.js"}',
|
||||
'/internal/circular-a/index.metadata.json': 'MOCK METADATA',
|
||||
'/re-directed/index.js': `import {Z} from 'lib-1/sub-2';`,
|
||||
'/re-directed/package.json': '{"esm2015": "./index.js"}',
|
||||
'/re-directed/index.metadata.json': 'MOCK METADATA',
|
||||
'/path-alias/index.js':
|
||||
`import {TestHelper} from '@app/components';\nimport {Service} from '@app/shared';\nimport {TestHelper} from '@lib/shared/test';\nimport {X} from 'lib-1';`,
|
||||
'/path-alias/package.json': '{"esm2015": "./index.js"}',
|
||||
'/path-alias/index.metadata.json': 'MOCK METADATA',
|
||||
'/node_modules/lib-1/index.js': 'export declare class X {}',
|
||||
'/node_modules/lib-1/package.json': '{"esm2015": "./index.js"}',
|
||||
'/node_modules/lib-1/index.metadata.json': 'MOCK METADATA',
|
||||
'/node_modules/lib-1/deep/import/index.js': 'export declare class DeepImport {}',
|
||||
'/node_modules/lib-1/sub-1/index.js': 'export declare class Y {}',
|
||||
'/node_modules/lib-1/sub-1/package.json': '{"esm2015": "./index.js"}',
|
||||
'/node_modules/lib-1/sub-1/index.metadata.json': 'MOCK METADATA',
|
||||
'/node_modules/lib-1/sub-2.js': `export * from './sub-2/sub-2';`,
|
||||
'/node_modules/lib-1/sub-2/sub-2.js': `export declare class Z {}';`,
|
||||
'/node_modules/lib-1/sub-2/package.json': '{"esm2015": "./sub-2.js"}',
|
||||
'/node_modules/lib-1/sub-2/sub-2.metadata.json': 'MOCK METADATA',
|
||||
'/dist/components/index.js': `class MyComponent {};`,
|
||||
'/dist/components/package.json': '{"esm2015": "./index.js"}',
|
||||
'/dist/components/index.metadata.json': 'MOCK METADATA',
|
||||
'/dist/shared/index.js': `import {X} from 'lib-1';\nexport class Service {}`,
|
||||
'/dist/shared/package.json': '{"esm2015": "./index.js"}',
|
||||
'/dist/shared/index.metadata.json': 'MOCK METADATA',
|
||||
'/dist/lib/shared/test/index.js': `export class TestHelper {}`,
|
||||
'/dist/lib/shared/test/package.json': '{"esm2015": "./index.js"}',
|
||||
'/dist/lib/shared/test/index.metadata.json': 'MOCK METADATA',
|
||||
});
|
||||
}
|
||||
|
||||
function restoreRealFileSystem() { mockFs.restore(); }
|
||||
});
|
||||
|
||||
describe('isStringImportOrReexport', () => {
|
||||
it('should return true if the statement is an import', () => {
|
||||
expect(host.isStringImportOrReexport(createStatement('import {X} from "some/x";')))
|
||||
.toBe(true);
|
||||
expect(host.isStringImportOrReexport(createStatement('import * as X from "some/x";')))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the statement is a re-export', () => {
|
||||
expect(host.isStringImportOrReexport(createStatement('export {X} from "some/x";')))
|
||||
.toBe(true);
|
||||
expect(host.isStringImportOrReexport(createStatement('export * from "some/x";'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the statement is not an import or a re-export', () => {
|
||||
expect(host.isStringImportOrReexport(createStatement('class X {}'))).toBe(false);
|
||||
expect(host.isStringImportOrReexport(createStatement('export function foo() {}')))
|
||||
.toBe(false);
|
||||
expect(host.isStringImportOrReexport(createStatement('export const X = 10;'))).toBe(false);
|
||||
});
|
||||
|
||||
function createStatement(source: string) {
|
||||
return ts
|
||||
.createSourceFile('source.js', source, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS)
|
||||
.statements[0];
|
||||
}
|
||||
});
|
||||
|
||||
describe('hasImportOrReexportStatements', () => {
|
||||
it('should return true if there is an import statement', () => {
|
||||
expect(host.hasImportOrReexportStatements('import {X} from "some/x";')).toBe(true);
|
||||
expect(host.hasImportOrReexportStatements('import * as X from "some/x";')).toBe(true);
|
||||
expect(
|
||||
host.hasImportOrReexportStatements('blah blah\n\n import {X} from "some/x";\nblah blah'))
|
||||
.toBe(true);
|
||||
expect(host.hasImportOrReexportStatements('\t\timport {X} from "some/x";')).toBe(true);
|
||||
});
|
||||
it('should return true if there is a re-export statement', () => {
|
||||
expect(host.hasImportOrReexportStatements('export {X} from "some/x";')).toBe(true);
|
||||
expect(
|
||||
host.hasImportOrReexportStatements('blah blah\n\n export {X} from "some/x";\nblah blah'))
|
||||
.toBe(true);
|
||||
expect(host.hasImportOrReexportStatements('\t\texport {X} from "some/x";')).toBe(true);
|
||||
expect(host.hasImportOrReexportStatements(
|
||||
'blah blah\n\n export * from "@angular/core;\nblah blah'))
|
||||
.toBe(true);
|
||||
});
|
||||
it('should return false if there is no import nor re-export statement', () => {
|
||||
expect(host.hasImportOrReexportStatements('blah blah')).toBe(false);
|
||||
expect(host.hasImportOrReexportStatements('export function moo() {}')).toBe(false);
|
||||
expect(
|
||||
host.hasImportOrReexportStatements('Some text that happens to include the word import'))
|
||||
.toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,154 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {DependencyHost} from '../../src/packages/dependency_host';
|
||||
import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver';
|
||||
import {EntryPoint} from '../../src/packages/entry_point';
|
||||
import {ModuleResolver} from '../../src/packages/module_resolver';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
|
||||
const _ = AbsoluteFsPath.from;
|
||||
|
||||
describe('DependencyResolver', () => {
|
||||
let host: DependencyHost;
|
||||
let resolver: DependencyResolver;
|
||||
beforeEach(() => {
|
||||
host = new DependencyHost(new ModuleResolver());
|
||||
resolver = new DependencyResolver(new MockLogger(), host);
|
||||
});
|
||||
describe('sortEntryPointsByDependency()', () => {
|
||||
const first = {
|
||||
path: _('/first'),
|
||||
packageJson: {esm5: './index.js'},
|
||||
compiledByAngular: true
|
||||
} as EntryPoint;
|
||||
const second = {
|
||||
path: _('/second'),
|
||||
packageJson: {esm2015: './sub/index.js'},
|
||||
compiledByAngular: true
|
||||
} as EntryPoint;
|
||||
const third = {
|
||||
path: _('/third'),
|
||||
packageJson: {fesm5: './index.js'},
|
||||
compiledByAngular: true
|
||||
} as EntryPoint;
|
||||
const fourth = {
|
||||
path: _('/fourth'),
|
||||
packageJson: {fesm2015: './sub2/index.js'},
|
||||
compiledByAngular: true
|
||||
} as EntryPoint;
|
||||
const fifth = {
|
||||
path: _('/fifth'),
|
||||
packageJson: {module: './index.js'},
|
||||
compiledByAngular: true
|
||||
} as EntryPoint;
|
||||
|
||||
const dependencies = {
|
||||
[_('/first/index.js')]: {resolved: [second.path, third.path, '/ignored-1'], missing: []},
|
||||
[_('/second/sub/index.js')]: {resolved: [third.path, fifth.path], missing: []},
|
||||
[_('/third/index.js')]: {resolved: [fourth.path, '/ignored-2'], missing: []},
|
||||
[_('/fourth/sub2/index.js')]: {resolved: [fifth.path], missing: []},
|
||||
[_('/fifth/index.js')]: {resolved: [], missing: []},
|
||||
};
|
||||
|
||||
it('should order the entry points by their dependency on each other', () => {
|
||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies));
|
||||
const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]);
|
||||
expect(result.entryPoints).toEqual([fifth, fourth, third, second, first]);
|
||||
});
|
||||
|
||||
it('should remove entry-points that have missing direct dependencies', () => {
|
||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
||||
[_('/first/index.js')]: {resolved: [], missing: ['/missing']},
|
||||
[_('/second/sub/index.js')]: {resolved: [], missing: []},
|
||||
}));
|
||||
const result = resolver.sortEntryPointsByDependency([first, second]);
|
||||
expect(result.entryPoints).toEqual([second]);
|
||||
expect(result.invalidEntryPoints).toEqual([
|
||||
{entryPoint: first, missingDependencies: ['/missing']},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove entry points that depended upon an invalid entry-point', () => {
|
||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
||||
[_('/first/index.js')]: {resolved: [second.path], missing: []},
|
||||
[_('/second/sub/index.js')]: {resolved: [], missing: ['/missing']},
|
||||
[_('/third/index.js')]: {resolved: [], missing: []},
|
||||
}));
|
||||
// Note that we will process `first` before `second`, which has the missing dependency.
|
||||
const result = resolver.sortEntryPointsByDependency([first, second, third]);
|
||||
expect(result.entryPoints).toEqual([third]);
|
||||
expect(result.invalidEntryPoints).toEqual([
|
||||
{entryPoint: second, missingDependencies: ['/missing']},
|
||||
{entryPoint: first, missingDependencies: ['/missing']},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should remove entry points that will depend upon an invalid entry-point', () => {
|
||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
||||
[_('/first/index.js')]: {resolved: [second.path], missing: []},
|
||||
[_('/second/sub/index.js')]: {resolved: [], missing: ['/missing']},
|
||||
[_('/third/index.js')]: {resolved: [], missing: []},
|
||||
}));
|
||||
// Note that we will process `first` after `second`, which has the missing dependency.
|
||||
const result = resolver.sortEntryPointsByDependency([second, first, third]);
|
||||
expect(result.entryPoints).toEqual([third]);
|
||||
expect(result.invalidEntryPoints).toEqual([
|
||||
{entryPoint: second, missingDependencies: ['/missing']},
|
||||
{entryPoint: first, missingDependencies: [second.path]},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should error if the entry point does not have either the esm5 nor esm2015 formats', () => {
|
||||
expect(() => resolver.sortEntryPointsByDependency([
|
||||
{ path: '/first', packageJson: {}, compiledByAngular: true } as EntryPoint
|
||||
])).toThrowError(`There is no format with import statements in '/first' entry-point.`);
|
||||
});
|
||||
|
||||
it('should capture any dependencies that were ignored', () => {
|
||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies));
|
||||
const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]);
|
||||
expect(result.ignoredDependencies).toEqual([
|
||||
{entryPoint: first, dependencyPath: '/ignored-1'},
|
||||
{entryPoint: third, dependencyPath: '/ignored-2'},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only return dependencies of the target, if provided', () => {
|
||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies));
|
||||
const entryPoints = [fifth, first, fourth, second, third];
|
||||
let sorted: SortedEntryPointsInfo;
|
||||
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
|
||||
expect(sorted.entryPoints).toEqual([fifth, fourth, third, second, first]);
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, second);
|
||||
expect(sorted.entryPoints).toEqual([fifth, fourth, third, second]);
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, third);
|
||||
expect(sorted.entryPoints).toEqual([fifth, fourth, third]);
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, fourth);
|
||||
expect(sorted.entryPoints).toEqual([fifth, fourth]);
|
||||
sorted = resolver.sortEntryPointsByDependency(entryPoints, fifth);
|
||||
expect(sorted.entryPoints).toEqual([fifth]);
|
||||
});
|
||||
|
||||
interface DepMap {
|
||||
[path: string]: {resolved: string[], missing: string[]};
|
||||
}
|
||||
|
||||
function createFakeComputeDependencies(deps: DepMap) {
|
||||
return (entryPoint: string) => {
|
||||
const dependencies = new Set();
|
||||
const missing = new Set();
|
||||
const deepImports = new Set();
|
||||
deps[entryPoint].resolved.forEach(dep => dependencies.add(dep));
|
||||
deps[entryPoint].missing.forEach(dep => missing.add(dep));
|
||||
return {dependencies, missing, deepImports};
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
@ -9,11 +9,11 @@
|
||||
import * as mockFs from 'mock-fs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {DependencyHost} from '../../src/packages/dependency_host';
|
||||
import {DependencyResolver} from '../../src/packages/dependency_resolver';
|
||||
import {DependencyResolver} from '../../src/dependencies/dependency_resolver';
|
||||
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
|
||||
import {ModuleResolver} from '../../src/dependencies/module_resolver';
|
||||
import {EntryPoint} from '../../src/packages/entry_point';
|
||||
import {EntryPointFinder} from '../../src/packages/entry_point_finder';
|
||||
import {ModuleResolver} from '../../src/packages/module_resolver';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
|
||||
const _ = AbsoluteFsPath.from;
|
||||
@ -22,7 +22,8 @@ describe('findEntryPoints()', () => {
|
||||
let resolver: DependencyResolver;
|
||||
let finder: EntryPointFinder;
|
||||
beforeEach(() => {
|
||||
resolver = new DependencyResolver(new MockLogger(), new DependencyHost(new ModuleResolver()));
|
||||
resolver =
|
||||
new DependencyResolver(new MockLogger(), new EsmDependencyHost(new ModuleResolver()));
|
||||
spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => {
|
||||
return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []};
|
||||
});
|
||||
|
@ -1,212 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as mockFs from 'mock-fs';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/packages/module_resolver';
|
||||
|
||||
const _ = AbsoluteFsPath.from;
|
||||
|
||||
function createMockFileSystem() {
|
||||
mockFs({
|
||||
'/libs': {
|
||||
'local-package': {
|
||||
'package.json': 'PACKAGE.JSON for local-package',
|
||||
'index.js': `import {X} from './x';`,
|
||||
'x.js': `export class X {}`,
|
||||
'sub-folder': {
|
||||
'index.js': `import {X} from '../x';`,
|
||||
},
|
||||
'node_modules': {
|
||||
'package-1': {
|
||||
'sub-folder': {'index.js': `export class Z {}`},
|
||||
'package.json': 'PACKAGE.JSON for package-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
'node_modules': {
|
||||
'package-2': {
|
||||
'package.json': 'PACKAGE.JSON for package-2',
|
||||
'node_modules': {
|
||||
'package-3': {
|
||||
'package.json': 'PACKAGE.JSON for package-3',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/dist': {
|
||||
'package-4': {
|
||||
'x.js': `export class X {}`,
|
||||
'package.json': 'PACKAGE.JSON for package-4',
|
||||
'sub-folder': {'index.js': `import {X} from '@shared/package-4/x';`},
|
||||
},
|
||||
'sub-folder': {
|
||||
'package-4': {
|
||||
'package.json': 'PACKAGE.JSON for package-4',
|
||||
},
|
||||
'package-5': {
|
||||
'package.json': 'PACKAGE.JSON for package-5',
|
||||
'post-fix': {
|
||||
'package.json': 'PACKAGE.JSON for package-5/post-fix',
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
'/node_modules': {
|
||||
'top-package': {
|
||||
'package.json': 'PACKAGE.JSON for top-package',
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restoreRealFileSystem() {
|
||||
mockFs.restore();
|
||||
}
|
||||
|
||||
describe('ModuleResolver', () => {
|
||||
beforeEach(createMockFileSystem);
|
||||
afterEach(restoreRealFileSystem);
|
||||
|
||||
describe('resolveModule()', () => {
|
||||
describe('with relative paths', () => {
|
||||
it('should resolve sibling, child and aunt modules', () => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(resolver.resolveModuleImport('./x', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js')));
|
||||
expect(resolver.resolveModuleImport('./sub-folder', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedRelativeModule(_('/libs/local-package/sub-folder/index.js')));
|
||||
expect(resolver.resolveModuleImport('../x', _('/libs/local-package/sub-folder/index.js')))
|
||||
.toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js')));
|
||||
});
|
||||
|
||||
it('should return `null` if the resolved module relative module does not exist', () => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with non-mapped external paths', () => {
|
||||
it('should resolve to the package.json of a local node_modules package', () => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1')));
|
||||
expect(
|
||||
resolver.resolveModuleImport('package-1', _('/libs/local-package/sub-folder/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1')));
|
||||
expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/x.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1')));
|
||||
});
|
||||
|
||||
it('should resolve to the package.json of a higher node_modules package', () => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(resolver.resolveModuleImport('package-2', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2')));
|
||||
expect(resolver.resolveModuleImport('top-package', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/node_modules/top-package')));
|
||||
});
|
||||
|
||||
it('should return `null` if the package cannot be found', () => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should return `null` if the package is not accessible because it is in a inner node_modules package',
|
||||
() => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(resolver.resolveModuleImport('package-3', _('/libs/local-package/index.js')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should identify deep imports into an external module', () => {
|
||||
const resolver = new ModuleResolver();
|
||||
expect(
|
||||
resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js')))
|
||||
.toEqual(
|
||||
new ResolvedDeepImport(_('/libs/local-package/node_modules/package-1/sub-folder')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('with mapped path external modules', () => {
|
||||
it('should resolve to the package.json of simple mapped packages', () => {
|
||||
const resolver =
|
||||
new ModuleResolver({baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
|
||||
|
||||
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/package-4')));
|
||||
|
||||
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5')));
|
||||
});
|
||||
|
||||
it('should select the best match by the length of prefix before the *', () => {
|
||||
const resolver = new ModuleResolver({
|
||||
baseUrl: '/dist',
|
||||
paths: {
|
||||
'@lib/*': ['*'],
|
||||
'@lib/sub-folder/*': ['*'],
|
||||
}
|
||||
});
|
||||
|
||||
// We should match the second path (e.g. `'@lib/sub-folder/*'`), which will actually map to
|
||||
// `*` and so the final resolved path will not include the `sub-folder` segment.
|
||||
expect(resolver.resolveModuleImport(
|
||||
'@lib/sub-folder/package-4', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/package-4')));
|
||||
});
|
||||
|
||||
it('should follow the ordering of `paths` when matching mapped packages', () => {
|
||||
let resolver: ModuleResolver;
|
||||
|
||||
resolver = new ModuleResolver({baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
|
||||
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/package-4')));
|
||||
|
||||
resolver = new ModuleResolver({baseUrl: '/dist', paths: {'*': ['sub-folder/*', '*']}});
|
||||
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4')));
|
||||
});
|
||||
|
||||
it('should resolve packages when the path mappings have post-fixes', () => {
|
||||
const resolver =
|
||||
new ModuleResolver({baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}});
|
||||
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix')));
|
||||
});
|
||||
|
||||
it('should match paths against complex path matchers', () => {
|
||||
const resolver =
|
||||
new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}});
|
||||
expect(resolver.resolveModuleImport('@shared/package-4', _('/libs/local-package/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4')));
|
||||
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
|
||||
.toBe(null);
|
||||
});
|
||||
|
||||
it('should resolve path as "relative" if the mapped path is inside the current package',
|
||||
() => {
|
||||
const resolver = new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['*']}});
|
||||
expect(resolver.resolveModuleImport(
|
||||
'@shared/package-4/x', _('/dist/package-4/sub-folder/index.js')))
|
||||
.toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js')));
|
||||
});
|
||||
|
||||
it('should resolve paths where the wildcard matches more than one path segment', () => {
|
||||
const resolver =
|
||||
new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}});
|
||||
expect(
|
||||
resolver.resolveModuleImport(
|
||||
'@shared/sub-folder/package-5/post-fix', _('/dist/package-4/sub-folder/index.js')))
|
||||
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix')));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user