Skip to content

Commit 936a951

Browse files
alan-agius4vikerman
authored andcommitted
refactor(@angular-devkit/build-angular): move around i18n methods to make them re-usable
1 parent 3163a43 commit 936a951

File tree

11 files changed

+277
-193
lines changed

11 files changed

+277
-193
lines changed

packages/angular/cli/tasks/install-package.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,14 @@ export function installTempPackage(
8282
logger: logging.Logger,
8383
packageManager: PackageManager = PackageManager.Npm,
8484
): string {
85-
const tempPath = mkdtempSync(join(realpathSync(tmpdir()), '.ng-temp-packages-'));
85+
const tempPath = mkdtempSync(join(realpathSync(tmpdir()), 'angular-cli-packages-'));
8686

8787
// clean up temp directory on process exit
88-
process.on('exit', () => rimraf.sync(tempPath));
88+
process.on('exit', () => {
89+
try {
90+
rimraf.sync(tempPath);
91+
} catch { }
92+
});
8993

9094
// setup prefix/global modules path
9195
const packageManagerArgs = getPackageManagerArguments(packageManager);

packages/angular_devkit/build_angular/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"postcss-loader": "3.0.0",
4545
"raw-loader": "3.1.0",
4646
"regenerator-runtime": "0.13.3",
47+
"rimraf": "3.0.0",
4748
"rollup": "1.25.2",
4849
"rxjs": "6.5.3",
4950
"sass": "1.23.1",

packages/angular_devkit/build_angular/src/browser/index.ts

+53-167
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { EmittedFiles, WebpackLoggingCallback, runWebpack } from '@angular-devki
1010
import { join, json, logging, normalize, tags, virtualFs } from '@angular-devkit/core';
1111
import { NodeJsSyncHost } from '@angular-devkit/core/node';
1212
import * as fs from 'fs';
13-
import * as os from 'os';
1413
import * as path from 'path';
1514
import { Observable, from, of } from 'rxjs';
1615
import { concatMap, map, switchMap } from 'rxjs/operators';
@@ -49,12 +48,13 @@ import {
4948
normalizeOptimization,
5049
normalizeSourceMaps,
5150
} from '../utils';
51+
import { BundleActionExecutor } from '../utils/action-executor';
5252
import { findCachePath } from '../utils/cache-path';
5353
import { copyAssets } from '../utils/copy-assets';
5454
import { cachingDisabled } from '../utils/environment-options';
55-
import { emittedFilesToInlineOptions } from '../utils/i18n-inlining';
56-
import { I18nOptions, createI18nOptions, mergeDeprecatedI18nOptions } from '../utils/i18n-options';
57-
import { createTranslationLoader } from '../utils/load-translations';
55+
import { i18nInlineEmittedFiles } from '../utils/i18n-inlining';
56+
import { I18nOptions } from '../utils/i18n-options';
57+
import { ensureOutputPaths } from '../utils/output-paths';
5858
import {
5959
InlineOptions,
6060
ProcessBundleFile,
@@ -63,11 +63,12 @@ import {
6363
} from '../utils/process-bundle';
6464
import { assertCompatibleAngularVersion } from '../utils/version';
6565
import {
66+
BrowserWebpackConfigOptions,
6667
generateBrowserWebpackConfigFromContext,
68+
generateI18nBrowserWebpackConfigFromContext,
6769
getIndexInputFile,
6870
getIndexOutputFile,
6971
} from '../utils/webpack-browser-config';
70-
import { BundleActionExecutor } from './action-executor';
7172
import { Schema as BrowserBuilderSchema } from './schema';
7273

7374
const cacheDownlevelPath = cachingDisabled ? undefined : findCachePath('angular-build-dl');
@@ -99,23 +100,53 @@ export function createBrowserLoggingCallback(
99100
};
100101
}
101102

103+
// todo: the below should be cleaned once dev-server support the new i18n
104+
interface ConfigFromContextReturn {
105+
config: webpack.Configuration;
106+
projectRoot: string;
107+
projectSourceRoot?: string;
108+
}
109+
110+
export async function buildBrowserWebpackConfigFromContext(
111+
options: BrowserBuilderSchema,
112+
context: BuilderContext,
113+
host: virtualFs.Host<fs.Stats>,
114+
i18n: boolean,
115+
): Promise<ConfigFromContextReturn & { i18n: I18nOptions }>;
116+
export async function buildBrowserWebpackConfigFromContext(
117+
options: BrowserBuilderSchema,
118+
context: BuilderContext,
119+
host?: virtualFs.Host<fs.Stats>,
120+
): Promise<ConfigFromContextReturn>;
102121
export async function buildBrowserWebpackConfigFromContext(
103122
options: BrowserBuilderSchema,
104123
context: BuilderContext,
105124
host: virtualFs.Host<fs.Stats> = new NodeJsSyncHost(),
106-
): Promise<{ config: webpack.Configuration; projectRoot: string; projectSourceRoot?: string }> {
125+
i18n = false,
126+
): Promise<ConfigFromContextReturn & { i18n?: I18nOptions }> {
127+
const webpackPartialGenerator = (wco: BrowserWebpackConfigOptions) => [
128+
getCommonConfig(wco),
129+
getBrowserConfig(wco),
130+
getStylesConfig(wco),
131+
getStatsConfig(wco),
132+
getAnalyticsConfig(wco, context),
133+
getCompilerConfig(wco),
134+
wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {},
135+
];
136+
137+
if (i18n) {
138+
return generateI18nBrowserWebpackConfigFromContext(
139+
options,
140+
context,
141+
webpackPartialGenerator,
142+
host,
143+
);
144+
}
145+
107146
return generateBrowserWebpackConfigFromContext(
108147
options,
109148
context,
110-
wco => [
111-
getCommonConfig(wco),
112-
getBrowserConfig(wco),
113-
getStylesConfig(wco),
114-
getStatsConfig(wco),
115-
getAnalyticsConfig(wco, context),
116-
getCompilerConfig(wco),
117-
wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {},
118-
],
149+
webpackPartialGenerator,
119150
host,
120151
);
121152
}
@@ -161,89 +192,24 @@ async function initialize(
161192
projectSourceRoot?: string;
162193
i18n: I18nOptions;
163194
}> {
164-
if (!context.target) {
165-
throw new Error('The builder requires a target.');
166-
}
167-
168-
const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot);
169-
const usingIvy = tsConfig.options.enableIvy !== false;
170-
const metadata = await context.getProjectMetadata(context.target);
171-
const projectRoot = path.join(context.workspaceRoot, (metadata.root as string) || '');
172-
const i18n = createI18nOptions(metadata, options.localize);
173-
174-
// Until 11.0, support deprecated i18n options when not using new localize option
175-
// i18nFormat is automatically calculated
176-
if (options.localize === undefined && usingIvy) {
177-
mergeDeprecatedI18nOptions(i18n, options.i18nLocale, options.i18nFile);
178-
} else if (options.localize !== undefined && !usingIvy) {
179-
options.localize = undefined;
180-
181-
context.logger.warn(`Option 'localize' is not supported with View Engine.`);
182-
}
183-
184-
if (i18n.shouldInline) {
185-
// Load locales
186-
const loader = await createTranslationLoader();
187-
188-
const usedFormats = new Set<string>();
189-
for (const [locale, desc] of Object.entries(i18n.locales)) {
190-
if (i18n.inlineLocales.has(locale)) {
191-
const result = loader(path.join(projectRoot, desc.file));
192-
193-
usedFormats.add(result.format);
194-
if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) {
195-
// This limitation is only for legacy message id support (defaults to true as of 9.0)
196-
throw new Error(
197-
'Localization currently only supports using one type of translation file format for the entire application.',
198-
);
199-
}
200-
201-
desc.format = result.format;
202-
desc.translation = result.translation;
203-
}
204-
}
205-
206-
// Legacy message id's require the format of the translations
207-
if (usedFormats.size > 0) {
208-
options.i18nFormat = [...usedFormats][0];
209-
}
210-
}
211-
212195
const originalOutputPath = options.outputPath;
213-
214-
// If inlining store the output in a temporary location to facilitate post-processing
215-
if (i18n.shouldInline) {
216-
options.outputPath = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'angular-cli-'));
217-
}
218-
219-
const { config, projectSourceRoot } = await buildBrowserWebpackConfigFromContext(
196+
const { config, projectRoot, projectSourceRoot, i18n } = await buildBrowserWebpackConfigFromContext(
220197
options,
221198
context,
222199
host,
200+
true,
223201
);
224202

225-
if (i18n.shouldInline) {
226-
// Remove localize "polyfill"
227-
if (!config.resolve) {
228-
config.resolve = {};
229-
}
230-
if (!config.resolve.alias) {
231-
config.resolve.alias = {};
232-
}
233-
config.resolve.alias['@angular/localize/init'] = require.resolve('./empty.js');
234-
}
235-
236203
let transformedConfig;
237204
if (webpackConfigurationTransform) {
238205
transformedConfig = await webpackConfigurationTransform(config);
239206
}
240207

241208
if (options.deleteOutputPath) {
242-
await deleteOutputDir(
243-
normalize(context.workspaceRoot),
244-
normalize(originalOutputPath),
245-
host,
246-
).toPromise();
209+
deleteOutputDir(
210+
context.workspaceRoot,
211+
originalOutputPath,
212+
);
247213
}
248214

249215
return { config: transformedConfig || config, projectRoot, projectSourceRoot, i18n };
@@ -312,15 +278,7 @@ export function buildWebpackBrowser(
312278

313279
return { success };
314280
} else if (success) {
315-
const outputPaths =
316-
i18n.shouldInline && !i18n.flatOutput
317-
? [...i18n.inlineLocales].map(l => path.join(baseOutputPath, l))
318-
: [baseOutputPath];
319-
for (const outputPath of outputPaths) {
320-
if (!fs.existsSync(outputPath)) {
321-
fs.mkdirSync(outputPath, { recursive: true });
322-
}
323-
}
281+
const outputPaths = ensureOutputPaths(baseOutputPath, i18n);
324282

325283
let noModuleFiles: EmittedFiles[] | undefined;
326284
let moduleFiles: EmittedFiles[] | undefined;
@@ -586,14 +544,6 @@ export function buildWebpackBrowser(
586544
}
587545
} finally {
588546
executor.stop();
589-
590-
if (i18n.shouldInline) {
591-
try {
592-
// Remove temporary directory used for i18n processing
593-
// tslint:disable-next-line: no-non-null-assertion
594-
await host.delete(normalize(webpackStats.outputPath!)).toPromise();
595-
} catch {}
596-
}
597547
}
598548

599549
// Copy assets
@@ -787,70 +737,6 @@ function generateIndex(
787737
}).toPromise();
788738
}
789739

790-
async function i18nInlineEmittedFiles(
791-
context: BuilderContext,
792-
emittedFiles: EmittedFiles[],
793-
i18n: I18nOptions,
794-
baseOutputPath: string,
795-
outputPaths: string[],
796-
scriptsEntryPointName: string[],
797-
emittedPath: string,
798-
es5: boolean,
799-
missingTranslation: 'error' | 'warning' | 'ignore' | undefined,
800-
) {
801-
const executor = new BundleActionExecutor({ i18n });
802-
let hasErrors = false;
803-
try {
804-
const { options, originalFiles: processedFiles } = emittedFilesToInlineOptions(
805-
emittedFiles,
806-
scriptsEntryPointName,
807-
emittedPath,
808-
baseOutputPath,
809-
es5,
810-
missingTranslation,
811-
);
812-
813-
for await (const result of executor.inlineAll(options)) {
814-
for (const diagnostic of result.diagnostics) {
815-
if (diagnostic.type === 'error') {
816-
hasErrors = true;
817-
context.logger.error(diagnostic.message);
818-
} else {
819-
context.logger.warn(diagnostic.message);
820-
}
821-
}
822-
}
823-
824-
// Copy any non-processed files into the output locations
825-
await copyAssets(
826-
[
827-
{
828-
glob: '**/*',
829-
input: emittedPath,
830-
output: '',
831-
ignore: [...processedFiles].map(f => path.relative(emittedPath, f)),
832-
},
833-
],
834-
outputPaths,
835-
'',
836-
);
837-
} catch (err) {
838-
context.logger.error('Localized bundle generation failed: ' + err.message);
839-
840-
return false;
841-
} finally {
842-
executor.stop();
843-
}
844-
845-
context.logger.info(`Localized bundle generation ${hasErrors ? 'failed' : 'complete'}.`);
846-
847-
if (hasErrors) {
848-
return false;
849-
}
850-
851-
return true;
852-
}
853-
854740
function mapErrorToMessage(error: unknown): string | undefined {
855741
if (error instanceof Error) {
856742
return error.message;

packages/angular_devkit/build_angular/src/browser/action-cache.ts renamed to packages/angular_devkit/build_angular/src/utils/action-cache.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
*/
88
import { createHash } from 'crypto';
99
import * as fs from 'fs';
10-
import { copyFile } from '../utils/copy-file';
11-
import { manglingDisabled } from '../utils/environment-options';
12-
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle';
10+
import { copyFile } from './copy-file';
11+
import { manglingDisabled } from './environment-options';
12+
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from './process-bundle';
1313

1414
const cacache = require('cacache');
1515
const packageVersion = require('../../package.json').version;

packages/angular_devkit/build_angular/src/browser/action-executor.ts renamed to packages/angular_devkit/build_angular/src/utils/action-executor.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import JestWorker from 'jest-worker';
99
import * as os from 'os';
1010
import * as path from 'path';
1111
import * as v8 from 'v8';
12-
import { I18nOptions } from '../utils/i18n-options';
13-
import { InlineOptions, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle';
1412
import { BundleActionCache } from './action-cache';
13+
import { I18nOptions } from './i18n-options';
14+
import { InlineOptions, ProcessBundleOptions, ProcessBundleResult } from './process-bundle';
1515

1616
const hasThreadSupport = (() => {
1717
try {
@@ -28,10 +28,10 @@ const hasThreadSupport = (() => {
2828
// Processes use JSON which is much more limited
2929
const serialize = ((v8 as unknown) as { serialize(value: unknown): Buffer }).serialize;
3030

31-
let workerFile = require.resolve('../utils/process-bundle');
31+
let workerFile = require.resolve('./process-bundle');
3232
workerFile =
3333
path.extname(workerFile) === '.ts'
34-
? require.resolve('../utils/process-bundle-bootstrap')
34+
? require.resolve('./process-bundle-bootstrap')
3535
: workerFile;
3636

3737
export class BundleActionExecutor {

packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,17 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import { Path, resolve, virtualFs } from '@angular-devkit/core';
9-
import { EMPTY, Observable } from 'rxjs';
10-
import { concatMap, last, map } from 'rxjs/operators';
8+
import { resolve } from 'path';
9+
import * as rimraf from 'rimraf';
1110

1211
/**
1312
* Delete an output directory, but error out if it's the root of the project.
1413
*/
15-
export function deleteOutputDir(root: Path, outputPath: Path, host: virtualFs.Host): Observable<void> {
14+
export function deleteOutputDir(root: string, outputPath: string) {
1615
const resolvedOutputPath = resolve(root, outputPath);
1716
if (resolvedOutputPath === root) {
1817
throw new Error('Output path MUST not be project root directory!');
1918
}
2019

21-
return host.exists(resolvedOutputPath).pipe(
22-
concatMap(exists => exists ? host.delete(resolvedOutputPath) : EMPTY),
23-
last(null, null),
24-
map(() => undefined),
25-
);
20+
rimraf.sync(resolvedOutputPath);
2621
}

0 commit comments

Comments
 (0)