/** * @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 {existsSync, readFileSync, readdirSync, statSync} from 'fs'; import * as mockFs from 'mock-fs'; import {join} from 'path'; const Module = require('module'); import {mainNgcc} from '../../src/main'; import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers'; import {EntryPointPackageJson} from '../../src/packages/entry_point'; describe('ngcc main()', () => { beforeEach(createMockFileSystem); afterEach(restoreRealFileSystem); it('should run ngcc without errors for esm2015', () => { expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm2015']})) .not.toThrow(); }); it('should run ngcc without errors for esm5', () => { expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider: ['esm5']})) .not.toThrow(); }); describe('with targetEntryPointPath', () => { it('should only compile the given package entry-point (and its dependencies).', () => { mainNgcc({basePath: '/node_modules', targetEntryPointPath: '@angular/common/http'}); expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ module: '0.0.0-PLACEHOLDER', es2015: '0.0.0-PLACEHOLDER', esm5: '0.0.0-PLACEHOLDER', esm2015: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', fesm2015: '0.0.0-PLACEHOLDER', }); // * `common` is a dependency of `common/http`, so is compiled. expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ module: '0.0.0-PLACEHOLDER', es2015: '0.0.0-PLACEHOLDER', esm5: '0.0.0-PLACEHOLDER', esm2015: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', fesm2015: '0.0.0-PLACEHOLDER', }); // * `core` is a dependency of `common`, so is compiled. expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ module: '0.0.0-PLACEHOLDER', es2015: '0.0.0-PLACEHOLDER', esm5: '0.0.0-PLACEHOLDER', esm2015: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', fesm2015: '0.0.0-PLACEHOLDER', }); // * `common/testing` is not a dependency of `common/http` so is not compiled. expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toBeUndefined(); }); it('should mark a non-Angular package target as processed', () => { mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'test-package'}); // `test-package` has no Angular but is marked as processed. expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({ es2015: '0.0.0-PLACEHOLDER', }); // * `core` is a dependency of `test-package`, but it is not processed, since test-package // was not processed. expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined(); }); }); describe('with propertiesToConsider', () => { it('should only compile the entry-point formats given in the `propertiesToConsider` list', () => { mainNgcc({ basePath: '/node_modules', propertiesToConsider: ['main', 'esm5', 'module', 'fesm5'] }); // * the `main` property is UMD, which is not yet supported. // * none of the ES2015 formats are compiled as they are not on the `propertiesToConsider` // list. expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ esm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', }); expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ esm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', }); expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({ esm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', }); expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ esm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', fesm5: '0.0.0-PLACEHOLDER', }); }); }); describe('with compileAllFormats set to false', () => { it('should only compile the first matching format', () => { mainNgcc({ basePath: '/node_modules', propertiesToConsider: ['main', 'module', 'fesm5', 'esm5'], compileAllFormats: false }); // * The `main` is UMD, which is not yet supported, and so is not compiled. // * In the Angular packages fesm5 and module have the same underlying format, // so both are marked as compiled. // * The `esm5` is not compiled because we stopped after the `fesm5` format. expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({ fesm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', }); expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({ fesm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', }); expect(loadPackage('@angular/common/testing').__processed_by_ivy_ngcc__).toEqual({ fesm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', }); expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({ fesm5: '0.0.0-PLACEHOLDER', module: '0.0.0-PLACEHOLDER', }); }); }); describe('with createNewEntryPointFormats', () => { it('should create new files rather than overwriting the originals', () => { const ANGULAR_CORE_IMPORT_REGEX = /import \* as ɵngcc\d+ from '@angular\/core';/; mainNgcc({ basePath: '/node_modules', createNewEntryPointFormats: true, propertiesToConsider: ['esm5'] }); // Updates the package.json expect(loadPackage('@angular/common').esm5).toEqual('./esm5/common.js'); expect((loadPackage('@angular/common') as any).esm5_ivy_ngcc) .toEqual('__ivy_ngcc__/esm5/common.js'); // Doesn't touch original files expect(readFileSync(`/node_modules/@angular/common/esm5/src/common_module.js`, 'utf8')) .not.toMatch(ANGULAR_CORE_IMPORT_REGEX); // Or create a backup of the original expect(existsSync(`/node_modules/@angular/common/esm5/src/common_module.js.__ivy_ngcc_bak`)) .toBe(false); // Creates new files expect(readFileSync( `/node_modules/@angular/common/__ivy_ngcc__/esm5/src/common_module.js`, 'utf8')) .toMatch(ANGULAR_CORE_IMPORT_REGEX); // Copies over files (unchanged) that did not need compiling expect(existsSync(`/node_modules/@angular/common/__ivy_ngcc__/esm5/src/version.js`)); expect(readFileSync(`/node_modules/@angular/common/__ivy_ngcc__/esm5/src/version.js`, 'utf8')) .toEqual(readFileSync(`/node_modules/@angular/common/esm5/src/version.js`, 'utf8')); // Overwrites .d.ts files (as usual) expect(readFileSync(`/node_modules/@angular/common/common.d.ts`, 'utf8')) .toMatch(ANGULAR_CORE_IMPORT_REGEX); expect(existsSync(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`)).toBe(true); }); }); }); function createMockFileSystem() { mockFs({ '/node_modules/@angular': loadAngularPackages(), '/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')), '/node_modules/tslib': loadDirectory(resolveNpmTreeArtifact('tslib', 'tslib.js')), '/node_modules/test-package': { 'package.json': '{"name": "test-package", "es2015": "./index.js", "typings": "./index.d.ts"}', 'index.js': 'import {AppModule} from "@angular/common"; export class MyApp extends AppModule;', 'index.d.s': 'import {AppModule} from "@angular/common"; export declare class MyApp extends AppModule;', } }); spyOn(Module, '_resolveFilename').and.callFake(mockResolve); } function restoreRealFileSystem() { mockFs.restore(); } /** Load the built Angular packages into an in-memory structure. */ function loadAngularPackages(): Directory { const packagesDirectory: Directory = {}; getAngularPackagesFromRunfiles().forEach( ({name, pkgPath}) => { packagesDirectory[name] = loadDirectory(pkgPath); }); return packagesDirectory; } /** * Load real files from the filesystem into an "in-memory" structure, * which can be used with `mock-fs`. * @param directoryPath the path to the directory we want to load. */ function loadDirectory(directoryPath: string): Directory { const directory: Directory = {}; readdirSync(directoryPath).forEach(item => { const itemPath = join(directoryPath, item); if (statSync(itemPath).isDirectory()) { directory[item] = loadDirectory(itemPath); } else { directory[item] = readFileSync(itemPath, 'utf-8'); } }); return directory; } interface Directory { [pathSegment: string]: string|Directory; } /** * A mock implementation of the node.js Module._resolveFilename function, * which we are spying on to support mocking out the file-system in these tests. * * @param request the path to a module that needs resolving. */ function mockResolve(request: string): string|null { if (existsSync(request)) { const stat = statSync(request); if (stat.isFile()) { return request; } else if (stat.isDirectory()) { const pIndex = mockResolve(request + '/index'); if (pIndex && existsSync(pIndex)) { return pIndex; } } } for (const ext of ['.js', '.d.ts']) { if (existsSync(request + ext)) { return request + ext; } } if (request.indexOf('/node_modules') === 0) { // We already tried adding node_modules so give up. return null; } else { return mockResolve(join('/node_modules', request)); } } function loadPackage(packageName: string): EntryPointPackageJson { return JSON.parse(readFileSync(`/node_modules/${packageName}/package.json`, 'utf8')); }