feat(@angular-devkit/build-angular): integrate JIT mode linker

With this change we intergate JIT mode linker into the Angular CLI.

Closes #20281
This commit is contained in:
Alan Agius 2021-03-18 14:32:46 +01:00
parent 8862a9f07c
commit 2616ef0d3f
4 changed files with 42 additions and 32 deletions

View File

@ -16,7 +16,10 @@ export interface ApplicationPresetOptions {
translation?: unknown; translation?: unknown;
}; };
angularLinker?: boolean; angularLinker?: {
shouldLink: boolean;
jitMode: boolean;
};
forceES5?: boolean; forceES5?: boolean;
forceAsyncTransformation?: boolean; forceAsyncTransformation?: boolean;
@ -124,13 +127,14 @@ export default function (api: unknown, options: ApplicationPresetOptions) {
const plugins = []; const plugins = [];
let needRuntimeTransform = false; let needRuntimeTransform = false;
if (options.angularLinker) { if (options.angularLinker?.shouldLink) {
// Babel currently is synchronous so import cannot be used // Babel currently is synchronous so import cannot be used
const { const {
createEs2015LinkerPlugin, createEs2015LinkerPlugin,
} = require('@angular/compiler-cli/linker/babel'); } = require('@angular/compiler-cli/linker/babel') as typeof import('@angular/compiler-cli/linker/babel');
plugins.push(createEs2015LinkerPlugin({ plugins.push(createEs2015LinkerPlugin({
linkerJitMode: options.angularLinker.jitMode,
logger: createNgtscLogger(options.diagnosticReporter), logger: createNgtscLogger(options.diagnosticReporter),
fileSystem: { fileSystem: {
resolve: path.resolve, resolve: path.resolve,
@ -138,7 +142,9 @@ export default function (api: unknown, options: ApplicationPresetOptions) {
dirname: path.dirname, dirname: path.dirname,
relative: path.relative, relative: path.relative,
readFile: fs.readFileSync, readFile: fs.readFileSync,
}, // Node.JS types don't overlap the Compiler types.
// tslint:disable-next-line: no-any
} as any,
})); }));
} }

View File

@ -10,11 +10,9 @@ import { custom } from 'babel-loader';
import { ScriptTarget } from 'typescript'; import { ScriptTarget } from 'typescript';
import { ApplicationPresetOptions } from './presets/application'; import { ApplicationPresetOptions } from './presets/application';
interface AngularCustomOptions { interface AngularCustomOptions extends Pick<ApplicationPresetOptions, 'angularLinker' | 'i18n'> {
forceAsyncTransformation: boolean; forceAsyncTransformation: boolean;
forceES5: boolean; forceES5: boolean;
shouldLink: boolean;
i18n: ApplicationPresetOptions['i18n'];
} }
function requiresLinking( function requiresLinking(
@ -41,20 +39,25 @@ export default custom<AngularCustomOptions>(() => {
}); });
return { return {
async customOptions({ i18n, scriptTarget, ...rawOptions }, { source }) { async customOptions({ i18n, scriptTarget, aot, ...rawOptions }, { source }) {
// Must process file if plugins are added // Must process file if plugins are added
let shouldProcess = Array.isArray(rawOptions.plugins) && rawOptions.plugins.length > 0; let shouldProcess = Array.isArray(rawOptions.plugins) && rawOptions.plugins.length > 0;
const customOptions: AngularCustomOptions = { const customOptions: AngularCustomOptions = {
forceAsyncTransformation: false, forceAsyncTransformation: false,
forceES5: false, forceES5: false,
shouldLink: false, angularLinker: undefined,
i18n: undefined, i18n: undefined,
}; };
// Analyze file for linking // Analyze file for linking
customOptions.shouldLink = await requiresLinking(this.resourcePath, source); if (await requiresLinking(this.resourcePath, source)) {
shouldProcess ||= customOptions.shouldLink; customOptions.angularLinker = {
shouldLink: true,
jitMode: aot !== true,
};
shouldProcess = true;
}
// Analyze for ES target processing // Analyze for ES target processing
const esTarget = scriptTarget as ScriptTarget | undefined; const esTarget = scriptTarget as ScriptTarget | undefined;
@ -109,10 +112,7 @@ export default custom<AngularCustomOptions>(() => {
[ [
require('./presets/application').default, require('./presets/application').default,
{ {
angularLinker: customOptions.shouldLink, ...customOptions,
forceES5: customOptions.forceES5,
forceAsyncTransformation: customOptions.forceAsyncTransformation,
i18n: customOptions.i18n,
diagnosticReporter: (type, message) => { diagnosticReporter: (type, message) => {
switch (type) { switch (type) {
case 'error': case 'error':

View File

@ -493,6 +493,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
options: { options: {
cacheDirectory: findCachePath('babel-webpack'), cacheDirectory: findCachePath('babel-webpack'),
scriptTarget: wco.scriptTarget, scriptTarget: wco.scriptTarget,
aot: buildOptions.aot,
}, },
}, },
...buildOptimizerUseRule, ...buildOptimizerUseRule,

View File

@ -1,8 +1,7 @@
import { writeFile } from '../../../utils/fs';
import { getActivePackageManager } from '../../../utils/packages';
import { ng, silentYarn } from '../../../utils/process';
import { updateJsonFile } from '../../../utils/project';
import { getGlobalVariable } from '../../../utils/env'; import { getGlobalVariable } from '../../../utils/env';
import { writeFile } from '../../../utils/fs';
import { ng } from '../../../utils/process';
import { updateJsonFile } from '../../../utils/project';
export default async function () { export default async function () {
if ((getGlobalVariable('argv')['ve'])) { if ((getGlobalVariable('argv')['ve'])) {
@ -13,8 +12,7 @@ export default async function () {
await ng('generate', 'library', 'my-lib'); await ng('generate', 'library', 'my-lib');
// Enable partial compilation mode (linker) for the library // Enable partial compilation mode (linker) for the library
// Enable ivy for production as well (current schematic disables ivy in production) await updateJsonFile('projects/my-lib/tsconfig.lib.json', config => {
await updateJsonFile('projects/my-lib/tsconfig.lib.prod.json', config => {
const { angularCompilerOptions = {} } = config; const { angularCompilerOptions = {} } = config;
angularCompilerOptions.enableIvy = true; angularCompilerOptions.enableIvy = true;
angularCompilerOptions.compilationMode = 'partial'; angularCompilerOptions.compilationMode = 'partial';
@ -51,7 +49,7 @@ export default async function () {
template: '<lib-my-lib></lib-my-lib>' template: '<lib-my-lib></lib-my-lib>'
}) })
export class AppComponent { export class AppComponent {
title = 'app'; title = 'test-project';
constructor(myLibService: MyLibService) { constructor(myLibService: MyLibService) {
console.log(myLibService); console.log(myLibService);
@ -85,18 +83,23 @@ export default async function () {
}); });
`); `);
await runLibraryTests(); // Build library in partial mode (development)
await runLibraryTests(true); await ng('build', 'my-lib', '--configuration=development');
// AOT linking
await runTests();
// JIT linking
await updateJsonFile('angular.json', config => {
const build = config.projects['test-project'].architect.build;
build.options.aot = false;
build.configurations.production.buildOptimizer = false;
});
await runTests();
} }
async function runLibraryTests(prodMode = false): Promise<void> { async function runTests(): Promise<void> {
const args = ['build', 'my-lib'];
if (!prodMode) {
args.push('--configuration=development');
}
await ng(...args);
// Check that the tests succeeds both with named project, unnamed (should test app), and prod. // Check that the tests succeeds both with named project, unnamed (should test app), and prod.
await ng('e2e'); await ng('e2e');
await ng('e2e', 'test-project', '--devServerTarget=test-project:serve:production'); await ng('e2e', 'test-project', '--devServerTarget=test-project:serve:production');