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;
};
angularLinker?: boolean;
angularLinker?: {
shouldLink: boolean;
jitMode: boolean;
};
forceES5?: boolean;
forceAsyncTransformation?: boolean;
@ -124,13 +127,14 @@ export default function (api: unknown, options: ApplicationPresetOptions) {
const plugins = [];
let needRuntimeTransform = false;
if (options.angularLinker) {
if (options.angularLinker?.shouldLink) {
// Babel currently is synchronous so import cannot be used
const {
createEs2015LinkerPlugin,
} = require('@angular/compiler-cli/linker/babel');
} = require('@angular/compiler-cli/linker/babel') as typeof import('@angular/compiler-cli/linker/babel');
plugins.push(createEs2015LinkerPlugin({
linkerJitMode: options.angularLinker.jitMode,
logger: createNgtscLogger(options.diagnosticReporter),
fileSystem: {
resolve: path.resolve,
@ -138,7 +142,9 @@ export default function (api: unknown, options: ApplicationPresetOptions) {
dirname: path.dirname,
relative: path.relative,
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 { ApplicationPresetOptions } from './presets/application';
interface AngularCustomOptions {
interface AngularCustomOptions extends Pick<ApplicationPresetOptions, 'angularLinker' | 'i18n'> {
forceAsyncTransformation: boolean;
forceES5: boolean;
shouldLink: boolean;
i18n: ApplicationPresetOptions['i18n'];
}
function requiresLinking(
@ -41,20 +39,25 @@ export default custom<AngularCustomOptions>(() => {
});
return {
async customOptions({ i18n, scriptTarget, ...rawOptions }, { source }) {
async customOptions({ i18n, scriptTarget, aot, ...rawOptions }, { source }) {
// Must process file if plugins are added
let shouldProcess = Array.isArray(rawOptions.plugins) && rawOptions.plugins.length > 0;
const customOptions: AngularCustomOptions = {
forceAsyncTransformation: false,
forceES5: false,
shouldLink: false,
angularLinker: undefined,
i18n: undefined,
};
// Analyze file for linking
customOptions.shouldLink = await requiresLinking(this.resourcePath, source);
shouldProcess ||= customOptions.shouldLink;
if (await requiresLinking(this.resourcePath, source)) {
customOptions.angularLinker = {
shouldLink: true,
jitMode: aot !== true,
};
shouldProcess = true;
}
// Analyze for ES target processing
const esTarget = scriptTarget as ScriptTarget | undefined;
@ -109,10 +112,7 @@ export default custom<AngularCustomOptions>(() => {
[
require('./presets/application').default,
{
angularLinker: customOptions.shouldLink,
forceES5: customOptions.forceES5,
forceAsyncTransformation: customOptions.forceAsyncTransformation,
i18n: customOptions.i18n,
...customOptions,
diagnosticReporter: (type, message) => {
switch (type) {
case 'error':

View File

@ -493,6 +493,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
options: {
cacheDirectory: findCachePath('babel-webpack'),
scriptTarget: wco.scriptTarget,
aot: buildOptions.aot,
},
},
...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 { writeFile } from '../../../utils/fs';
import { ng } from '../../../utils/process';
import { updateJsonFile } from '../../../utils/project';
export default async function () {
if ((getGlobalVariable('argv')['ve'])) {
@ -13,8 +12,7 @@ export default async function () {
await ng('generate', 'library', 'my-lib');
// 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.prod.json', config => {
await updateJsonFile('projects/my-lib/tsconfig.lib.json', config => {
const { angularCompilerOptions = {} } = config;
angularCompilerOptions.enableIvy = true;
angularCompilerOptions.compilationMode = 'partial';
@ -51,7 +49,7 @@ export default async function () {
template: '<lib-my-lib></lib-my-lib>'
})
export class AppComponent {
title = 'app';
title = 'test-project';
constructor(myLibService: MyLibService) {
console.log(myLibService);
@ -85,18 +83,23 @@ export default async function () {
});
`);
await runLibraryTests();
await runLibraryTests(true);
// Build library in partial mode (development)
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> {
const args = ['build', 'my-lib'];
if (!prodMode) {
args.push('--configuration=development');
}
await ng(...args);
async function runTests(): Promise<void> {
// Check that the tests succeeds both with named project, unnamed (should test app), and prod.
await ng('e2e');
await ng('e2e', 'test-project', '--devServerTarget=test-project:serve:production');