1 | "use strict";
|
---|
2 | /**
|
---|
3 | * @license
|
---|
4 | * Copyright Google LLC All Rights Reserved.
|
---|
5 | *
|
---|
6 | * Use of this source code is governed by an MIT-style license that can be
|
---|
7 | * found in the LICENSE file at https://angular.io/license
|
---|
8 | */
|
---|
9 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
---|
10 | if (k2 === undefined) k2 = k;
|
---|
11 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
---|
12 | }) : (function(o, m, k, k2) {
|
---|
13 | if (k2 === undefined) k2 = k;
|
---|
14 | o[k2] = m[k];
|
---|
15 | }));
|
---|
16 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
---|
17 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
---|
18 | }) : function(o, v) {
|
---|
19 | o["default"] = v;
|
---|
20 | });
|
---|
21 | var __importStar = (this && this.__importStar) || function (mod) {
|
---|
22 | if (mod && mod.__esModule) return mod;
|
---|
23 | var result = {};
|
---|
24 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
---|
25 | __setModuleDefault(result, mod);
|
---|
26 | return result;
|
---|
27 | };
|
---|
28 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
---|
29 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
---|
30 | };
|
---|
31 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
32 | exports.inlineLocales = exports.createI18nPlugins = exports.process = void 0;
|
---|
33 | const remapping_1 = __importDefault(require("@ampproject/remapping"));
|
---|
34 | const core_1 = require("@babel/core");
|
---|
35 | const template_1 = __importDefault(require("@babel/template"));
|
---|
36 | const cacache = __importStar(require("cacache"));
|
---|
37 | const crypto_1 = require("crypto");
|
---|
38 | const fs = __importStar(require("fs"));
|
---|
39 | const path = __importStar(require("path"));
|
---|
40 | const terser_1 = require("terser");
|
---|
41 | const worker_threads_1 = require("worker_threads");
|
---|
42 | const environment_options_1 = require("./environment-options");
|
---|
43 | // Lazy loaded webpack-sources object
|
---|
44 | // Webpack is only imported if needed during the processing
|
---|
45 | let webpackSources;
|
---|
46 | // If code size is larger than 500KB, consider lower fidelity but faster sourcemap merge
|
---|
47 | const FAST_SOURCEMAP_THRESHOLD = 500 * 1024;
|
---|
48 | const { cachePath, i18n } = (worker_threads_1.workerData || {});
|
---|
49 | async function cachePut(content, key, integrity) {
|
---|
50 | if (cachePath && key) {
|
---|
51 | await cacache.put(cachePath, key, content, {
|
---|
52 | metadata: { integrity },
|
---|
53 | });
|
---|
54 | }
|
---|
55 | }
|
---|
56 | async function process(options) {
|
---|
57 | var _a;
|
---|
58 | if (!options.cacheKeys) {
|
---|
59 | options.cacheKeys = [];
|
---|
60 | }
|
---|
61 | const result = { name: options.name };
|
---|
62 | if (options.integrityAlgorithm) {
|
---|
63 | // Store unmodified code integrity value -- used for SRI value replacement
|
---|
64 | result.integrity = generateIntegrityValue(options.integrityAlgorithm, options.code);
|
---|
65 | }
|
---|
66 | // Runtime chunk requires specialized handling
|
---|
67 | if (options.runtime) {
|
---|
68 | return { ...result, ...(await processRuntime(options)) };
|
---|
69 | }
|
---|
70 | const basePath = path.dirname(options.filename);
|
---|
71 | const filename = path.basename(options.filename);
|
---|
72 | const downlevelFilename = filename.replace(/\-(es20\d{2}|esnext)/, '-es5');
|
---|
73 | const downlevel = !options.optimizeOnly;
|
---|
74 | const sourceCode = options.code;
|
---|
75 | if (downlevel) {
|
---|
76 | const { supportedBrowsers: targets = [] } = options;
|
---|
77 | // todo: revisit this in version 10, when we update our defaults browserslist
|
---|
78 | // Without this workaround bundles will not be downlevelled because Babel doesn't know handle to 'op_mini all'
|
---|
79 | // See: https://github.com/babel/babel/issues/11155
|
---|
80 | if (Array.isArray(targets) && targets.includes('op_mini all')) {
|
---|
81 | targets.push('ie_mob 11');
|
---|
82 | }
|
---|
83 | else if ('op_mini' in targets) {
|
---|
84 | targets['ie_mob'] = '11';
|
---|
85 | }
|
---|
86 | // Downlevel the bundle
|
---|
87 | const transformResult = await core_1.transformAsync(sourceCode, {
|
---|
88 | filename,
|
---|
89 | // using false ensures that babel will NOT search and process sourcemap comments (large memory usage)
|
---|
90 | // The types do not include the false option even though it is valid
|
---|
91 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
92 | inputSourceMap: false,
|
---|
93 | babelrc: false,
|
---|
94 | configFile: false,
|
---|
95 | presets: [
|
---|
96 | [
|
---|
97 | require.resolve('@babel/preset-env'),
|
---|
98 | {
|
---|
99 | // browserslist-compatible query or object of minimum environment versions to support
|
---|
100 | targets,
|
---|
101 | // modules aren't needed since the bundles use webpack's custom module loading
|
---|
102 | modules: false,
|
---|
103 | // 'transform-typeof-symbol' generates slower code
|
---|
104 | exclude: ['transform-typeof-symbol'],
|
---|
105 | },
|
---|
106 | ],
|
---|
107 | ],
|
---|
108 | plugins: [
|
---|
109 | createIifeWrapperPlugin(),
|
---|
110 | ...(options.replacements ? [createReplacePlugin(options.replacements)] : []),
|
---|
111 | ],
|
---|
112 | minified: environment_options_1.allowMinify && !!options.optimize,
|
---|
113 | compact: !environment_options_1.shouldBeautify && !!options.optimize,
|
---|
114 | sourceMaps: !!options.map,
|
---|
115 | });
|
---|
116 | if (!transformResult || !transformResult.code) {
|
---|
117 | throw new Error(`Unknown error occurred processing bundle for "${options.filename}".`);
|
---|
118 | }
|
---|
119 | result.downlevel = await processBundle({
|
---|
120 | ...options,
|
---|
121 | code: transformResult.code,
|
---|
122 | downlevelMap: (_a = transformResult.map) !== null && _a !== void 0 ? _a : undefined,
|
---|
123 | filename: path.join(basePath, downlevelFilename),
|
---|
124 | isOriginal: false,
|
---|
125 | });
|
---|
126 | }
|
---|
127 | if (!result.original && !options.ignoreOriginal) {
|
---|
128 | result.original = await processBundle({
|
---|
129 | ...options,
|
---|
130 | isOriginal: true,
|
---|
131 | });
|
---|
132 | }
|
---|
133 | return result;
|
---|
134 | }
|
---|
135 | exports.process = process;
|
---|
136 | async function processBundle(options) {
|
---|
137 | const { optimize, isOriginal, code, map, downlevelMap, filename: filepath, hiddenSourceMaps, cacheKeys = [], integrityAlgorithm, } = options;
|
---|
138 | const filename = path.basename(filepath);
|
---|
139 | let resultCode = code;
|
---|
140 | let optimizeResult;
|
---|
141 | if (optimize) {
|
---|
142 | optimizeResult = await terserMangle(code, {
|
---|
143 | filename,
|
---|
144 | sourcemap: !!map,
|
---|
145 | compress: !isOriginal,
|
---|
146 | ecma: isOriginal ? 2015 : 5,
|
---|
147 | });
|
---|
148 | resultCode = optimizeResult.code;
|
---|
149 | }
|
---|
150 | let mapContent;
|
---|
151 | if (map) {
|
---|
152 | if (!hiddenSourceMaps) {
|
---|
153 | resultCode += `\n//# sourceMappingURL=${filename}.map`;
|
---|
154 | }
|
---|
155 | const partialSourcemaps = [];
|
---|
156 | if (optimizeResult && optimizeResult.map) {
|
---|
157 | partialSourcemaps.push(optimizeResult.map);
|
---|
158 | }
|
---|
159 | if (downlevelMap) {
|
---|
160 | partialSourcemaps.push(downlevelMap);
|
---|
161 | }
|
---|
162 | if (partialSourcemaps.length > 0) {
|
---|
163 | partialSourcemaps.push(map);
|
---|
164 | const fullSourcemap = remapping_1.default(partialSourcemaps, () => null);
|
---|
165 | mapContent = JSON.stringify(fullSourcemap);
|
---|
166 | }
|
---|
167 | else {
|
---|
168 | mapContent = map;
|
---|
169 | }
|
---|
170 | await cachePut(mapContent, cacheKeys[isOriginal ? 1 /* OriginalMap */ : 3 /* DownlevelMap */]);
|
---|
171 | fs.writeFileSync(filepath + '.map', mapContent);
|
---|
172 | }
|
---|
173 | const fileResult = createFileEntry(filepath, resultCode, mapContent, integrityAlgorithm);
|
---|
174 | await cachePut(resultCode, cacheKeys[isOriginal ? 0 /* OriginalCode */ : 2 /* DownlevelCode */], fileResult.integrity);
|
---|
175 | fs.writeFileSync(filepath, resultCode);
|
---|
176 | return fileResult;
|
---|
177 | }
|
---|
178 | async function terserMangle(code, options = {}) {
|
---|
179 | // Note: Investigate converting the AST instead of re-parsing
|
---|
180 | // estree -> terser is already supported; need babel -> estree/terser
|
---|
181 | // Mangle downlevel code
|
---|
182 | const minifyOutput = await terser_1.minify(options.filename ? { [options.filename]: code } : code, {
|
---|
183 | compress: environment_options_1.allowMinify && !!options.compress,
|
---|
184 | ecma: options.ecma || 5,
|
---|
185 | mangle: environment_options_1.allowMangle,
|
---|
186 | safari10: true,
|
---|
187 | format: {
|
---|
188 | ascii_only: true,
|
---|
189 | webkit: true,
|
---|
190 | beautify: environment_options_1.shouldBeautify,
|
---|
191 | wrap_func_args: false,
|
---|
192 | },
|
---|
193 | sourceMap: !!options.sourcemap &&
|
---|
194 | {
|
---|
195 | asObject: true,
|
---|
196 | // typings don't include asObject option
|
---|
197 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
198 | },
|
---|
199 | });
|
---|
200 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
---|
201 | return { code: minifyOutput.code, map: minifyOutput.map };
|
---|
202 | }
|
---|
203 | function createFileEntry(filename, code, map, integrityAlgorithm) {
|
---|
204 | return {
|
---|
205 | filename: filename,
|
---|
206 | size: Buffer.byteLength(code),
|
---|
207 | integrity: integrityAlgorithm && generateIntegrityValue(integrityAlgorithm, code),
|
---|
208 | map: !map
|
---|
209 | ? undefined
|
---|
210 | : {
|
---|
211 | filename: filename + '.map',
|
---|
212 | size: Buffer.byteLength(map),
|
---|
213 | },
|
---|
214 | };
|
---|
215 | }
|
---|
216 | function generateIntegrityValue(hashAlgorithm, code) {
|
---|
217 | return hashAlgorithm + '-' + crypto_1.createHash(hashAlgorithm).update(code).digest('base64');
|
---|
218 | }
|
---|
219 | // The webpack runtime chunk is already ES5.
|
---|
220 | // However, two variants are still needed due to lazy routing and SRI differences
|
---|
221 | // NOTE: This should eventually be a babel plugin
|
---|
222 | async function processRuntime(options) {
|
---|
223 | let originalCode = options.code;
|
---|
224 | let downlevelCode = options.code;
|
---|
225 | // Replace integrity hashes with updated values
|
---|
226 | if (options.integrityAlgorithm && options.runtimeData) {
|
---|
227 | for (const data of options.runtimeData) {
|
---|
228 | if (!data.integrity) {
|
---|
229 | continue;
|
---|
230 | }
|
---|
231 | if (data.original && data.original.integrity) {
|
---|
232 | originalCode = originalCode.replace(data.integrity, data.original.integrity);
|
---|
233 | }
|
---|
234 | if (data.downlevel && data.downlevel.integrity) {
|
---|
235 | downlevelCode = downlevelCode.replace(data.integrity, data.downlevel.integrity);
|
---|
236 | }
|
---|
237 | }
|
---|
238 | }
|
---|
239 | // Adjust lazy loaded scripts to point to the proper variant
|
---|
240 | // Extra spacing is intentional to align source line positions
|
---|
241 | downlevelCode = downlevelCode.replace(/"\-(es20\d{2}|esnext)\./, ' "-es5.');
|
---|
242 | return {
|
---|
243 | original: await processBundle({
|
---|
244 | ...options,
|
---|
245 | code: originalCode,
|
---|
246 | isOriginal: true,
|
---|
247 | }),
|
---|
248 | downlevel: await processBundle({
|
---|
249 | ...options,
|
---|
250 | code: downlevelCode,
|
---|
251 | filename: options.filename.replace(/\-(es20\d{2}|esnext)/, '-es5'),
|
---|
252 | isOriginal: false,
|
---|
253 | }),
|
---|
254 | };
|
---|
255 | }
|
---|
256 | function createReplacePlugin(replacements) {
|
---|
257 | return {
|
---|
258 | visitor: {
|
---|
259 | StringLiteral(path) {
|
---|
260 | for (const replacement of replacements) {
|
---|
261 | if (path.node.value === replacement[0]) {
|
---|
262 | path.node.value = replacement[1];
|
---|
263 | }
|
---|
264 | }
|
---|
265 | },
|
---|
266 | },
|
---|
267 | };
|
---|
268 | }
|
---|
269 | function createIifeWrapperPlugin() {
|
---|
270 | return {
|
---|
271 | visitor: {
|
---|
272 | Program: {
|
---|
273 | exit(path) {
|
---|
274 | // Save existing body and directives
|
---|
275 | const { body, directives } = path.node;
|
---|
276 | // Clear out body and directives for wrapper
|
---|
277 | path.node.body = [];
|
---|
278 | path.node.directives = [];
|
---|
279 | // Create the wrapper - "(function() { ... })();"
|
---|
280 | const wrapper = core_1.types.expressionStatement(core_1.types.callExpression(core_1.types.parenthesizedExpression(core_1.types.functionExpression(undefined, [], core_1.types.blockStatement(body, directives))), []));
|
---|
281 | // Insert the wrapper
|
---|
282 | path.pushContainer('body', wrapper);
|
---|
283 | },
|
---|
284 | },
|
---|
285 | },
|
---|
286 | };
|
---|
287 | }
|
---|
288 | const USE_LOCALIZE_PLUGINS = false;
|
---|
289 | async function createI18nPlugins(locale, translation, missingTranslation, shouldInline, localeDataContent) {
|
---|
290 | const plugins = [];
|
---|
291 | const localizeDiag = await Promise.resolve().then(() => __importStar(require('@angular/localize/src/tools/src/diagnostics')));
|
---|
292 | const diagnostics = new localizeDiag.Diagnostics();
|
---|
293 | if (shouldInline) {
|
---|
294 | const es2015 = await Promise.resolve().then(() => __importStar(require('@angular/localize/src/tools/src/translate/source_files/es2015_translate_plugin')));
|
---|
295 | plugins.push(
|
---|
296 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
297 | es2015.makeEs2015TranslatePlugin(diagnostics, (translation || {}), {
|
---|
298 | missingTranslation: translation === undefined ? 'ignore' : missingTranslation,
|
---|
299 | }));
|
---|
300 | const es5 = await Promise.resolve().then(() => __importStar(require('@angular/localize/src/tools/src/translate/source_files/es5_translate_plugin')));
|
---|
301 | plugins.push(
|
---|
302 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
303 | es5.makeEs5TranslatePlugin(diagnostics, (translation || {}), {
|
---|
304 | missingTranslation: translation === undefined ? 'ignore' : missingTranslation,
|
---|
305 | }));
|
---|
306 | }
|
---|
307 | const inlineLocale = await Promise.resolve().then(() => __importStar(require('@angular/localize/src/tools/src/translate/source_files/locale_plugin')));
|
---|
308 | plugins.push(inlineLocale.makeLocalePlugin(locale));
|
---|
309 | if (localeDataContent) {
|
---|
310 | plugins.push({
|
---|
311 | visitor: {
|
---|
312 | Program(path) {
|
---|
313 | path.unshiftContainer('body', template_1.default.ast(localeDataContent));
|
---|
314 | },
|
---|
315 | },
|
---|
316 | });
|
---|
317 | }
|
---|
318 | return { diagnostics, plugins };
|
---|
319 | }
|
---|
320 | exports.createI18nPlugins = createI18nPlugins;
|
---|
321 | const localizeName = '$localize';
|
---|
322 | async function inlineLocales(options) {
|
---|
323 | var _a;
|
---|
324 | if (!i18n || i18n.inlineLocales.size === 0) {
|
---|
325 | return { file: options.filename, diagnostics: [], count: 0 };
|
---|
326 | }
|
---|
327 | if (i18n.flatOutput && i18n.inlineLocales.size > 1) {
|
---|
328 | throw new Error('Flat output is only supported when inlining one locale.');
|
---|
329 | }
|
---|
330 | const hasLocalizeName = options.code.includes(localizeName);
|
---|
331 | if (!hasLocalizeName && !options.setLocale) {
|
---|
332 | return inlineCopyOnly(options);
|
---|
333 | }
|
---|
334 | let ast;
|
---|
335 | try {
|
---|
336 | ast = core_1.parseSync(options.code, {
|
---|
337 | babelrc: false,
|
---|
338 | configFile: false,
|
---|
339 | sourceType: 'script',
|
---|
340 | filename: options.filename,
|
---|
341 | });
|
---|
342 | }
|
---|
343 | catch (error) {
|
---|
344 | if (error.message) {
|
---|
345 | // Make the error more readable.
|
---|
346 | // Same errors will contain the full content of the file as the error message
|
---|
347 | // Which makes it hard to find the actual error message.
|
---|
348 | const index = error.message.indexOf(')\n');
|
---|
349 | const msg = index !== -1 ? error.message.substr(0, index + 1) : error.message;
|
---|
350 | throw new Error(`${msg}\nAn error occurred inlining file "${options.filename}"`);
|
---|
351 | }
|
---|
352 | }
|
---|
353 | if (!ast) {
|
---|
354 | throw new Error(`Unknown error occurred inlining file "${options.filename}"`);
|
---|
355 | }
|
---|
356 | if (!USE_LOCALIZE_PLUGINS) {
|
---|
357 | return inlineLocalesDirect(ast, options);
|
---|
358 | }
|
---|
359 | const diagnostics = [];
|
---|
360 | for (const locale of i18n.inlineLocales) {
|
---|
361 | const isSourceLocale = locale === i18n.sourceLocale;
|
---|
362 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
363 | const translations = isSourceLocale ? {} : i18n.locales[locale].translation || {};
|
---|
364 | let localeDataContent;
|
---|
365 | if (options.setLocale) {
|
---|
366 | // If locale data is provided, load it and prepend to file
|
---|
367 | const localeDataPath = (_a = i18n.locales[locale]) === null || _a === void 0 ? void 0 : _a.dataPath;
|
---|
368 | if (localeDataPath) {
|
---|
369 | localeDataContent = await loadLocaleData(localeDataPath, true, options.es5);
|
---|
370 | }
|
---|
371 | }
|
---|
372 | const { diagnostics: localeDiagnostics, plugins } = await createI18nPlugins(locale, translations, isSourceLocale ? 'ignore' : options.missingTranslation || 'warning', true, localeDataContent);
|
---|
373 | const transformResult = await core_1.transformFromAstSync(ast, options.code, {
|
---|
374 | filename: options.filename,
|
---|
375 | // using false ensures that babel will NOT search and process sourcemap comments (large memory usage)
|
---|
376 | // The types do not include the false option even though it is valid
|
---|
377 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
378 | inputSourceMap: false,
|
---|
379 | babelrc: false,
|
---|
380 | configFile: false,
|
---|
381 | plugins,
|
---|
382 | compact: !environment_options_1.shouldBeautify,
|
---|
383 | sourceMaps: !!options.map,
|
---|
384 | });
|
---|
385 | diagnostics.push(...localeDiagnostics.messages);
|
---|
386 | if (!transformResult || !transformResult.code) {
|
---|
387 | throw new Error(`Unknown error occurred processing bundle for "${options.filename}".`);
|
---|
388 | }
|
---|
389 | const outputPath = path.join(options.outputPath, i18n.flatOutput ? '' : locale, options.filename);
|
---|
390 | fs.writeFileSync(outputPath, transformResult.code);
|
---|
391 | if (options.map && transformResult.map) {
|
---|
392 | const outputMap = remapping_1.default([transformResult.map, options.map], () => null);
|
---|
393 | fs.writeFileSync(outputPath + '.map', JSON.stringify(outputMap));
|
---|
394 | }
|
---|
395 | }
|
---|
396 | return { file: options.filename, diagnostics };
|
---|
397 | }
|
---|
398 | exports.inlineLocales = inlineLocales;
|
---|
399 | async function inlineLocalesDirect(ast, options) {
|
---|
400 | if (!i18n || i18n.inlineLocales.size === 0) {
|
---|
401 | return { file: options.filename, diagnostics: [], count: 0 };
|
---|
402 | }
|
---|
403 | const { default: generate } = await Promise.resolve().then(() => __importStar(require('@babel/generator')));
|
---|
404 | const utils = await Promise.resolve().then(() => __importStar(require('@angular/localize/src/tools/src/source_file_utils')));
|
---|
405 | const localizeDiag = await Promise.resolve().then(() => __importStar(require('@angular/localize/src/tools/src/diagnostics')));
|
---|
406 | const diagnostics = new localizeDiag.Diagnostics();
|
---|
407 | const positions = findLocalizePositions(ast, options, utils);
|
---|
408 | if (positions.length === 0 && !options.setLocale) {
|
---|
409 | return inlineCopyOnly(options);
|
---|
410 | }
|
---|
411 | const inputMap = !!options.map && JSON.parse(options.map);
|
---|
412 | // Cleanup source root otherwise it will be added to each source entry
|
---|
413 | const mapSourceRoot = inputMap && inputMap.sourceRoot;
|
---|
414 | if (inputMap) {
|
---|
415 | delete inputMap.sourceRoot;
|
---|
416 | }
|
---|
417 | // Load Webpack only when needed
|
---|
418 | if (webpackSources === undefined) {
|
---|
419 | webpackSources = (await Promise.resolve().then(() => __importStar(require('webpack')))).sources;
|
---|
420 | }
|
---|
421 | const { ConcatSource, OriginalSource, ReplaceSource, SourceMapSource } = webpackSources;
|
---|
422 | for (const locale of i18n.inlineLocales) {
|
---|
423 | const content = new ReplaceSource(inputMap
|
---|
424 | ? new SourceMapSource(options.code, options.filename, inputMap)
|
---|
425 | : new OriginalSource(options.code, options.filename));
|
---|
426 | const isSourceLocale = locale === i18n.sourceLocale;
|
---|
427 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
428 | const translations = isSourceLocale ? {} : i18n.locales[locale].translation || {};
|
---|
429 | for (const position of positions) {
|
---|
430 | const translated = utils.translate(diagnostics, translations, position.messageParts, position.expressions, isSourceLocale ? 'ignore' : options.missingTranslation || 'warning');
|
---|
431 | const expression = utils.buildLocalizeReplacement(translated[0], translated[1]);
|
---|
432 | const { code } = generate(expression);
|
---|
433 | content.replace(position.start, position.end - 1, code);
|
---|
434 | }
|
---|
435 | let outputSource = content;
|
---|
436 | if (options.setLocale) {
|
---|
437 | const setLocaleText = `var $localize=Object.assign(void 0===$localize?{}:$localize,{locale:"${locale}"});\n`;
|
---|
438 | // If locale data is provided, load it and prepend to file
|
---|
439 | let localeDataSource;
|
---|
440 | const localeDataPath = i18n.locales[locale] && i18n.locales[locale].dataPath;
|
---|
441 | if (localeDataPath) {
|
---|
442 | const localeDataContent = await loadLocaleData(localeDataPath, true, options.es5);
|
---|
443 | localeDataSource = new OriginalSource(localeDataContent, path.basename(localeDataPath));
|
---|
444 | }
|
---|
445 | outputSource = localeDataSource
|
---|
446 | ? // The semicolon ensures that there is no syntax error between statements
|
---|
447 | new ConcatSource(setLocaleText, localeDataSource, ';\n', content)
|
---|
448 | : new ConcatSource(setLocaleText, content);
|
---|
449 | }
|
---|
450 | const { source: outputCode, map: outputMap } = outputSource.sourceAndMap();
|
---|
451 | const outputPath = path.join(options.outputPath, i18n.flatOutput ? '' : locale, options.filename);
|
---|
452 | fs.writeFileSync(outputPath, outputCode);
|
---|
453 | if (inputMap && outputMap) {
|
---|
454 | outputMap.file = options.filename;
|
---|
455 | if (mapSourceRoot) {
|
---|
456 | outputMap.sourceRoot = mapSourceRoot;
|
---|
457 | }
|
---|
458 | fs.writeFileSync(outputPath + '.map', JSON.stringify(outputMap));
|
---|
459 | }
|
---|
460 | }
|
---|
461 | return { file: options.filename, diagnostics: diagnostics.messages, count: positions.length };
|
---|
462 | }
|
---|
463 | function inlineCopyOnly(options) {
|
---|
464 | if (!i18n) {
|
---|
465 | throw new Error('i18n options are missing');
|
---|
466 | }
|
---|
467 | for (const locale of i18n.inlineLocales) {
|
---|
468 | const outputPath = path.join(options.outputPath, i18n.flatOutput ? '' : locale, options.filename);
|
---|
469 | fs.writeFileSync(outputPath, options.code);
|
---|
470 | if (options.map) {
|
---|
471 | fs.writeFileSync(outputPath + '.map', options.map);
|
---|
472 | }
|
---|
473 | }
|
---|
474 | return { file: options.filename, diagnostics: [], count: 0 };
|
---|
475 | }
|
---|
476 | function findLocalizePositions(ast, options, utils) {
|
---|
477 | const positions = [];
|
---|
478 | // Workaround to ensure a path hub is present for traversal
|
---|
479 | const { File } = require('@babel/core');
|
---|
480 | const file = new File({}, { code: options.code, ast });
|
---|
481 | if (options.es5) {
|
---|
482 | core_1.traverse(file.ast, {
|
---|
483 | CallExpression(path) {
|
---|
484 | const callee = path.get('callee');
|
---|
485 | if (callee.isIdentifier() &&
|
---|
486 | callee.node.name === localizeName &&
|
---|
487 | utils.isGlobalIdentifier(callee)) {
|
---|
488 | const [messageParts, expressions] = unwrapLocalizeCall(path, utils);
|
---|
489 | positions.push({
|
---|
490 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
---|
491 | start: path.node.start,
|
---|
492 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
---|
493 | end: path.node.end,
|
---|
494 | messageParts,
|
---|
495 | expressions,
|
---|
496 | });
|
---|
497 | }
|
---|
498 | },
|
---|
499 | });
|
---|
500 | }
|
---|
501 | else {
|
---|
502 | core_1.traverse(file.ast, {
|
---|
503 | TaggedTemplateExpression(path) {
|
---|
504 | if (core_1.types.isIdentifier(path.node.tag) && path.node.tag.name === localizeName) {
|
---|
505 | const [messageParts, expressions] = unwrapTemplateLiteral(path, utils);
|
---|
506 | positions.push({
|
---|
507 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
---|
508 | start: path.node.start,
|
---|
509 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
---|
510 | end: path.node.end,
|
---|
511 | messageParts,
|
---|
512 | expressions,
|
---|
513 | });
|
---|
514 | }
|
---|
515 | },
|
---|
516 | });
|
---|
517 | }
|
---|
518 | return positions;
|
---|
519 | }
|
---|
520 | function unwrapTemplateLiteral(path, utils) {
|
---|
521 | const [messageParts] = utils.unwrapMessagePartsFromTemplateLiteral(path.get('quasi').get('quasis'));
|
---|
522 | const [expressions] = utils.unwrapExpressionsFromTemplateLiteral(path.get('quasi'));
|
---|
523 | return [messageParts, expressions];
|
---|
524 | }
|
---|
525 | function unwrapLocalizeCall(path, utils) {
|
---|
526 | const [messageParts] = utils.unwrapMessagePartsFromLocalizeCall(path);
|
---|
527 | const [expressions] = utils.unwrapSubstitutionsFromLocalizeCall(path);
|
---|
528 | return [messageParts, expressions];
|
---|
529 | }
|
---|
530 | async function loadLocaleData(path, optimize, es5) {
|
---|
531 | // The path is validated during option processing before the build starts
|
---|
532 | const content = fs.readFileSync(path, 'utf8');
|
---|
533 | // Downlevel and optimize the data
|
---|
534 | const transformResult = await core_1.transformAsync(content, {
|
---|
535 | filename: path,
|
---|
536 | // The types do not include the false option even though it is valid
|
---|
537 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
---|
538 | inputSourceMap: false,
|
---|
539 | babelrc: false,
|
---|
540 | configFile: false,
|
---|
541 | presets: [
|
---|
542 | [
|
---|
543 | require.resolve('@babel/preset-env'),
|
---|
544 | {
|
---|
545 | bugfixes: true,
|
---|
546 | // IE 11 is the oldest supported browser
|
---|
547 | targets: es5 ? { ie: '11' } : { esmodules: true },
|
---|
548 | },
|
---|
549 | ],
|
---|
550 | ],
|
---|
551 | minified: environment_options_1.allowMinify && optimize,
|
---|
552 | compact: !environment_options_1.shouldBeautify && optimize,
|
---|
553 | comments: !optimize,
|
---|
554 | });
|
---|
555 | if (!transformResult || !transformResult.code) {
|
---|
556 | throw new Error(`Unknown error occurred processing bundle for "${path}".`);
|
---|
557 | }
|
---|
558 | return transformResult.code;
|
---|
559 | }
|
---|