UNPKG

10.3 kBJavaScriptView Raw
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 */
9Object.defineProperty(exports, "__esModule", { value: true });
10exports.execute = void 0;
11const architect_1 = require("@angular-devkit/architect");
12const build_webpack_1 = require("@angular-devkit/build-webpack");
13const fs = require("fs");
14const path = require("path");
15const webpack = require("webpack");
16const schema_1 = require("../browser/schema");
17const i18n_options_1 = require("../utils/i18n-options");
18const version_1 = require("../utils/version");
19const webpack_browser_config_1 = require("../utils/webpack-browser-config");
20const configs_1 = require("../webpack/configs");
21const stats_1 = require("../webpack/utils/stats");
22const schema_2 = require("./schema");
23function getI18nOutfile(format) {
24 switch (format) {
25 case 'xmb':
26 return 'messages.xmb';
27 case 'xlf':
28 case 'xlif':
29 case 'xliff':
30 case 'xlf2':
31 case 'xliff2':
32 return 'messages.xlf';
33 case 'json':
34 case 'legacy-migrate':
35 return 'messages.json';
36 case 'arb':
37 return 'messages.arb';
38 default:
39 throw new Error(`Unsupported format "${format}"`);
40 }
41}
42async function getSerializer(format, sourceLocale, basePath, useLegacyIds, diagnostics) {
43 switch (format) {
44 case schema_2.Format.Xmb:
45 const { XmbTranslationSerializer } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/translation_files/xmb_translation_serializer'));
46 // eslint-disable-next-line @typescript-eslint/no-explicit-any
47 return new XmbTranslationSerializer(basePath, useLegacyIds);
48 case schema_2.Format.Xlf:
49 case schema_2.Format.Xlif:
50 case schema_2.Format.Xliff:
51 const { Xliff1TranslationSerializer } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/translation_files/xliff1_translation_serializer'));
52 // eslint-disable-next-line @typescript-eslint/no-explicit-any
53 return new Xliff1TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
54 case schema_2.Format.Xlf2:
55 case schema_2.Format.Xliff2:
56 const { Xliff2TranslationSerializer } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/translation_files/xliff2_translation_serializer'));
57 // eslint-disable-next-line @typescript-eslint/no-explicit-any
58 return new Xliff2TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
59 case schema_2.Format.Json:
60 const { SimpleJsonTranslationSerializer } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/translation_files/json_translation_serializer'));
61 return new SimpleJsonTranslationSerializer(sourceLocale);
62 case schema_2.Format.LegacyMigrate:
63 const { LegacyMessageIdMigrationSerializer } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/translation_files/legacy_message_id_migration_serializer'));
64 return new LegacyMessageIdMigrationSerializer(diagnostics);
65 case schema_2.Format.Arb:
66 const { ArbTranslationSerializer } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/translation_files/arb_translation_serializer'));
67 const fileSystem = {
68 relative(from, to) {
69 return path.relative(from, to);
70 },
71 };
72 // eslint-disable-next-line @typescript-eslint/no-explicit-any
73 return new ArbTranslationSerializer(sourceLocale, basePath, fileSystem);
74 }
75}
76function normalizeFormatOption(options) {
77 let format = options.format;
78 switch (format) {
79 case schema_2.Format.Xlf:
80 case schema_2.Format.Xlif:
81 case schema_2.Format.Xliff:
82 format = schema_2.Format.Xlf;
83 break;
84 case schema_2.Format.Xlf2:
85 case schema_2.Format.Xliff2:
86 format = schema_2.Format.Xlf2;
87 break;
88 }
89 // Default format is xliff1
90 return format !== null && format !== void 0 ? format : schema_2.Format.Xlf;
91}
92class NoEmitPlugin {
93 apply(compiler) {
94 compiler.hooks.shouldEmit.tap('angular-no-emit', () => false);
95 }
96}
97/**
98 * @experimental Direct usage of this function is considered experimental.
99 */
100async function execute(options, context, transforms) {
101 var _a;
102 // Check Angular version.
103 version_1.assertCompatibleAngularVersion(context.workspaceRoot, context.logger);
104 const browserTarget = architect_1.targetFromTargetString(options.browserTarget);
105 const browserOptions = await context.validateOptions(await context.getTargetOptions(browserTarget), await context.getBuilderNameForTarget(browserTarget));
106 const format = normalizeFormatOption(options);
107 // We need to determine the outFile name so that AngularCompiler can retrieve it.
108 let outFile = options.outFile || getI18nOutfile(format);
109 if (options.outputPath) {
110 // AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead.
111 outFile = path.join(options.outputPath, outFile);
112 }
113 outFile = path.resolve(context.workspaceRoot, outFile);
114 if (!context.target || !context.target.project) {
115 throw new Error('The builder requires a target.');
116 }
117 const metadata = await context.getProjectMetadata(context.target);
118 const i18n = i18n_options_1.createI18nOptions(metadata);
119 let useLegacyIds = true;
120 const ivyMessages = [];
121 const { config, projectRoot } = await webpack_browser_config_1.generateBrowserWebpackConfigFromContext({
122 ...browserOptions,
123 optimization: false,
124 sourceMap: {
125 scripts: true,
126 styles: false,
127 vendor: true,
128 },
129 buildOptimizer: false,
130 aot: true,
131 progress: options.progress,
132 budgets: [],
133 assets: [],
134 scripts: [],
135 styles: [],
136 deleteOutputPath: false,
137 extractLicenses: false,
138 subresourceIntegrity: false,
139 outputHashing: schema_1.OutputHashing.None,
140 namedChunks: true,
141 }, context, (wco) => {
142 var _a;
143 if (wco.tsConfig.options.enableIvy === false) {
144 context.logger.warn('Ivy extraction enabled but application is not Ivy enabled. Extraction may fail.');
145 }
146 // Default value for legacy message ids is currently true
147 useLegacyIds = (_a = wco.tsConfig.options.enableI18nLegacyMessageIdFormat) !== null && _a !== void 0 ? _a : true;
148 const partials = [
149 { plugins: [new NoEmitPlugin()] },
150 configs_1.getCommonConfig(wco),
151 configs_1.getBrowserConfig(wco),
152 configs_1.getTypeScriptConfig(wco),
153 configs_1.getWorkerConfig(wco),
154 configs_1.getStatsConfig(wco),
155 ];
156 // Add Ivy application file extractor support
157 partials.unshift({
158 module: {
159 rules: [
160 {
161 test: /\.[t|j]s$/,
162 loader: require.resolve('./ivy-extract-loader'),
163 options: {
164 messageHandler: (messages) => ivyMessages.push(...messages),
165 },
166 },
167 ],
168 },
169 });
170 // Replace all stylesheets with an empty default export
171 partials.push({
172 plugins: [
173 new webpack.NormalModuleReplacementPlugin(/\.(css|scss|sass|styl|less)$/, path.join(__dirname, 'empty-export-default.js')),
174 new webpack.NormalModuleReplacementPlugin(/^angular-resource:style,/, path.join(__dirname, 'empty-export-default.js')),
175 ],
176 });
177 return partials;
178 });
179 try {
180 require.resolve('@angular/localize');
181 }
182 catch {
183 return {
184 success: false,
185 error: `Ivy extraction requires the '@angular/localize' package.`,
186 outputPath: outFile,
187 };
188 }
189 const webpackResult = await build_webpack_1.runWebpack((await ((_a = transforms === null || transforms === void 0 ? void 0 : transforms.webpackConfiguration) === null || _a === void 0 ? void 0 : _a.call(transforms, config))) || config, context, {
190 logging: stats_1.createWebpackLoggingCallback(false, context.logger),
191 webpackFactory: webpack,
192 }).toPromise();
193 // Set the outputPath to the extraction output location for downstream consumers
194 webpackResult.outputPath = outFile;
195 // Complete if Webpack build failed
196 if (!webpackResult.success) {
197 return webpackResult;
198 }
199 const basePath = config.context || projectRoot;
200 const { checkDuplicateMessages } = await Promise.resolve().then(() => require('@angular/localize/src/tools/src/extract/duplicates'));
201 // The filesystem is used to create a relative path for each file
202 // from the basePath. This relative path is then used in the error message.
203 const checkFileSystem = {
204 relative(from, to) {
205 return path.relative(from, to);
206 },
207 };
208 const diagnostics = checkDuplicateMessages(
209 // eslint-disable-next-line @typescript-eslint/no-explicit-any
210 checkFileSystem, ivyMessages, 'warning',
211 // eslint-disable-next-line @typescript-eslint/no-explicit-any
212 basePath);
213 if (diagnostics.messages.length > 0) {
214 context.logger.warn(diagnostics.formatDiagnostics(''));
215 }
216 // Serialize all extracted messages
217 const serializer = await getSerializer(format, i18n.sourceLocale, basePath, useLegacyIds, diagnostics);
218 const content = serializer.serialize(ivyMessages);
219 // Ensure directory exists
220 const outputPath = path.dirname(outFile);
221 if (!fs.existsSync(outputPath)) {
222 fs.mkdirSync(outputPath, { recursive: true });
223 }
224 // Write translation file
225 fs.writeFileSync(outFile, content);
226 return webpackResult;
227}
228exports.execute = execute;
229exports.default = architect_1.createBuilder(execute);