Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(@angular-devkit/build-angular): use custom babel loader for i18n dev-server support #20172

Merged
merged 1 commit into from Mar 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 40 additions & 23 deletions packages/angular_devkit/build_angular/src/babel/webpack-loader.ts
Expand Up @@ -7,11 +7,13 @@
*/
import { custom } from 'babel-loader';
import { ScriptTarget } from 'typescript';
import { ApplicationPresetOptions } from './presets/application';

interface AngularCustomOptions {
forceAsyncTransformation: boolean;
forceES5: boolean;
shouldLink: boolean;
i18n: ApplicationPresetOptions['i18n'];
}

/**
Expand Down Expand Up @@ -65,56 +67,70 @@ export default custom<AngularCustomOptions>(() => {
});

return {
async customOptions({ scriptTarget, ...loaderOptions }, { source }) {
async customOptions({ i18n, scriptTarget, ...rawOptions }, { source }) {
// Must process file if plugins are added
let shouldProcess = Array.isArray(loaderOptions.plugins) && loaderOptions.plugins.length > 0;
let shouldProcess = Array.isArray(rawOptions.plugins) && rawOptions.plugins.length > 0;

const customOptions: AngularCustomOptions = {
forceAsyncTransformation: false,
forceES5: false,
shouldLink: false,
i18n: undefined,
};

// Analyze file for linking
let shouldLink = false;
const { hasLinkerSupport, requiresLinking } = await checkLinking(this.resourcePath, source);
if (requiresLinking && !hasLinkerSupport) {
// Cannot link if there is no linker support
this.emitError(
'File requires the Angular linker. "@angular/compiler-cli" version 11.1.0 or greater is needed.',
);
} else {
shouldLink = requiresLinking;
customOptions.shouldLink = requiresLinking;
}
shouldProcess ||= shouldLink;
shouldProcess ||= customOptions.shouldLink;

// Analyze for ES target processing
let forceES5 = false;
let forceAsyncTransformation = false;
const esTarget = scriptTarget as ScriptTarget;
if (esTarget < ScriptTarget.ES2015) {
// TypeScript files will have already been downlevelled
forceES5 = !/\.tsx?$/.test(this.resourcePath);
} else if (esTarget >= ScriptTarget.ES2017) {
forceAsyncTransformation = source.includes('async');
const esTarget = scriptTarget as ScriptTarget | undefined;
if (esTarget !== undefined) {
if (esTarget < ScriptTarget.ES2015) {
// TypeScript files will have already been downlevelled
customOptions.forceES5 = !/\.tsx?$/.test(this.resourcePath);
} else if (esTarget >= ScriptTarget.ES2017) {
customOptions.forceAsyncTransformation = source.includes('async');
}
shouldProcess ||= customOptions.forceAsyncTransformation || customOptions.forceES5;
}

// Analyze for i18n inlining
if (
i18n &&
!/[\\\/]@angular[\\\/](?:compiler|localize)/.test(this.resourcePath) &&
source.includes('$localize')
) {
customOptions.i18n = i18n as ApplicationPresetOptions['i18n'];
shouldProcess = true;
}
shouldProcess ||= forceAsyncTransformation || forceES5;

// Add provided loader options to default base options
const options: Record<string, unknown> = {
const loaderOptions: Record<string, unknown> = {
...baseOptions,
...loaderOptions,
...rawOptions,
cacheIdentifier: JSON.stringify({
buildAngular: require('../../package.json').version,
forceAsyncTransformation,
forceES5,
shouldLink,
customOptions,
baseOptions,
loaderOptions,
rawOptions,
}),
};

// Skip babel processing if no actions are needed
if (!shouldProcess) {
// Force the current file to be ignored
options.ignore = [() => true];
loaderOptions.ignore = [() => true];
}

return { custom: { forceAsyncTransformation, forceES5, shouldLink }, loader: options };
return { custom: customOptions, loader: loaderOptions };
},
config(configuration, { customOptions }) {
return {
Expand All @@ -127,6 +143,7 @@ export default custom<AngularCustomOptions>(() => {
angularLinker: customOptions.shouldLink,
forceES5: customOptions.forceES5,
forceAsyncTransformation: customOptions.forceAsyncTransformation,
i18n: customOptions.i18n,
diagnosticReporter: (type, message) => {
switch (type) {
case 'error':
Expand All @@ -139,7 +156,7 @@ export default custom<AngularCustomOptions>(() => {
break;
}
},
} as import('./presets/application').ApplicationPresetOptions,
} as ApplicationPresetOptions,
],
],
};
Expand Down
52 changes: 10 additions & 42 deletions packages/angular_devkit/build_angular/src/dev-server/index.ts
Expand Up @@ -354,7 +354,6 @@ async function setupLocalize(
webpackConfig: webpack.Configuration,
) {
const localeDescription = i18n.locales[locale];
const i18nDiagnostics: { type: string, message: string }[] = [];

// Modify main entrypoint to include locale data
if (
Expand All @@ -378,37 +377,25 @@ async function setupLocalize(
translation = {};
}

const i18nLoaderOptions = {
locale,
missingTranslationBehavior,
translation: i18n.shouldInline ? translation : undefined,
};

const i18nRule: webpack.RuleSetRule = {
test: /\.(?:m?js|ts)$/,
test: /\.(?:[cm]?js|ts)$/,
enforce: 'post',
use: [
{
loader: require.resolve('babel-loader'),
loader: require.resolve('../babel/webpack-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
cacheCompression: false,
cacheDirectory: findCachePath('babel-loader'),
cacheDirectory: findCachePath('babel-dev-server-i18n'),
cacheIdentifier: JSON.stringify({
buildAngular: require('../../package.json').version,
locale,
translationIntegrity: localeDescription?.files.map((file) => file.integrity),
}),
sourceType: 'unambiguous',
presets: [
[
require.resolve('../babel/presets/application'),
{
i18n: {
locale,
translation: i18n.shouldInline ? translation : undefined,
missingTranslationBehavior,
},
diagnosticReporter: (type, message) => i18nDiagnostics.push({ type, message }),
} as import('../babel/presets/application').ApplicationPresetOptions,
],
],
i18n: i18nLoaderOptions,
},
},
],
Expand All @@ -423,25 +410,6 @@ async function setupLocalize(
}

rules.push(i18nRule);

// Add a plugin to inject the i18n diagnostics
// tslint:disable-next-line: no-non-null-assertion
webpackConfig.plugins!.push({
apply: (compiler: webpack.Compiler) => {
compiler.hooks.thisCompilation.tap('build-angular', compilation => {
compilation.hooks.finishModules.tap('build-angular', () => {
for (const diagnostic of i18nDiagnostics) {
if (diagnostic.type === 'error') {
addError(compilation, diagnostic.message);
} else {
addWarning(compilation, diagnostic.message);
}
}
i18nDiagnostics.length = 0;
});
});
},
});
}

export default createBuilder<DevServerBuilderOptions, DevServerBuilderOutput>(serveWebpackBrowser);