refactor(ivy): ngcc - implement abstract FileSystem (#29643)

This commit introduces a new interface, which abstracts access
to the underlying `FileSystem`. There is initially one concrete
implementation, `NodeJsFileSystem`, which is simply wrapping the
`fs` library of NodeJs.

Going forward, we can provide a `MockFileSystem` for test, which
should allow us to stop using `mock-fs` for most of the unit tests.
We could also implement a `CachedFileSystem` that may improve the
performance of ngcc.

PR Close #29643
This commit is contained in:
Pete Bacon Darwin
2019-04-28 20:47:57 +01:00
committed by Andrew Kushnir
parent 1fd2cc6340
commit 16d7dde2ad
36 changed files with 590 additions and 353 deletions

View File

@ -9,6 +9,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyResolver, SortedEntryPointsInfo} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint} from '../../src/packages/entry_point';
import {MockLogger} from '../helpers/mock_logger';
@ -18,7 +19,8 @@ describe('DependencyResolver', () => {
let host: EsmDependencyHost;
let resolver: DependencyResolver;
beforeEach(() => {
host = new EsmDependencyHost(new ModuleResolver());
const fs = new NodeJSFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs));
resolver = new DependencyResolver(new MockLogger(), host);
});
describe('sortEntryPointsByDependency()', () => {

View File

@ -11,12 +11,16 @@ import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
const _ = AbsoluteFsPath.from;
describe('DependencyHost', () => {
let host: EsmDependencyHost;
beforeEach(() => host = new EsmDependencyHost(new ModuleResolver()));
beforeEach(() => {
const fs = new NodeJSFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs));
});
describe('getDependencies()', () => {
beforeEach(createMockFileSystem);
@ -94,13 +98,14 @@ describe('DependencyHost', () => {
});
it('should support `paths` alias mappings when resolving modules', () => {
host = new EsmDependencyHost(new ModuleResolver({
baseUrl: '/dist',
paths: {
'@app/*': ['*'],
'@lib/*/test': ['lib/*/test'],
}
}));
const fs = new NodeJSFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs, {
baseUrl: '/dist',
paths: {
'@app/*': ['*'],
'@lib/*/test': ['lib/*/test'],
}
}));
const {dependencies, missing, deepImports} = host.findDependencies(_('/path-alias/index.js'));
expect(dependencies.size).toBe(4);
expect(dependencies.has(_('/dist/components'))).toBe(true);

View File

@ -10,6 +10,7 @@ import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
const _ = AbsoluteFsPath.from;
@ -78,7 +79,8 @@ describe('ModuleResolver', () => {
describe('resolveModule()', () => {
describe('with relative paths', () => {
it('should resolve sibling, child and aunt modules', () => {
const resolver = new ModuleResolver();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
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')))
@ -88,14 +90,16 @@ describe('ModuleResolver', () => {
});
it('should return `null` if the resolved module relative module does not exist', () => {
const resolver = new ModuleResolver();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
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();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1')));
expect(
@ -106,7 +110,8 @@ describe('ModuleResolver', () => {
});
it('should resolve to the package.json of a higher node_modules package', () => {
const resolver = new ModuleResolver();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
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')))
@ -114,20 +119,23 @@ describe('ModuleResolver', () => {
});
it('should return `null` if the package cannot be found', () => {
const resolver = new ModuleResolver();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
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();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
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();
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(
resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js')))
.toEqual(
@ -137,8 +145,9 @@ describe('ModuleResolver', () => {
describe('with mapped path external modules', () => {
it('should resolve to the package.json of simple mapped packages', () => {
const fs = new NodeJSFileSystem();
const resolver =
new ModuleResolver({baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/package-4')));
@ -148,7 +157,8 @@ describe('ModuleResolver', () => {
});
it('should select the best match by the length of prefix before the *', () => {
const resolver = new ModuleResolver({
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs, {
baseUrl: '/dist',
paths: {
'@lib/*': ['*'],
@ -166,25 +176,28 @@ describe('ModuleResolver', () => {
it('should follow the ordering of `paths` when matching mapped packages', () => {
let resolver: ModuleResolver;
resolver = new ModuleResolver({baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
const fs = new NodeJSFileSystem();
resolver = new ModuleResolver(fs, {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/*', '*']}});
resolver = new ModuleResolver(fs, {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 fs = new NodeJSFileSystem();
const resolver =
new ModuleResolver({baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}});
new ModuleResolver(fs, {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 fs = new NodeJSFileSystem();
const resolver =
new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}});
new ModuleResolver(fs, {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')))
@ -193,15 +206,17 @@ describe('ModuleResolver', () => {
it('should resolve path as "relative" if the mapped path is inside the current package',
() => {
const resolver = new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['*']}});
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs, {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']}});
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(
fs, {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')))