From c246b0f1cf1e1e0ae65f77712bb0ebc6f3c7c477 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Mon, 9 Jan 2023 05:15:37 -0500 Subject: [PATCH] feat(webpack): remove support for legacy browsers (#14190) --- .../react/plugins/component-testing/index.ts | 25 +- .../component-testing/webpack-fallback.ts | 2 +- .../react/plugins/storybook/index.spec.ts | 33 -- packages/react/plugins/storybook/index.ts | 35 +- packages/react/plugins/webpack.ts | 111 ++-- packages/webpack/index.ts | 4 + packages/webpack/package.json | 1 - .../executors/dev-server/dev-server.impl.ts | 1 + .../dev-server/lib/get-dev-server-config.ts | 8 +- .../webpack/lib/get-webpack-config.ts | 314 +----------- .../webpack/lib/normalize-options.ts | 2 + .../webpack/src/executors/webpack/schema.d.ts | 1 + .../src/executors/webpack/webpack.impl.ts | 9 +- .../generate-package-json-webpack-plugin.ts | 0 .../webpack/src/plugins/stats-json-plugin.ts | 10 + packages/webpack/src/utils/config.ts | 438 +--------------- .../webpack/src/utils/create-copy-plugin.ts | 24 + .../src/utils/get-client-environment.ts | 30 ++ .../src/utils/get-css-module-local-ident.ts | 31 ++ packages/webpack/src/utils/models.ts | 4 +- .../src/utils/webpack/normalize-entry.ts | 20 +- .../src/utils/webpack/package-chunk-sort.ts | 4 +- .../src/utils/webpack/partials/browser.ts | 86 ---- .../src/utils/webpack/partials/common.ts | 191 ------- .../src/utils/webpack/partials/styles.ts | 219 -------- .../utils/webpack/plugins/raw-css-loader.ts | 5 - .../plugins/remove-empty-scripts-plugin.ts | 179 ------- .../src/utils/webpack/safari-nomodule.js | 23 - packages/webpack/src/utils/with-nx.ts | 345 +++++++++++++ packages/webpack/src/utils/with-web.ts | 478 ++++++++++++++++++ 30 files changed, 1063 insertions(+), 1570 deletions(-) delete mode 100644 packages/react/plugins/storybook/index.spec.ts rename packages/webpack/src/{utils => plugins}/generate-package-json-webpack-plugin.ts (100%) create mode 100644 packages/webpack/src/plugins/stats-json-plugin.ts create mode 100644 packages/webpack/src/utils/create-copy-plugin.ts create mode 100644 packages/webpack/src/utils/get-client-environment.ts create mode 100644 packages/webpack/src/utils/get-css-module-local-ident.ts delete mode 100644 packages/webpack/src/utils/webpack/partials/browser.ts delete mode 100644 packages/webpack/src/utils/webpack/partials/common.ts delete mode 100644 packages/webpack/src/utils/webpack/partials/styles.ts delete mode 100644 packages/webpack/src/utils/webpack/plugins/raw-css-loader.ts delete mode 100644 packages/webpack/src/utils/webpack/plugins/remove-empty-scripts-plugin.ts delete mode 100644 packages/webpack/src/utils/webpack/safari-nomodule.js create mode 100644 packages/webpack/src/utils/with-nx.ts create mode 100644 packages/webpack/src/utils/with-web.ts diff --git a/packages/react/plugins/component-testing/index.ts b/packages/react/plugins/component-testing/index.ts index a463049bea9418..7063e1f16ade6b 100644 --- a/packages/react/plugins/component-testing/index.ts +++ b/packages/react/plugins/component-testing/index.ts @@ -199,19 +199,13 @@ function buildTargetWebpack( const options = normalizeOptions( withSchemaDefaults(parsed, context), workspaceRoot, + buildableProjectConfig.root!, buildableProjectConfig.sourceRoot! ); if (options.webpackConfig) { let customWebpack: any; - const isScriptOptimizeOn = - typeof options.optimization === 'boolean' - ? options.optimization - : options.optimization && options.optimization.scripts - ? options.optimization.scripts - : false; - customWebpack = resolveCustomWebpackConfig( options.webpackConfig, options.tsConfig @@ -219,16 +213,13 @@ function buildTargetWebpack( return async () => { customWebpack = await customWebpack; - const defaultWebpack = getWebpackConfig( - context, - options, - isScriptOptimizeOn, - { - root: ctProjectConfig.root, - sourceRoot: ctProjectConfig.sourceRoot, - configuration: parsed.configuration, - } - ); + // TODO(jack): Once webpackConfig is always set in @nrwl/webpack:webpack, we no longer need this default. + const defaultWebpack = getWebpackConfig(context, { + ...options, + root: workspaceRoot, + projectRoot: ctProjectConfig.root, + sourceRoot: ctProjectConfig.sourceRoot, + }); if (customWebpack) { return await customWebpack(defaultWebpack, { diff --git a/packages/react/plugins/component-testing/webpack-fallback.ts b/packages/react/plugins/component-testing/webpack-fallback.ts index 397314e16f08bf..a35a6933c86c5c 100644 --- a/packages/react/plugins/component-testing/webpack-fallback.ts +++ b/packages/react/plugins/component-testing/webpack-fallback.ts @@ -1,6 +1,6 @@ import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin'; import { Configuration } from 'webpack'; -import { getCSSModuleLocalIdent } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config'; +import { getCSSModuleLocalIdent } from '@nrwl/webpack'; export function buildBaseWebpackConfig({ tsConfigPath = 'tsconfig.cy.json', diff --git a/packages/react/plugins/storybook/index.spec.ts b/packages/react/plugins/storybook/index.spec.ts deleted file mode 100644 index 03afb45727e6e4..00000000000000 --- a/packages/react/plugins/storybook/index.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { webpack } from './index'; -import { join } from 'path'; - -jest.mock('@nrwl/webpack/src/executors/webpack/lib/get-webpack-config', () => { - return { - getStylesPartial: () => ({}), - }; -}); - -describe('Storybook webpack config', () => { - it('should skip type checking', async () => { - const config = await webpack( - { - resolve: { - plugins: [], - }, - plugins: [], - module: { - rules: [], - }, - }, - { - configDir: join(__dirname, '../..'), - } - ); - - expect( - config.plugins.find( - (p) => p.constructor.name === 'ForkTsCheckerWebpackPlugin' - ) - ).toBeFalsy(); - }); -}); diff --git a/packages/react/plugins/storybook/index.ts b/packages/react/plugins/storybook/index.ts index 38f98ba9b69148..139d2af04a3018 100644 --- a/packages/react/plugins/storybook/index.ts +++ b/packages/react/plugins/storybook/index.ts @@ -4,17 +4,19 @@ import { readJsonFile, workspaceRoot, } from '@nrwl/devkit'; -import { getBaseWebpackPartial } from '@nrwl/webpack/src/utils/config'; +import { + composePlugins, + getBaseWebpackPartial, +} from '@nrwl/webpack/src/utils/config'; import { NormalizedWebpackExecutorOptions } from '@nrwl/webpack/src/executors/webpack/schema'; -import { getStylesPartial } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config'; import { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils'; import { join } from 'path'; import { gte } from 'semver'; import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack'; import * as mergeWebpack from 'webpack-merge'; import { mergePlugins } from './merge-plugins'; - -const reactWebpackConfig = require('../webpack'); +import { withReact } from '../webpack'; +import { withNx, withWeb } from '@nrwl/webpack'; // This is shamelessly taken from CRA and modified for NX use // https://github.com/facebook/create-react-app/blob/4784997f0682e75eb32a897b4ffe34d735912e6c/packages/react-scripts/config/env.js#L71 @@ -118,21 +120,13 @@ export const webpack = async ( const extractCss = storybookWebpackConfig.mode === 'production'; // ESM build for modern browsers. - const baseWebpackConfig = mergeWebpack.merge([ - getBaseWebpackPartial(builderOptions, { - isScriptOptimizeOn, - skipTypeCheck: true, - }), - getStylesPartial( - options.workspaceRoot, - options.configDir, - builderOptions, - extractCss - ), - ]); - - // run it through the React customizations - const finalConfig = reactWebpackConfig(baseWebpackConfig); + let baseWebpackConfig: Configuration = {}; + const configure = composePlugins( + withNx({ skipTypeChecking: true }), + withWeb(), + withReact() + ); + const finalConfig = configure(baseWebpackConfig, { options: builderOptions }); // Check whether the project .babelrc uses @emotion/babel-plugin. There's currently // a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently @@ -197,7 +191,8 @@ export const webpack = async ( plugins: mergePlugins( ...((storybookWebpackConfig.resolve.plugins ?? []) as unknown as WebpackPluginInstance[]), - ...(finalConfig.resolve.plugins ?? []) + ...((finalConfig.resolve + .plugins as unknown as WebpackPluginInstance[]) ?? []) ), }, plugins: mergePlugins( diff --git a/packages/react/plugins/webpack.ts b/packages/react/plugins/webpack.ts index 0b8d69792a77a4..61a4cb1479bbeb 100644 --- a/packages/react/plugins/webpack.ts +++ b/packages/react/plugins/webpack.ts @@ -1,58 +1,77 @@ import type { Configuration } from 'webpack'; import ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +import { NormalizedWebpackExecutorOptions } from '@nrwl/webpack'; +import { ExecutorContext } from 'nx/src/config/misc-interfaces'; // Add React-specific configuration -export function getWebpackConfig(config: Configuration) { - config.module.rules.push({ - test: /\.svg$/, - issuer: /\.(js|ts|md)x?$/, - use: [ - { - loader: require.resolve('@svgr/webpack'), - options: { - svgo: false, - titleProp: true, - ref: true, +export function withReact() { + return function configure( + config: Configuration, + _ctx?: { + options: NormalizedWebpackExecutorOptions; + context: ExecutorContext; + } + ): Configuration { + config.module.rules.push({ + test: /\.svg$/, + issuer: /\.(js|ts|md)x?$/, + use: [ + { + loader: require.resolve('@svgr/webpack'), + options: { + svgo: false, + titleProp: true, + ref: true, + }, }, - }, - { - loader: require.resolve('file-loader'), - options: { - name: '[name].[hash].[ext]', + { + loader: require.resolve('file-loader'), + options: { + name: '[name].[hash].[ext]', + }, }, - }, - ], - }); + ], + }); - if (config.mode === 'development' && config['devServer']?.hot) { - // add `react-refresh/babel` to babel loader plugin - const babelLoader = config.module.rules.find( - (rule) => - typeof rule !== 'string' && - rule.loader?.toString().includes('babel-loader') - ); - if (babelLoader && typeof babelLoader !== 'string') { - babelLoader.options['plugins'] = [ - ...(babelLoader.options['plugins'] || []), - [ - require.resolve('react-refresh/babel'), - { - skipEnvCheck: true, - }, - ], - ]; + if (config.mode === 'development' && config['devServer']?.hot) { + // add `react-refresh/babel` to babel loader plugin + const babelLoader = config.module.rules.find( + (rule) => + typeof rule !== 'string' && + rule.loader?.toString().includes('babel-loader') + ); + if (babelLoader && typeof babelLoader !== 'string') { + babelLoader.options['plugins'] = [ + ...(babelLoader.options['plugins'] || []), + [ + require.resolve('react-refresh/babel'), + { + skipEnvCheck: true, + }, + ], + ]; + } + // add https://github.com/pmmmwh/react-refresh-webpack-plugin to webpack plugin + config.plugins.push(new ReactRefreshPlugin()); } - // add https://github.com/pmmmwh/react-refresh-webpack-plugin to webpack plugin - config.plugins.push(new ReactRefreshPlugin()); - } - // enable webpack node api - config.node = { - __dirname: true, - __filename: true, - }; + // enable webpack node api + config.node = { + __dirname: true, + __filename: true, + }; - return config; + return config; + }; } -module.exports = getWebpackConfig; +// Support existing default exports as well as new named export. +const legacyExport: any = withReact(); +legacyExport.withReact = withReact; + +/** @deprecated use `import { withReact } from '@nrwl/react'` */ +// This is here for backward compatibility if anyone imports {getWebpackConfig} directly. +// TODO(jack): Remove in Nx 16 +legacyExport.getWebpackConfig = withReact(); + +export default legacyExport; diff --git a/packages/webpack/index.ts b/packages/webpack/index.ts index 57e56c9b3afbc1..1cd12a35512ac2 100644 --- a/packages/webpack/index.ts +++ b/packages/webpack/index.ts @@ -1,3 +1,4 @@ +export * from './src/utils/create-copy-plugin'; export * from './src/utils/config'; export * from './src/generators/init/init'; export * from './src/generators/webpack-project/webpack-project'; @@ -11,3 +12,6 @@ export type { FileReplacement, } from './src/executors/webpack/schema'; export * from './src/executors/webpack/webpack.impl'; +export * from './src/utils/get-css-module-local-ident'; +export * from './src/utils/with-nx'; +export * from './src/utils/with-web'; diff --git a/packages/webpack/package.json b/packages/webpack/package.json index b327f4ad9b52b9..4e56ba8e659865 100644 --- a/packages/webpack/package.json +++ b/packages/webpack/package.json @@ -54,7 +54,6 @@ "postcss": "^8.4.14", "postcss-import": "~14.1.0", "postcss-loader": "^6.1.1", - "raw-loader": "^4.0.2", "rxjs": "^6.5.4", "sass": "^1.42.1", "sass-loader": "^12.2.0", diff --git a/packages/webpack/src/executors/dev-server/dev-server.impl.ts b/packages/webpack/src/executors/dev-server/dev-server.impl.ts index 34420a1ca1cb48..0da28079446ad7 100644 --- a/packages/webpack/src/executors/dev-server/dev-server.impl.ts +++ b/packages/webpack/src/executors/dev-server/dev-server.impl.ts @@ -30,6 +30,7 @@ export async function* devServerExecutor( const buildOptions = normalizeOptions( getBuildOptions(serveOptions, context), context.root, + projectRoot, sourceRoot ); diff --git a/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts b/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts index cfbe56ca770aff..403a1fc71b62bd 100644 --- a/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts +++ b/packages/webpack/src/executors/dev-server/lib/get-dev-server-config.ts @@ -20,13 +20,7 @@ export function getDevServerConfig( const workspaceRoot = context.root; const { root: projectRoot, sourceRoot } = context.projectsConfigurations.projects[context.projectName]; - const webpackConfig = getWebpackConfig( - context, - buildOptions, - typeof buildOptions.optimization === 'boolean' - ? buildOptions.optimization - : buildOptions.optimization?.scripts - ); + const webpackConfig = getWebpackConfig(context, buildOptions); (webpackConfig as any).devServer = getDevServerPartial( workspaceRoot, diff --git a/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts b/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts index 85c5d0c82e1e0f..1881e8601765e4 100644 --- a/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts +++ b/packages/webpack/src/executors/webpack/lib/get-webpack-config.ts @@ -1,28 +1,10 @@ -import * as path from 'path'; -import { posix, resolve } from 'path'; -import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript'; -import { getHashDigest, interpolateName } from 'loader-utils'; import type { Configuration } from 'webpack'; - -import { NormalizedWebpackExecutorOptions } from '../schema'; - -// TODO(jack): These should be inlined in a single function so it is easier to understand -import { getBaseWebpackPartial } from '../../../utils/config'; -import { getBrowserConfig } from '../../../utils/webpack/partials/browser'; -import { getCommonConfig } from '../../../utils/webpack/partials/common'; -import { getStylesConfig } from '../../../utils/webpack/partials/styles'; import { ExecutorContext } from '@nrwl/devkit'; -import MiniCssExtractPlugin = require('mini-css-extract-plugin'); -import webpackMerge = require('webpack-merge'); -import postcssImports = require('postcss-import'); - -// PostCSS options depend on the webpack loader, but we need to set the `config` path as a string due to this check: -// https://github.com/webpack-contrib/postcss-loader/blob/0d342b1/src/utils.js#L36 -interface PostcssOptions { - (loader: any): any; - config?: string; -} +import { NormalizedWebpackExecutorOptions } from '../schema'; +import { withNx } from '../../../utils/with-nx'; +import { withWeb } from '../../../utils/with-web'; +import { composePlugins } from '@nrwl/webpack'; interface GetWebpackConfigOverrides { root: string; @@ -30,288 +12,14 @@ interface GetWebpackConfigOverrides { configuration?: string; } +/** @deprecated Use withNx, withWeb, or withReact */ +// TODO(jack): Remove in Nx 16 export function getWebpackConfig( context: ExecutorContext, - options: NormalizedWebpackExecutorOptions, - isScriptOptimizeOn?: boolean, - overrides?: GetWebpackConfigOverrides -): Configuration { - const tsConfig = readTsConfig(options.tsConfig); - const workspaceRoot = context.root; - - let sourceRoot: string; - let projectRoot: string; - if (overrides) { - projectRoot = overrides.root; - sourceRoot = overrides.sourceRoot; - } else { - const project = - context.projectsConfigurations.projects[context.projectName]; - projectRoot = project.root; - sourceRoot = project.sourceRoot; - } - - const wco: any = { - root: workspaceRoot, - projectRoot: resolve(workspaceRoot, projectRoot), - sourceRoot: resolve(workspaceRoot, sourceRoot), - buildOptions: convertBuildOptions(options), - console, - tsConfig, - tsConfigPath: options.tsConfig, - }; - // TODO(jack): Replace merge behavior with an inlined config so it is easier to understand. - return webpackMerge.merge([ - _getBaseWebpackPartial( - context, - options, - isScriptOptimizeOn, - tsConfig.options.emitDecoratorMetadata, - overrides - ), - options.target === 'web' - ? getPolyfillsPartial(options.polyfills, isScriptOptimizeOn) - : {}, - options.target === 'web' - ? getStylesPartial( - wco.root, - wco.projectRoot, - wco.buildOptions, - options.extractCss, - options.postcssConfig - ) - : {}, - getCommonPartial(wco), - options.target === 'web' ? getBrowserConfig(wco) : {}, - ]); -} - -function _getBaseWebpackPartial( - context: ExecutorContext, - options: NormalizedWebpackExecutorOptions, - isScriptOptimizeOn: boolean, - emitDecoratorMetadata: boolean, - overrides?: GetWebpackConfigOverrides -) { - let partial = getBaseWebpackPartial( - options, - { - isScriptOptimizeOn, - emitDecoratorMetadata, - configuration: overrides?.configuration ?? context.configurationName, - }, - context - ); - delete partial.resolve.mainFields; - return partial; -} - -function getCommonPartial(wco: any): Configuration { - const commonConfig: Configuration = getCommonConfig(wco); - delete commonConfig.entry; - delete commonConfig.resolve.modules; - delete commonConfig.resolve.extensions; - delete commonConfig.output.path; - delete commonConfig.module; - return commonConfig; -} - -export function getStylesPartial( - workspaceRoot: string, - projectRoot: string, - options: any, - extractCss: boolean, - postcssConfig?: string + options: NormalizedWebpackExecutorOptions ): Configuration { - const includePaths: string[] = []; - - if (options?.stylePreprocessorOptions?.includePaths?.length > 0) { - options.stylePreprocessorOptions.includePaths.forEach( - (includePath: string) => - includePaths.push(path.resolve(workspaceRoot, includePath)) - ); - } - - const partial = getStylesConfig(workspaceRoot, options, includePaths); - const rules = partial.module.rules.map((rule) => { - if (!Array.isArray(rule.use)) { - return rule; - } - rule.use = rule.use.map((loaderConfig) => { - if ( - typeof loaderConfig === 'object' && - loaderConfig.loader === require.resolve('raw-loader') - ) { - return { - loader: require.resolve('style-loader'), - }; - } - return loaderConfig; - }); - return rule; - }); - - const loaderModulesOptions = { - modules: { - mode: 'local', - getLocalIdent: getCSSModuleLocalIdent, - }, - importLoaders: 1, - }; - const postcssOptions: PostcssOptions = () => ({ - plugins: [ - postcssImports({ - addModulesDirectories: includePaths, - resolve: (url: string) => (url.startsWith('~') ? url.slice(1) : url), - }), - ], - }); - // If a path to postcssConfig is passed in, set it for app and all libs, otherwise - // use automatic detection. - if (typeof postcssConfig === 'string') { - postcssOptions.config = path.join(workspaceRoot, postcssConfig); - } - - const commonLoaders = [ - { - loader: extractCss - ? MiniCssExtractPlugin.loader - : require.resolve('style-loader'), - }, - { - loader: require.resolve('css-loader'), - options: loaderModulesOptions, - }, - { - loader: require.resolve('postcss-loader'), - options: { - implementation: require('postcss'), - postcssOptions: postcssOptions, - }, - }, - ]; - - partial.module.rules = [ - { - test: /\.css$|\.scss$|\.sass$|\.less$|\.styl$/, - oneOf: [ - { - test: /\.module\.css$/, - use: commonLoaders, - }, - { - test: /\.module\.(scss|sass)$/, - use: [ - ...commonLoaders, - { - loader: require.resolve('sass-loader'), - options: { - implementation: require('sass'), - sassOptions: { - fiber: false, - precision: 8, - includePaths, - }, - }, - }, - ], - }, - { - test: /\.module\.less$/, - use: [ - ...commonLoaders, - { - loader: require.resolve('less-loader'), - options: { - lessOptions: { - paths: includePaths, - }, - }, - }, - ], - }, - { - test: /\.module\.styl$/, - use: [ - ...commonLoaders, - { - loader: require.resolve('stylus-loader'), - options: { - stylusOptions: { - include: includePaths, - }, - }, - }, - ], - }, - ...rules, - ], - }, - ]; - return partial; -} - -export function getPolyfillsPartial( - polyfills: string, - isScriptOptimizeOn: boolean -): Configuration { - const config = { - entry: {} as { [key: string]: string[] }, - }; - - if (polyfills && isScriptOptimizeOn) { - // Safari 10.1 supports