Skip to content

Experimental Webpack 5 Support #18820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/angular_devkit/build_angular/src/utils/process-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from 'webpack-sources';
import { allowMangle, allowMinify, shouldBeautify } from './environment-options';
import { I18nOptions } from './i18n-options';
import { isWebpackFiveOrHigher } from './webpack-version';

type LocalizeUtilities = typeof import('@angular/localize/src/tools/src/source_file_utils');

Expand Down Expand Up @@ -219,21 +220,25 @@ async function mergeSourceMaps(
filename: string,
fast = false,
): Promise<RawSourceMap> {
if (fast) {
// Webpack 5 terser sourcemaps currently fail merging with the high-quality method
// TODO_WEBPACK_5: Investigate high-quality sourcemap merge failures
if (fast || isWebpackFiveOrHigher()) {
return mergeSourceMapsFast(inputSourceMap, resultSourceMap);
}

// SourceMapSource produces high-quality sourcemaps
// The last argument is not yet in the typings
// tslint:disable-next-line: no-any
return new (SourceMapSource as any)(
// Final sourcemap will always be available when providing the input sourcemaps
// tslint:disable-next-line: no-non-null-assertion
const finalSourceMap = new SourceMapSource(
resultCode,
filename,
resultSourceMap,
inputCode,
inputSourceMap,
true,
).map();
).map()!;

return finalSourceMap;
}

async function mergeSourceMapsFast(first: RawSourceMap, second: RawSourceMap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as webpack from 'webpack';
import { isWebpackFiveOrHigher } from './webpack-version';

const WebpackError = require('webpack/lib/WebpackError');
const isWebpackFiveOrHigher = (() => {
if (typeof webpack.version === 'string') {
const versionParts = webpack.version.split('.');
if (versionParts[0] && Number(versionParts[0]) >= 5) {
return true;
}
}

return false;
})();

export function addWarning(compilation: webpack.compilation.Compilation, message: string): void {
if (isWebpackFiveOrHigher) {
if (isWebpackFiveOrHigher()) {
compilation.warnings.push(new WebpackError(message));
} else {
// Allows building with either Webpack 4 or 5+ types
Expand All @@ -30,7 +21,7 @@ export function addWarning(compilation: webpack.compilation.Compilation, message
}

export function addError(compilation: webpack.compilation.Compilation, message: string): void {
if (isWebpackFiveOrHigher) {
if (isWebpackFiveOrHigher()) {
compilation.errors.push(new WebpackError(message));
} else {
// Allows building with either Webpack 4 or 5+ types
Expand Down
28 changes: 28 additions & 0 deletions packages/angular_devkit/build_angular/src/utils/webpack-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @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 * as webpack from 'webpack';

let cachedIsWebpackFiveOrHigher: boolean | undefined;
export function isWebpackFiveOrHigher(): boolean {
if (cachedIsWebpackFiveOrHigher === undefined) {
cachedIsWebpackFiveOrHigher = false;
if (typeof webpack.version === 'string') {
const versionParts = webpack.version.split('.');
if (versionParts[0] && Number(versionParts[0]) >= 5) {
cachedIsWebpackFiveOrHigher = true;
}
}
}

return cachedIsWebpackFiveOrHigher;
}

// tslint:disable-next-line: no-any
export function withWebpackFourOrFive<T, R>(webpackFourValue: T, webpackFiveValue: R): any {
return isWebpackFiveOrHigher() ? webpackFiveValue : webpackFourValue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
import * as webpack from 'webpack';
import { WebpackConfigOptions } from '../../utils/build-options';
import { isWebpackFiveOrHigher, withWebpackFourOrFive } from '../../utils/webpack-version';
import { CommonJsUsageWarnPlugin } from '../plugins';
import { getSourceMapDevTool } from '../utils/helpers';

Expand Down Expand Up @@ -37,7 +38,13 @@ export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configurati
}));
}

if (extractLicenses) {
// TODO_WEBPACK_5: Investigate build/serve issues with the `license-webpack-plugin` package
if (extractLicenses && isWebpackFiveOrHigher()) {
wco.logger.warn(
'WARNING: License extraction is currently disabled when using Webpack 5. ' +
'This is temporary and will be corrected in a future update.',
);
} else if (extractLicenses) {
const LicenseWebpackPlugin = require('license-webpack-plugin').LicenseWebpackPlugin;
extraPlugins.push(new LicenseWebpackPlugin({
stats: {
Expand Down Expand Up @@ -71,6 +78,7 @@ export function getBrowserConfig(wco: WebpackConfigOptions): webpack.Configurati
resolve: {
mainFields: ['es2015', 'browser', 'module', 'main'],
},
...withWebpackFourOrFive({}, { target: ['web', 'es5'] }),
output: {
crossOriginLoading,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
shouldBeautify,
} from '../../utils/environment-options';
import { findAllNodeModules } from '../../utils/find-up';
import { isWebpackFiveOrHigher, withWebpackFourOrFive } from '../../utils/webpack-version';
import {
BundleBudgetPlugin,
DedupeModuleResolvePlugin,
Expand Down Expand Up @@ -317,7 +318,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
);
}

if (buildOptions.namedChunks) {
if (buildOptions.namedChunks && !isWebpackFiveOrHigher()) {
extraPlugins.push(new NamedLazyChunksPlugin());
}

Expand Down Expand Up @@ -475,9 +476,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
extensions: ['.ts', '.tsx', '.mjs', '.js'],
symlinks: !buildOptions.preserveSymlinks,
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
plugins: [
PnpWebpackPlugin,
],
plugins: isWebpackFiveOrHigher() ? [] : [PnpWebpackPlugin],
},
resolveLoader: {
symlinks: !buildOptions.preserveSymlinks,
Expand All @@ -488,20 +487,23 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
'node_modules',
...findAllNodeModules(__dirname, projectRoot),
],
plugins: [PnpWebpackPlugin.moduleLoader(module)],
plugins: isWebpackFiveOrHigher() ? [] : [PnpWebpackPlugin.moduleLoader(module)],
},
context: root,
entry: entryPoints,
output: {
futureEmitAssets: true,
...withWebpackFourOrFive({ futureEmitAssets: true }, {}),
path: path.resolve(root, buildOptions.outputPath),
publicPath: buildOptions.deployUrl,
filename: `[name]${targetInFileName}${hashFormat.chunk}.js`,
},
watch: buildOptions.watch,
watchOptions: {
poll: buildOptions.poll,
ignored: buildOptions.poll === undefined ? undefined : /[\\\/]node_modules[\\\/]/,
ignored:
buildOptions.poll === undefined
? undefined
: withWebpackFourOrFive(/[\\\/]node_modules[\\\/]/, 'node_modules/**'),
},
performance: {
hints: false,
Expand Down Expand Up @@ -586,9 +588,12 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
},
optimization: {
minimizer: extraMinimizers,
moduleIds: 'hashed',
noEmitOnErrors: true,
moduleIds: withWebpackFourOrFive('hashed', 'deterministic'),
...withWebpackFourOrFive({}, buildOptions.namedChunks ? { chunkIds: 'named' } : {}),
...withWebpackFourOrFive({ noEmitOnErrors: true }, { emitOnErrors: false }),
},
// TODO_WEBPACK_5: Investigate non-working cache in development builds
...withWebpackFourOrFive({}, { cache: false }),
plugins: [
// Always replace the context for the System.import in angular/core to prevent warnings.
// https://github.com/angular/angular/issues/11580
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
import { isAbsolute } from 'path';
import { Configuration, ContextReplacementPlugin } from 'webpack';
import { WebpackConfigOptions } from '../../utils/build-options';
import { isWebpackFiveOrHigher } from '../../utils/webpack-version';
import { getSourceMapDevTool } from '../utils/helpers';

type ExternalHookWebpack5 = (
data: { context: string; request: string },
callback: (err?: Error, result?: string) => void,
) => void;

/**
* Returns a partial Webpack configuration specific to creating a bundle for node
* @param wco Options which include the build options and app config
Expand All @@ -29,7 +35,13 @@ export function getServerConfig(wco: WebpackConfigOptions): Configuration {

const externals: Configuration['externals'] = [...externalDependencies];
if (!bundleDependencies) {
externals.push(externalizePackages);
if (isWebpackFiveOrHigher()) {
const hook: ExternalHookWebpack5 = ({ context, request }, callback) =>
externalizePackages(request, context, callback);
externals.push(hook);
} else {
externals.push(externalizePackages as unknown as ExternalHookWebpack5);
}
}

const config: Configuration = {
Expand Down
Loading