diff --git a/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts b/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts index 31595dfb93b3..b62f045fe234 100644 --- a/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts +++ b/packages/angular_devkit/build_angular/src/webpack/plugins/scripts-webpack-plugin.ts @@ -12,6 +12,11 @@ import { Chunk, Compilation, Compiler, sources as webpackSources } from 'webpack const Entrypoint = require('webpack/lib/Entrypoint'); +/** + * The name of the plugin provided to Webpack when tapping Webpack compiler hooks. + */ +const PLUGIN_NAME = 'scripts-webpack-plugin'; + export interface ScriptsWebpackPluginOptions { name: string; sourceMap?: boolean; @@ -97,8 +102,8 @@ export class ScriptsWebpackPlugin { .filter((script) => !!script) .map((script) => path.resolve(this.options.basePath || '', script)); - compiler.hooks.thisCompilation.tap('scripts-webpack-plugin', (compilation) => { - compilation.hooks.additionalAssets.tapPromise('scripts-webpack-plugin', async () => { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { + compilation.hooks.additionalAssets.tapPromise(PLUGIN_NAME, async () => { if (await this.shouldSkip(compilation, scripts)) { if (this._cachedOutput) { this._insertOutput(compilation, this._cachedOutput, true); @@ -149,19 +154,32 @@ export class ScriptsWebpackPlugin { }); const combinedSource = new webpackSources.CachedSource(concatSource); - const filename = interpolateName( - { resourcePath: 'scripts.js' }, - this.options.filename as string, - { - content: combinedSource.source(), - }, - ); - - const output = { filename, source: combinedSource }; + + const output = { filename: this.options.filename, source: combinedSource }; this._insertOutput(compilation, output); this._cachedOutput = output; addDependencies(compilation, scripts); }); + compilation.hooks.processAssets.tapPromise( + { + name: PLUGIN_NAME, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, + }, + async () => { + const assetName = this.options.filename; + const asset = compilation.getAsset(assetName); + if (asset) { + const interpolatedFilename = interpolateName( + { resourcePath: 'scripts.js' }, + assetName, + { content: asset.source.source() }, + ); + if (assetName !== interpolatedFilename) { + compilation.renameAsset(assetName, interpolatedFilename); + } + } + }, + ); }); } } diff --git a/tests/legacy-cli/e2e/tests/build/scripts-output-hashing.ts b/tests/legacy-cli/e2e/tests/build/scripts-output-hashing.ts new file mode 100644 index 000000000000..430b7a8478ac --- /dev/null +++ b/tests/legacy-cli/e2e/tests/build/scripts-output-hashing.ts @@ -0,0 +1,45 @@ +import { expectFileMatchToExist, expectFileToMatch, writeMultipleFiles } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { updateJsonFile, updateTsConfig } from '../../utils/project'; + +function getScriptsFilename(): Promise { + return expectFileMatchToExist('dist/test-project/', /external-module\.[0-9a-f]{16}\.js/); +} + +export default async function () { + // verify content hash is based on code after optimizations + await writeMultipleFiles({ + 'src/script.js': 'try { console.log(); } catch {}', + }); + await updateJsonFile('angular.json', (configJson) => { + const build = configJson.projects['test-project'].architect.build; + build.options['scripts'] = [ + { + input: 'src/script.js', + inject: true, + bundleName: 'external-module', + }, + ]; + build.configurations['production'].outputHashing = 'all'; + configJson['cli'] = { cache: { enabled: 'false' } }; + }); + await updateTsConfig((json) => { + json['compilerOptions']['target'] = 'es2017'; + json['compilerOptions']['module'] = 'es2020'; + }); + await ng('build', '--configuration=production'); + const filenameBuild1 = await getScriptsFilename(); + await expectFileToMatch(`dist/test-project/${filenameBuild1}`, 'try{console.log()}catch(c){}'); + + await updateTsConfig((json) => { + json['compilerOptions']['target'] = 'es2019'; + }); + await ng('build', '--configuration=production'); + const filenameBuild2 = await getScriptsFilename(); + await expectFileToMatch(`dist/test-project/${filenameBuild2}`, 'try{console.log()}catch{}'); + if (filenameBuild1 === filenameBuild2) { + throw new Error( + 'Contents of the built file changed between builds, but the content hash stayed the same!', + ); + } +}