From 936a9512ae3231ac74ee33453e095091f2682dc4 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Thu, 24 Oct 2019 19:30:38 +0200 Subject: [PATCH] refactor(@angular-devkit/build-angular): move around i18n methods to make them re-usable --- packages/angular/cli/tasks/install-package.ts | 8 +- .../angular_devkit/build_angular/package.json | 1 + .../build_angular/src/browser/index.ts | 220 +++++------------- .../src/{browser => utils}/action-cache.ts | 6 +- .../src/{browser => utils}/action-executor.ts | 8 +- .../src/utils/delete-output-dir.ts | 13 +- .../build_angular/src/utils/i18n-inlining.ts | 71 +++++- .../build_angular/src/utils/i18n-options.ts | 86 ++++++- .../build_angular/src/utils/output-paths.ts | 25 ++ .../build_angular/src/utils/process-bundle.ts | 26 ++- tests/legacy-cli/e2e_runner.ts | 6 +- 11 files changed, 277 insertions(+), 193 deletions(-) rename packages/angular_devkit/build_angular/src/{browser => utils}/action-cache.ts (97%) rename packages/angular_devkit/build_angular/src/{browser => utils}/action-executor.ts (95%) create mode 100644 packages/angular_devkit/build_angular/src/utils/output-paths.ts diff --git a/packages/angular/cli/tasks/install-package.ts b/packages/angular/cli/tasks/install-package.ts index 2505ece1c8..cbc657e3f4 100644 --- a/packages/angular/cli/tasks/install-package.ts +++ b/packages/angular/cli/tasks/install-package.ts @@ -82,10 +82,14 @@ export function installTempPackage( logger: logging.Logger, packageManager: PackageManager = PackageManager.Npm, ): string { - const tempPath = mkdtempSync(join(realpathSync(tmpdir()), '.ng-temp-packages-')); + const tempPath = mkdtempSync(join(realpathSync(tmpdir()), 'angular-cli-packages-')); // clean up temp directory on process exit - process.on('exit', () => rimraf.sync(tempPath)); + process.on('exit', () => { + try { + rimraf.sync(tempPath); + } catch { } + }); // setup prefix/global modules path const packageManagerArgs = getPackageManagerArguments(packageManager); diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index 0a1269c64c..fab7a5d227 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -44,6 +44,7 @@ "postcss-loader": "3.0.0", "raw-loader": "3.1.0", "regenerator-runtime": "0.13.3", + "rimraf": "3.0.0", "rollup": "1.25.2", "rxjs": "6.5.3", "sass": "1.23.1", diff --git a/packages/angular_devkit/build_angular/src/browser/index.ts b/packages/angular_devkit/build_angular/src/browser/index.ts index 8bc55bcc51..00cc9980b3 100644 --- a/packages/angular_devkit/build_angular/src/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/browser/index.ts @@ -10,7 +10,6 @@ import { EmittedFiles, WebpackLoggingCallback, runWebpack } from '@angular-devki import { join, json, logging, normalize, tags, virtualFs } from '@angular-devkit/core'; import { NodeJsSyncHost } from '@angular-devkit/core/node'; import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; import { Observable, from, of } from 'rxjs'; import { concatMap, map, switchMap } from 'rxjs/operators'; @@ -49,12 +48,13 @@ import { normalizeOptimization, normalizeSourceMaps, } from '../utils'; +import { BundleActionExecutor } from '../utils/action-executor'; import { findCachePath } from '../utils/cache-path'; import { copyAssets } from '../utils/copy-assets'; import { cachingDisabled } from '../utils/environment-options'; -import { emittedFilesToInlineOptions } from '../utils/i18n-inlining'; -import { I18nOptions, createI18nOptions, mergeDeprecatedI18nOptions } from '../utils/i18n-options'; -import { createTranslationLoader } from '../utils/load-translations'; +import { i18nInlineEmittedFiles } from '../utils/i18n-inlining'; +import { I18nOptions } from '../utils/i18n-options'; +import { ensureOutputPaths } from '../utils/output-paths'; import { InlineOptions, ProcessBundleFile, @@ -63,11 +63,12 @@ import { } from '../utils/process-bundle'; import { assertCompatibleAngularVersion } from '../utils/version'; import { + BrowserWebpackConfigOptions, generateBrowserWebpackConfigFromContext, + generateI18nBrowserWebpackConfigFromContext, getIndexInputFile, getIndexOutputFile, } from '../utils/webpack-browser-config'; -import { BundleActionExecutor } from './action-executor'; import { Schema as BrowserBuilderSchema } from './schema'; const cacheDownlevelPath = cachingDisabled ? undefined : findCachePath('angular-build-dl'); @@ -99,23 +100,53 @@ export function createBrowserLoggingCallback( }; } +// todo: the below should be cleaned once dev-server support the new i18n +interface ConfigFromContextReturn { + config: webpack.Configuration; + projectRoot: string; + projectSourceRoot?: string; +} + +export async function buildBrowserWebpackConfigFromContext( + options: BrowserBuilderSchema, + context: BuilderContext, + host: virtualFs.Host, + i18n: boolean, +): Promise; +export async function buildBrowserWebpackConfigFromContext( + options: BrowserBuilderSchema, + context: BuilderContext, + host?: virtualFs.Host, +): Promise; export async function buildBrowserWebpackConfigFromContext( options: BrowserBuilderSchema, context: BuilderContext, host: virtualFs.Host = new NodeJsSyncHost(), -): Promise<{ config: webpack.Configuration; projectRoot: string; projectSourceRoot?: string }> { + i18n = false, +): Promise { + const webpackPartialGenerator = (wco: BrowserWebpackConfigOptions) => [ + getCommonConfig(wco), + getBrowserConfig(wco), + getStylesConfig(wco), + getStatsConfig(wco), + getAnalyticsConfig(wco, context), + getCompilerConfig(wco), + wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {}, + ]; + + if (i18n) { + return generateI18nBrowserWebpackConfigFromContext( + options, + context, + webpackPartialGenerator, + host, + ); + } + return generateBrowserWebpackConfigFromContext( options, context, - wco => [ - getCommonConfig(wco), - getBrowserConfig(wco), - getStylesConfig(wco), - getStatsConfig(wco), - getAnalyticsConfig(wco, context), - getCompilerConfig(wco), - wco.buildOptions.webWorkerTsConfig ? getWorkerConfig(wco) : {}, - ], + webpackPartialGenerator, host, ); } @@ -161,89 +192,24 @@ async function initialize( projectSourceRoot?: string; i18n: I18nOptions; }> { - if (!context.target) { - throw new Error('The builder requires a target.'); - } - - const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot); - const usingIvy = tsConfig.options.enableIvy !== false; - const metadata = await context.getProjectMetadata(context.target); - const projectRoot = path.join(context.workspaceRoot, (metadata.root as string) || ''); - const i18n = createI18nOptions(metadata, options.localize); - - // Until 11.0, support deprecated i18n options when not using new localize option - // i18nFormat is automatically calculated - if (options.localize === undefined && usingIvy) { - mergeDeprecatedI18nOptions(i18n, options.i18nLocale, options.i18nFile); - } else if (options.localize !== undefined && !usingIvy) { - options.localize = undefined; - - context.logger.warn(`Option 'localize' is not supported with View Engine.`); - } - - if (i18n.shouldInline) { - // LoadĀ locales - const loader = await createTranslationLoader(); - - const usedFormats = new Set(); - for (const [locale, desc] of Object.entries(i18n.locales)) { - if (i18n.inlineLocales.has(locale)) { - const result = loader(path.join(projectRoot, desc.file)); - - usedFormats.add(result.format); - if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) { - // This limitation is only for legacy message id support (defaults to true as of 9.0) - throw new Error( - 'Localization currently only supports using one type of translation file format for the entire application.', - ); - } - - desc.format = result.format; - desc.translation = result.translation; - } - } - - // Legacy message id's require the format of the translations - if (usedFormats.size > 0) { - options.i18nFormat = [...usedFormats][0]; - } - } - const originalOutputPath = options.outputPath; - - // If inlining store the output in a temporary location to facilitate post-processing - if (i18n.shouldInline) { - options.outputPath = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'angular-cli-')); - } - - const { config, projectSourceRoot } = await buildBrowserWebpackConfigFromContext( + const { config, projectRoot, projectSourceRoot, i18n } = await buildBrowserWebpackConfigFromContext( options, context, host, + true, ); - if (i18n.shouldInline) { - // Remove localize "polyfill" - if (!config.resolve) { - config.resolve = {}; - } - if (!config.resolve.alias) { - config.resolve.alias = {}; - } - config.resolve.alias['@angular/localize/init'] = require.resolve('./empty.js'); - } - let transformedConfig; if (webpackConfigurationTransform) { transformedConfig = await webpackConfigurationTransform(config); } if (options.deleteOutputPath) { - await deleteOutputDir( - normalize(context.workspaceRoot), - normalize(originalOutputPath), - host, - ).toPromise(); + deleteOutputDir( + context.workspaceRoot, + originalOutputPath, + ); } return { config: transformedConfig || config, projectRoot, projectSourceRoot, i18n }; @@ -312,15 +278,7 @@ export function buildWebpackBrowser( return { success }; } else if (success) { - const outputPaths = - i18n.shouldInline && !i18n.flatOutput - ? [...i18n.inlineLocales].map(l => path.join(baseOutputPath, l)) - : [baseOutputPath]; - for (const outputPath of outputPaths) { - if (!fs.existsSync(outputPath)) { - fs.mkdirSync(outputPath, { recursive: true }); - } - } + const outputPaths = ensureOutputPaths(baseOutputPath, i18n); let noModuleFiles: EmittedFiles[] | undefined; let moduleFiles: EmittedFiles[] | undefined; @@ -586,14 +544,6 @@ export function buildWebpackBrowser( } } finally { executor.stop(); - - if (i18n.shouldInline) { - try { - // Remove temporary directory used for i18n processing - // tslint:disable-next-line: no-non-null-assertion - await host.delete(normalize(webpackStats.outputPath!)).toPromise(); - } catch {} - } } // Copy assets @@ -787,70 +737,6 @@ function generateIndex( }).toPromise(); } -async function i18nInlineEmittedFiles( - context: BuilderContext, - emittedFiles: EmittedFiles[], - i18n: I18nOptions, - baseOutputPath: string, - outputPaths: string[], - scriptsEntryPointName: string[], - emittedPath: string, - es5: boolean, - missingTranslation: 'error' | 'warning' | 'ignore' | undefined, -) { - const executor = new BundleActionExecutor({ i18n }); - let hasErrors = false; - try { - const { options, originalFiles: processedFiles } = emittedFilesToInlineOptions( - emittedFiles, - scriptsEntryPointName, - emittedPath, - baseOutputPath, - es5, - missingTranslation, - ); - - for await (const result of executor.inlineAll(options)) { - for (const diagnostic of result.diagnostics) { - if (diagnostic.type === 'error') { - hasErrors = true; - context.logger.error(diagnostic.message); - } else { - context.logger.warn(diagnostic.message); - } - } - } - - // Copy any non-processed files into the output locations - await copyAssets( - [ - { - glob: '**/*', - input: emittedPath, - output: '', - ignore: [...processedFiles].map(f => path.relative(emittedPath, f)), - }, - ], - outputPaths, - '', - ); - } catch (err) { - context.logger.error('Localized bundle generation failed: ' + err.message); - - return false; - } finally { - executor.stop(); - } - - context.logger.info(`Localized bundle generation ${hasErrors ? 'failed' : 'complete'}.`); - - if (hasErrors) { - return false; - } - - return true; -} - function mapErrorToMessage(error: unknown): string | undefined { if (error instanceof Error) { return error.message; diff --git a/packages/angular_devkit/build_angular/src/browser/action-cache.ts b/packages/angular_devkit/build_angular/src/utils/action-cache.ts similarity index 97% rename from packages/angular_devkit/build_angular/src/browser/action-cache.ts rename to packages/angular_devkit/build_angular/src/utils/action-cache.ts index ebd0fd407e..bb8f969520 100644 --- a/packages/angular_devkit/build_angular/src/browser/action-cache.ts +++ b/packages/angular_devkit/build_angular/src/utils/action-cache.ts @@ -7,9 +7,9 @@ */ import { createHash } from 'crypto'; import * as fs from 'fs'; -import { copyFile } from '../utils/copy-file'; -import { manglingDisabled } from '../utils/environment-options'; -import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle'; +import { copyFile } from './copy-file'; +import { manglingDisabled } from './environment-options'; +import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from './process-bundle'; const cacache = require('cacache'); const packageVersion = require('../../package.json').version; diff --git a/packages/angular_devkit/build_angular/src/browser/action-executor.ts b/packages/angular_devkit/build_angular/src/utils/action-executor.ts similarity index 95% rename from packages/angular_devkit/build_angular/src/browser/action-executor.ts rename to packages/angular_devkit/build_angular/src/utils/action-executor.ts index f2995f8e77..4e589af63d 100644 --- a/packages/angular_devkit/build_angular/src/browser/action-executor.ts +++ b/packages/angular_devkit/build_angular/src/utils/action-executor.ts @@ -9,9 +9,9 @@ import JestWorker from 'jest-worker'; import * as os from 'os'; import * as path from 'path'; import * as v8 from 'v8'; -import { I18nOptions } from '../utils/i18n-options'; -import { InlineOptions, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle'; import { BundleActionCache } from './action-cache'; +import { I18nOptions } from './i18n-options'; +import { InlineOptions, ProcessBundleOptions, ProcessBundleResult } from './process-bundle'; const hasThreadSupport = (() => { try { @@ -28,10 +28,10 @@ const hasThreadSupport = (() => { // Processes use JSON which is much more limited const serialize = ((v8 as unknown) as { serialize(value: unknown): Buffer }).serialize; -let workerFile = require.resolve('../utils/process-bundle'); +let workerFile = require.resolve('./process-bundle'); workerFile = path.extname(workerFile) === '.ts' - ? require.resolve('../utils/process-bundle-bootstrap') + ? require.resolve('./process-bundle-bootstrap') : workerFile; export class BundleActionExecutor { diff --git a/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts b/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts index b4234cd269..bb8c648a12 100644 --- a/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts +++ b/packages/angular_devkit/build_angular/src/utils/delete-output-dir.ts @@ -5,22 +5,17 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { Path, resolve, virtualFs } from '@angular-devkit/core'; -import { EMPTY, Observable } from 'rxjs'; -import { concatMap, last, map } from 'rxjs/operators'; +import { resolve } from 'path'; +import * as rimraf from 'rimraf'; /** * Delete an output directory, but error out if it's the root of the project. */ -export function deleteOutputDir(root: Path, outputPath: Path, host: virtualFs.Host): Observable { +export function deleteOutputDir(root: string, outputPath: string) { const resolvedOutputPath = resolve(root, outputPath); if (resolvedOutputPath === root) { throw new Error('Output path MUST not be project root directory!'); } - return host.exists(resolvedOutputPath).pipe( - concatMap(exists => exists ? host.delete(resolvedOutputPath) : EMPTY), - last(null, null), - map(() => undefined), - ); + rimraf.sync(resolvedOutputPath); } diff --git a/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts b/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts index 432bd1a5b5..b202e42f83 100644 --- a/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts +++ b/packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts @@ -5,12 +5,16 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import { BuilderContext } from '@angular-devkit/architect'; import { EmittedFiles } from '@angular-devkit/build-webpack'; import * as fs from 'fs'; import * as path from 'path'; +import { BundleActionExecutor } from './action-executor'; +import { copyAssets } from './copy-assets'; +import { I18nOptions } from './i18n-options'; import { InlineOptions } from './process-bundle'; -export function emittedFilesToInlineOptions( +function emittedFilesToInlineOptions( emittedFiles: EmittedFiles[], scriptsEntryPointName: string[], emittedPath: string, @@ -55,3 +59,68 @@ export function emittedFilesToInlineOptions( return { options, originalFiles }; } + + +export async function i18nInlineEmittedFiles( + context: BuilderContext, + emittedFiles: EmittedFiles[], + i18n: I18nOptions, + baseOutputPath: string, + outputPaths: string[], + scriptsEntryPointName: string[], + emittedPath: string, + es5: boolean, + missingTranslation: 'error' | 'warning' | 'ignore' | undefined, +): Promise { + const executor = new BundleActionExecutor({ i18n }); + let hasErrors = false; + try { + const { options, originalFiles: processedFiles } = emittedFilesToInlineOptions( + emittedFiles, + scriptsEntryPointName, + emittedPath, + baseOutputPath, + es5, + missingTranslation, + ); + + for await (const result of executor.inlineAll(options)) { + for (const diagnostic of result.diagnostics) { + if (diagnostic.type === 'error') { + hasErrors = true; + context.logger.error(diagnostic.message); + } else { + context.logger.warn(diagnostic.message); + } + } + } + + // Copy any non-processed files into the output locations + await copyAssets( + [ + { + glob: '**/*', + input: emittedPath, + output: '', + ignore: [...processedFiles].map(f => path.relative(emittedPath, f)), + }, + ], + outputPaths, + '', + ); + } catch (err) { + context.logger.error('Localized bundle generation failed: ' + err.message); + + return false; + } finally { + executor.stop(); + } + + if (hasErrors) { + context.logger.error('Localized bundle generation failed.'); + } else { + context.logger.info('Localized bundle generation complete.'); + } + + return !hasErrors; +} diff --git a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts b/packages/angular_devkit/build_angular/src/utils/i18n-options.ts index 4db1ef7a60..35da21a669 100644 --- a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/i18n-options.ts @@ -5,7 +5,16 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { json } from '@angular-devkit/core'; +import { BuilderContext } from '@angular-devkit/architect'; +import { json, virtualFs } from '@angular-devkit/core'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig'; +import { Schema as BrowserBuilderSchema } from '../browser/schema'; +import { Schema as ServerBuilderSchema } from '../server/schema'; +import { createTranslationLoader } from './load-translations'; export interface I18nOptions { inlineLocales: Set; @@ -82,7 +91,80 @@ export function createI18nOptions( return i18n; } -export function mergeDeprecatedI18nOptions(i18n: I18nOptions, i18nLocale: string | undefined, i18nFile: string | undefined): I18nOptions { +export async function configureI18nBuild( + context: BuilderContext, + host: virtualFs.Host, + options: T, +): Promise<{ + buildOptions: T, + i18n: I18nOptions, +}> { + if (!context.target) { + throw new Error('The builder requires a target.'); + } + + const buildOptions = { ... options }; + + const tsConfig = readTsconfig(buildOptions.tsConfig, context.workspaceRoot); + const usingIvy = tsConfig.options.enableIvy !== false; + const metadata = await context.getProjectMetadata(context.target); + const i18n = createI18nOptions(metadata, buildOptions.localize); + + // Until 11.0, support deprecated i18n options when not using new localize option + // i18nFormat is automatically calculated + if (buildOptions.localize === undefined && usingIvy) { + mergeDeprecatedI18nOptions(i18n, buildOptions.i18nLocale, buildOptions.i18nFile); + } else if (buildOptions.localize !== undefined && !usingIvy) { + buildOptions.localize = undefined; + + context.logger.warn(`Option 'localize' is not supported with View Engine.`); + } + + if (i18n.inlineLocales.size > 0) { + // LoadĀ locales + const loader = await createTranslationLoader(); + const projectRoot = path.join(context.workspaceRoot, (metadata.root as string) || ''); + const usedFormats = new Set(); + for (const [locale, desc] of Object.entries(i18n.locales)) { + if (i18n.inlineLocales.has(locale)) { + const result = loader(path.join(projectRoot, desc.file)); + + usedFormats.add(result.format); + if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) { + // This limitation is only for legacy message id support (defaults to true as of 9.0) + throw new Error( + 'Localization currently only supports using one type of translation file format for the entire application.', + ); + } + + desc.format = result.format; + desc.translation = result.translation; + } + } + + // Legacy message id's require the format of the translations + if (usedFormats.size > 0) { + buildOptions.i18nFormat = [...usedFormats][0]; + } + } + + // If inlining store the output in a temporary location to facilitate post-processing + if (i18n.shouldInline) { + const tempPath = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'angular-cli-i18n-')); + buildOptions.outputPath = tempPath; + + // Remove temporary directory used for i18n processing + process.on('exit', () => { + try { + rimraf.sync(tempPath); + } catch { } + }); + } + + return { buildOptions, i18n }; +} + +function mergeDeprecatedI18nOptions(i18n: I18nOptions, i18nLocale: string | undefined, i18nFile: string | undefined): I18nOptions { if (i18nFile !== undefined && i18nLocale === undefined) { throw new Error(`Option 'i18nFile' cannot be used without the 'i18nLocale' option.`); } diff --git a/packages/angular_devkit/build_angular/src/utils/output-paths.ts b/packages/angular_devkit/build_angular/src/utils/output-paths.ts new file mode 100644 index 0000000000..24f8563e66 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/output-paths.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright Google Inc. 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.io/license + */ + +import { existsSync, mkdirSync } from 'fs'; +import { join } from 'path'; +import { I18nOptions } from './i18n-options'; + +export function ensureOutputPaths(baseOutputPath: string, i18n: I18nOptions): string[] { + const outputPaths = i18n.shouldInline && !i18n.flatOutput + ? [...i18n.inlineLocales].map(l => join(baseOutputPath, l)) + : [baseOutputPath]; + + for (const outputPath of outputPaths) { + if (!existsSync(outputPath)) { + mkdirSync(outputPath, { recursive: true }); + } + } + + return outputPaths; +} diff --git a/packages/angular_devkit/build_angular/src/utils/process-bundle.ts b/packages/angular_devkit/build_angular/src/utils/process-bundle.ts index f8e8df6189..ab6193e7fb 100644 --- a/packages/angular_devkit/build_angular/src/utils/process-bundle.ts +++ b/packages/angular_devkit/build_angular/src/utils/process-bundle.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import { NodePath, parseSync, transformAsync, traverse, types } from '@babel/core'; +import { NodePath, ParseResult, parseSync, transformAsync, traverse, types } from '@babel/core'; import { createHash } from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; @@ -454,7 +454,9 @@ async function processRuntime( (options.cacheKeys && options.cacheKeys[CacheKey.DownlevelMap]) || null, ); fs.writeFileSync(downlevelFilePath + '.map', downlevelMap); - downlevelCode += `\n//# sourceMappingURL=${path.basename(downlevelFilePath)}.map`; + if (!options.hiddenSourceMaps) { + downlevelCode += `\n//# sourceMappingURL=${path.basename(downlevelFilePath)}.map`; + } } await cachePut( downlevelCode, @@ -594,13 +596,29 @@ function findLocalizePositions( options: InlineOptions, utils: typeof import('@angular/localize/src/tools/src/translate/source_files/source_file_utils'), ): LocalizePosition[] { - const ast = parseSync(options.code, { babelrc: false }); + let ast: ParseResult | undefined | null; + + try { + ast = parseSync(options.code, { + babelrc: false, + sourceType: 'script', + }); + } catch (error) { + if (error.message) { + // 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.substr(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 positions: LocalizePosition[] = []; - if (options.es5) { traverse(ast, { CallExpression(path: NodePath) { diff --git a/tests/legacy-cli/e2e_runner.ts b/tests/legacy-cli/e2e_runner.ts index 851fbcc3ee..57c183da47 100644 --- a/tests/legacy-cli/e2e_runner.ts +++ b/tests/legacy-cli/e2e_runner.ts @@ -93,7 +93,11 @@ allTests = allTests // Disabled on rc.0 due to needed sync with devkit for changes. .filter(name => !name.endsWith('/service-worker.ts')); -if (!argv.ve) { +if (argv.ve) { + // Remove Ivy specific tests + allTests = allTests + .filter(name => !name.includes('tests/i18n/ivy-localize-')); +} else { // These tests are disabled on the Ivy CI jobs because: // - Ivy doesn't support the functionality yet // - The test itself is not applicable to Ivy