@angular/build
Version:
Official build system for Angular
152 lines (151 loc) • 6.66 kB
JavaScript
;
/**
* @license
* Copyright Google LLC 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.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = inlineFile;
exports.inlineCode = inlineCode;
const remapping_1 = __importDefault(require("@ampproject/remapping"));
const core_1 = require("@babel/core");
const node_assert_1 = __importDefault(require("node:assert"));
const node_worker_threads_1 = require("node:worker_threads");
const error_1 = require("../../utils/error");
const load_esm_1 = require("../../utils/load-esm");
// Extract the application files and common options used for inline requests from the Worker context
// TODO: Evaluate overall performance difference of passing translations here as well
const { files, missingTranslation, shouldOptimize } = (node_worker_threads_1.workerData || {});
/**
* Inlines the provided locale and translation into a JavaScript file that contains `$localize` usage.
* This function is the main entry for the Worker's action that is called by the worker pool.
*
* @param request An InlineRequest object representing the options for inlining
* @returns An object containing the inlined file and optional map content.
*/
async function inlineFile(request) {
const data = files.get(request.filename);
(0, node_assert_1.default)(data !== undefined, `Invalid inline request for file '${request.filename}'.`);
const code = await data.text();
const map = await files.get(request.filename + '.map')?.text();
const result = await transformWithBabel(code, map && JSON.parse(map), request);
return {
file: request.filename,
code: result.code,
map: result.map,
messages: result.diagnostics.messages,
};
}
/**
* Inlines the provided locale and translation into JavaScript code that contains `$localize` usage.
* This function is a secondary entry primarily for use with component HMR update modules.
*
* @param request An InlineRequest object representing the options for inlining
* @returns An object containing the inlined code.
*/
async function inlineCode(request) {
const result = await transformWithBabel(request.code, undefined, request);
return {
output: result.code,
messages: result.diagnostics.messages,
};
}
/**
* Cached instance of the `@angular/localize/tools` module.
* This is used to remove the need to repeatedly import the module per file translation.
*/
let localizeToolsModule;
/**
* Attempts to load the `@angular/localize/tools` module containing the functionality to
* perform the file translations.
* This module must be dynamically loaded as it is an ESM module and this file is CommonJS.
*/
async function loadLocalizeTools() {
// Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround.
// Once TypeScript provides support for keeping the dynamic import this workaround can be
// changed to a direct dynamic import.
localizeToolsModule ??= await (0, load_esm_1.loadEsmModule)('@angular/localize/tools');
return localizeToolsModule;
}
/**
* Creates the needed Babel plugins to inline a given locale and translation for a JavaScript file.
* @param locale A string containing the locale specifier to use.
* @param translation A object record containing locale specific messages to use.
* @returns An array of Babel plugins.
*/
async function createI18nPlugins(locale, translation) {
const { Diagnostics, makeEs2015TranslatePlugin } = await loadLocalizeTools();
const plugins = [];
const diagnostics = new Diagnostics();
plugins.push(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
makeEs2015TranslatePlugin(diagnostics, (translation || {}), {
missingTranslation: translation === undefined ? 'ignore' : missingTranslation,
}));
// Create a plugin to replace the locale specifier constant inject by the build system with the actual specifier
plugins.push({
visitor: {
StringLiteral(path) {
if (path.node.value === '___NG_LOCALE_INSERT___') {
path.replaceWith(core_1.types.stringLiteral(locale));
}
},
},
});
return { diagnostics, plugins };
}
/**
* Transforms a JavaScript file using Babel to inline the request locale and translation.
* @param code A string containing the JavaScript code to transform.
* @param map A sourcemap object for the provided JavaScript code.
* @param options The inline request options to use.
* @returns An object containing the code, map, and diagnostics from the transformation.
*/
async function transformWithBabel(code, map, options) {
let ast;
try {
ast = (0, core_1.parseSync)(code, {
babelrc: false,
configFile: false,
sourceType: 'unambiguous',
filename: options.filename,
});
}
catch (error) {
(0, error_1.assertIsError)(error);
// Make the error more readable.
// Same errors will contain the full content of the file as the error message
// Which makes it hard to find the actual error message.
const index = error.message.indexOf(')\n');
const msg = index !== -1 ? error.message.slice(0, index + 1) : error.message;
throw new Error(`${msg}\nAn error occurred inlining file "${options.filename}"`);
}
if (!ast) {
throw new Error(`Unknown error occurred inlining file "${options.filename}"`);
}
const { diagnostics, plugins } = await createI18nPlugins(options.locale, options.translation);
const transformResult = await (0, core_1.transformFromAstAsync)(ast, code, {
filename: options.filename,
// false is a valid value but not included in the type definition
inputSourceMap: false,
sourceMaps: !!map,
compact: shouldOptimize,
configFile: false,
babelrc: false,
browserslistConfigFile: false,
plugins,
});
if (!transformResult || !transformResult.code) {
throw new Error(`Unknown error occurred processing bundle for "${options.filename}".`);
}
let outputMap;
if (map && transformResult.map) {
outputMap = (0, remapping_1.default)([transformResult.map, map], () => null);
}
return { code: transformResult.code, map: outputMap && JSON.stringify(outputMap), diagnostics };
}