parent
0bc93ee83d
commit
6feb56e014
@ -311,7 +311,7 @@ describe('CLI - Environment Variables', () => {
|
|||||||
|
|
||||||
updateFile(main2, `${newCode2}\n${content2}`);
|
updateFile(main2, `${newCode2}\n${content2}`);
|
||||||
|
|
||||||
runCLI(`run-many --target build --all --no-optimization`, {
|
runCLI(`run-many --target build --all --outputHashing=none`, {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
NODE_ENV: 'test',
|
NODE_ENV: 'test',
|
||||||
|
|||||||
@ -199,19 +199,13 @@ function buildTargetWebpack(
|
|||||||
const options = normalizeOptions(
|
const options = normalizeOptions(
|
||||||
withSchemaDefaults(parsed, context),
|
withSchemaDefaults(parsed, context),
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
|
buildableProjectConfig.root!,
|
||||||
buildableProjectConfig.sourceRoot!
|
buildableProjectConfig.sourceRoot!
|
||||||
);
|
);
|
||||||
|
|
||||||
if (options.webpackConfig) {
|
if (options.webpackConfig) {
|
||||||
let customWebpack: any;
|
let customWebpack: any;
|
||||||
|
|
||||||
const isScriptOptimizeOn =
|
|
||||||
typeof options.optimization === 'boolean'
|
|
||||||
? options.optimization
|
|
||||||
: options.optimization && options.optimization.scripts
|
|
||||||
? options.optimization.scripts
|
|
||||||
: false;
|
|
||||||
|
|
||||||
customWebpack = resolveCustomWebpackConfig(
|
customWebpack = resolveCustomWebpackConfig(
|
||||||
options.webpackConfig,
|
options.webpackConfig,
|
||||||
options.tsConfig
|
options.tsConfig
|
||||||
@ -219,16 +213,13 @@ function buildTargetWebpack(
|
|||||||
|
|
||||||
return async () => {
|
return async () => {
|
||||||
customWebpack = await customWebpack;
|
customWebpack = await customWebpack;
|
||||||
const defaultWebpack = getWebpackConfig(
|
// TODO(jack): Once webpackConfig is always set in @nrwl/webpack:webpack, we no longer need this default.
|
||||||
context,
|
const defaultWebpack = getWebpackConfig(context, {
|
||||||
options,
|
...options,
|
||||||
isScriptOptimizeOn,
|
root: workspaceRoot,
|
||||||
{
|
projectRoot: ctProjectConfig.root,
|
||||||
root: ctProjectConfig.root,
|
|
||||||
sourceRoot: ctProjectConfig.sourceRoot,
|
sourceRoot: ctProjectConfig.sourceRoot,
|
||||||
configuration: parsed.configuration,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (customWebpack) {
|
if (customWebpack) {
|
||||||
return await customWebpack(defaultWebpack, {
|
return await customWebpack(defaultWebpack, {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
||||||
import { Configuration } from 'webpack';
|
import { Configuration } from 'webpack';
|
||||||
import { getCSSModuleLocalIdent } from '@nrwl/webpack/src/executors/webpack/lib/get-webpack-config';
|
import { getCSSModuleLocalIdent } from '@nrwl/webpack';
|
||||||
|
|
||||||
export function buildBaseWebpackConfig({
|
export function buildBaseWebpackConfig({
|
||||||
tsConfigPath = 'tsconfig.cy.json',
|
tsConfigPath = 'tsconfig.cy.json',
|
||||||
|
|||||||
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -4,17 +4,19 @@ import {
|
|||||||
readJsonFile,
|
readJsonFile,
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
} from '@nrwl/devkit';
|
} 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 { 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 { checkAndCleanWithSemver } from '@nrwl/workspace/src/utilities/version-utils';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { gte } from 'semver';
|
import { gte } from 'semver';
|
||||||
import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack';
|
import { Configuration, DefinePlugin, WebpackPluginInstance } from 'webpack';
|
||||||
import * as mergeWebpack from 'webpack-merge';
|
import * as mergeWebpack from 'webpack-merge';
|
||||||
import { mergePlugins } from './merge-plugins';
|
import { mergePlugins } from './merge-plugins';
|
||||||
|
import { withReact } from '../webpack';
|
||||||
const reactWebpackConfig = require('../webpack');
|
import { withNx, withWeb } from '@nrwl/webpack';
|
||||||
|
|
||||||
// This is shamelessly taken from CRA and modified for NX use
|
// 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
|
// https://github.com/facebook/create-react-app/blob/4784997f0682e75eb32a897b4ffe34d735912e6c/packages/react-scripts/config/env.js#L71
|
||||||
@ -99,6 +101,8 @@ export const webpack = async (
|
|||||||
const builderOptions: NormalizedWebpackExecutorOptions = {
|
const builderOptions: NormalizedWebpackExecutorOptions = {
|
||||||
...options,
|
...options,
|
||||||
root: options.configDir,
|
root: options.configDir,
|
||||||
|
// These are blank because root is the absolute path to .storybook folder
|
||||||
|
projectRoot: '',
|
||||||
sourceRoot: '',
|
sourceRoot: '',
|
||||||
fileReplacements: [],
|
fileReplacements: [],
|
||||||
sourceMap: {
|
sourceMap: {
|
||||||
@ -118,21 +122,13 @@ export const webpack = async (
|
|||||||
const extractCss = storybookWebpackConfig.mode === 'production';
|
const extractCss = storybookWebpackConfig.mode === 'production';
|
||||||
|
|
||||||
// ESM build for modern browsers.
|
// ESM build for modern browsers.
|
||||||
const baseWebpackConfig = mergeWebpack.merge([
|
let baseWebpackConfig: Configuration = {};
|
||||||
getBaseWebpackPartial(builderOptions, {
|
const configure = composePlugins(
|
||||||
isScriptOptimizeOn,
|
withNx({ skipTypeChecking: true }),
|
||||||
skipTypeCheck: true,
|
withWeb(),
|
||||||
}),
|
withReact()
|
||||||
getStylesPartial(
|
);
|
||||||
options.workspaceRoot,
|
const finalConfig = configure(baseWebpackConfig, { options: builderOptions });
|
||||||
options.configDir,
|
|
||||||
builderOptions,
|
|
||||||
extractCss
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// run it through the React customizations
|
|
||||||
const finalConfig = reactWebpackConfig(baseWebpackConfig);
|
|
||||||
|
|
||||||
// Check whether the project .babelrc uses @emotion/babel-plugin. There's currently
|
// Check whether the project .babelrc uses @emotion/babel-plugin. There's currently
|
||||||
// a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently
|
// a Storybook issue (https://github.com/storybookjs/storybook/issues/13277) which apparently
|
||||||
@ -197,7 +193,8 @@ export const webpack = async (
|
|||||||
plugins: mergePlugins(
|
plugins: mergePlugins(
|
||||||
...((storybookWebpackConfig.resolve.plugins ??
|
...((storybookWebpackConfig.resolve.plugins ??
|
||||||
[]) as unknown as WebpackPluginInstance[]),
|
[]) as unknown as WebpackPluginInstance[]),
|
||||||
...(finalConfig.resolve.plugins ?? [])
|
...((finalConfig.resolve
|
||||||
|
.plugins as unknown as WebpackPluginInstance[]) ?? [])
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
plugins: mergePlugins(
|
plugins: mergePlugins(
|
||||||
|
|||||||
@ -1,8 +1,17 @@
|
|||||||
import type { Configuration } from 'webpack';
|
import type { Configuration } from 'webpack';
|
||||||
import ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
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
|
// Add React-specific configuration
|
||||||
export function getWebpackConfig(config: Configuration) {
|
export function withReact() {
|
||||||
|
return function configure(
|
||||||
|
config: Configuration,
|
||||||
|
_ctx?: {
|
||||||
|
options: NormalizedWebpackExecutorOptions;
|
||||||
|
context: ExecutorContext;
|
||||||
|
}
|
||||||
|
): Configuration {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.svg$/,
|
test: /\.svg$/,
|
||||||
issuer: /\.(js|ts|md)x?$/,
|
issuer: /\.(js|ts|md)x?$/,
|
||||||
@ -53,6 +62,16 @@ export function getWebpackConfig(config: Configuration) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
module.exports = legacyExport;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
export * from './src/utils/create-copy-plugin';
|
||||||
export * from './src/utils/config';
|
export * from './src/utils/config';
|
||||||
export * from './src/generators/init/init';
|
export * from './src/generators/init/init';
|
||||||
export * from './src/generators/webpack-project/webpack-project';
|
export * from './src/generators/webpack-project/webpack-project';
|
||||||
@ -11,3 +12,6 @@ export type {
|
|||||||
FileReplacement,
|
FileReplacement,
|
||||||
} from './src/executors/webpack/schema';
|
} from './src/executors/webpack/schema';
|
||||||
export * from './src/executors/webpack/webpack.impl';
|
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';
|
||||||
|
|||||||
@ -54,7 +54,6 @@
|
|||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"postcss-import": "~14.1.0",
|
"postcss-import": "~14.1.0",
|
||||||
"postcss-loader": "^6.1.1",
|
"postcss-loader": "^6.1.1",
|
||||||
"raw-loader": "^4.0.2",
|
|
||||||
"rxjs": "^6.5.4",
|
"rxjs": "^6.5.4",
|
||||||
"sass": "^1.42.1",
|
"sass": "^1.42.1",
|
||||||
"sass-loader": "^12.2.0",
|
"sass-loader": "^12.2.0",
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export async function* devServerExecutor(
|
|||||||
const buildOptions = normalizeOptions(
|
const buildOptions = normalizeOptions(
|
||||||
getBuildOptions(serveOptions, context),
|
getBuildOptions(serveOptions, context),
|
||||||
context.root,
|
context.root,
|
||||||
|
projectRoot,
|
||||||
sourceRoot
|
sourceRoot
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -20,13 +20,7 @@ export function getDevServerConfig(
|
|||||||
const workspaceRoot = context.root;
|
const workspaceRoot = context.root;
|
||||||
const { root: projectRoot, sourceRoot } =
|
const { root: projectRoot, sourceRoot } =
|
||||||
context.projectsConfigurations.projects[context.projectName];
|
context.projectsConfigurations.projects[context.projectName];
|
||||||
const webpackConfig = getWebpackConfig(
|
const webpackConfig = getWebpackConfig(context, buildOptions);
|
||||||
context,
|
|
||||||
buildOptions,
|
|
||||||
typeof buildOptions.optimization === 'boolean'
|
|
||||||
? buildOptions.optimization
|
|
||||||
: buildOptions.optimization?.scripts
|
|
||||||
);
|
|
||||||
|
|
||||||
(webpackConfig as any).devServer = getDevServerPartial(
|
(webpackConfig as any).devServer = getDevServerPartial(
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
|
|||||||
@ -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 type { Configuration } from 'webpack';
|
||||||
|
import { ExecutorContext } from '@nrwl/devkit';
|
||||||
|
|
||||||
import { NormalizedWebpackExecutorOptions } from '../schema';
|
import { NormalizedWebpackExecutorOptions } from '../schema';
|
||||||
|
import { withNx } from '../../../utils/with-nx';
|
||||||
// TODO(jack): These should be inlined in a single function so it is easier to understand
|
import { withWeb } from '../../../utils/with-web';
|
||||||
import { getBaseWebpackPartial } from '../../../utils/config';
|
import { composePlugins } from '@nrwl/webpack';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GetWebpackConfigOverrides {
|
interface GetWebpackConfigOverrides {
|
||||||
root: string;
|
root: string;
|
||||||
@ -30,288 +12,14 @@ interface GetWebpackConfigOverrides {
|
|||||||
configuration?: string;
|
configuration?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use withNx, withWeb, or withReact */
|
||||||
|
// TODO(jack): Remove in Nx 16
|
||||||
export function getWebpackConfig(
|
export function getWebpackConfig(
|
||||||
context: ExecutorContext,
|
context: ExecutorContext,
|
||||||
options: NormalizedWebpackExecutorOptions,
|
options: NormalizedWebpackExecutorOptions
|
||||||
isScriptOptimizeOn?: boolean,
|
|
||||||
overrides?: GetWebpackConfigOverrides
|
|
||||||
): Configuration {
|
): Configuration {
|
||||||
const tsConfig = readTsConfig(options.tsConfig);
|
const config: Configuration = {};
|
||||||
const workspaceRoot = context.root;
|
const configure =
|
||||||
|
options.target === 'node' ? withNx() : composePlugins(withNx(), withWeb());
|
||||||
let sourceRoot: string;
|
return configure(config, { options, context });
|
||||||
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 = <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
|
|
||||||
): 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 <script type="module"> but not <script nomodule>.
|
|
||||||
// Need to patch it up so the browser doesn't load both sets.
|
|
||||||
config.entry.polyfills = [
|
|
||||||
require.resolve('@nrwl/webpack/src/utils/webpack/safari-nomodule.js'),
|
|
||||||
...(polyfills ? [polyfills] : []),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
if (polyfills) {
|
|
||||||
config.entry.polyfills = [polyfills];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCSSModuleLocalIdent(
|
|
||||||
context,
|
|
||||||
localIdentName,
|
|
||||||
localName,
|
|
||||||
options
|
|
||||||
) {
|
|
||||||
// Use the filename or folder name, based on some uses the index.js / index.module.(css|scss|sass) project style
|
|
||||||
const fileNameOrFolder = context.resourcePath.match(
|
|
||||||
/index\.module\.(css|scss|sass|styl)$/
|
|
||||||
)
|
|
||||||
? '[folder]'
|
|
||||||
: '[name]';
|
|
||||||
// Create a hash based on a the file location and class name. Will be unique across a project, and close to globally unique.
|
|
||||||
const hash = getHashDigest(
|
|
||||||
posix.relative(context.rootContext, context.resourcePath) + localName,
|
|
||||||
'md5',
|
|
||||||
'base64',
|
|
||||||
5
|
|
||||||
);
|
|
||||||
// Use loaderUtils to find the file or folder name
|
|
||||||
const className = interpolateName(
|
|
||||||
context,
|
|
||||||
`${fileNameOrFolder}_${localName}__${hash}`,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
// Remove the .module that appears in every classname when based on the file and replace all "." with "_".
|
|
||||||
return className.replace('.module_', '_').replace(/\./g, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertBuildOptions(
|
|
||||||
buildOptions: NormalizedWebpackExecutorOptions
|
|
||||||
): any {
|
|
||||||
const options = buildOptions as any;
|
|
||||||
return {
|
|
||||||
...options,
|
|
||||||
buildOptimizer: options.optimization,
|
|
||||||
forkTypeChecker: false,
|
|
||||||
lazyModules: [] as string[],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,11 +12,13 @@ import type {
|
|||||||
export function normalizeOptions(
|
export function normalizeOptions(
|
||||||
options: WebpackExecutorOptions,
|
options: WebpackExecutorOptions,
|
||||||
root: string,
|
root: string,
|
||||||
|
projectRoot: string,
|
||||||
sourceRoot: string
|
sourceRoot: string
|
||||||
): NormalizedWebpackExecutorOptions {
|
): NormalizedWebpackExecutorOptions {
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
root,
|
root,
|
||||||
|
projectRoot,
|
||||||
sourceRoot,
|
sourceRoot,
|
||||||
target: options.target ?? 'web',
|
target: options.target ?? 'web',
|
||||||
main: resolve(root, options.main),
|
main: resolve(root, options.main),
|
||||||
|
|||||||
@ -85,5 +85,6 @@ export interface NormalizedWebpackExecutorOptions
|
|||||||
extends WebpackExecutorOptions {
|
extends WebpackExecutorOptions {
|
||||||
assets?: AssetGlobPattern[];
|
assets?: AssetGlobPattern[];
|
||||||
root?: string;
|
root?: string;
|
||||||
|
projectRoot?: string;
|
||||||
sourceRoot?: string;
|
sourceRoot?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@ async function getWebpackConfigs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = getWebpackConfig(context, options, isScriptOptimizeOn);
|
const config = getWebpackConfig(context, options);
|
||||||
|
|
||||||
if (customWebpack) {
|
if (customWebpack) {
|
||||||
return await customWebpack(config, {
|
return await customWebpack(config, {
|
||||||
@ -81,7 +81,12 @@ export async function* webpackExecutor(
|
|||||||
): AsyncGenerator<WebpackExecutorEvent, WebpackExecutorEvent, undefined> {
|
): AsyncGenerator<WebpackExecutorEvent, WebpackExecutorEvent, undefined> {
|
||||||
const metadata = context.projectsConfigurations.projects[context.projectName];
|
const metadata = context.projectsConfigurations.projects[context.projectName];
|
||||||
const sourceRoot = metadata.sourceRoot;
|
const sourceRoot = metadata.sourceRoot;
|
||||||
const options = normalizeOptions(_options, context.root, sourceRoot);
|
const options = normalizeOptions(
|
||||||
|
_options,
|
||||||
|
context.root,
|
||||||
|
metadata.root,
|
||||||
|
sourceRoot
|
||||||
|
);
|
||||||
const isScriptOptimizeOn =
|
const isScriptOptimizeOn =
|
||||||
typeof options.optimization === 'boolean'
|
typeof options.optimization === 'boolean'
|
||||||
? options.optimization
|
? options.optimization
|
||||||
|
|||||||
10
packages/webpack/src/plugins/stats-json-plugin.ts
Normal file
10
packages/webpack/src/plugins/stats-json-plugin.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Compiler, sources } from 'webpack';
|
||||||
|
|
||||||
|
export class StatsJsonPlugin {
|
||||||
|
apply(compiler: Compiler) {
|
||||||
|
compiler.hooks.emit.tap('angular-cli-stats', (compilation) => {
|
||||||
|
const data = JSON.stringify(compilation.getStats().toJson('verbose'));
|
||||||
|
compilation.assets[`stats.json`] = new sources.RawSource(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,427 +1,39 @@
|
|||||||
import { join, parse } from 'path';
|
import { Configuration } from 'webpack';
|
||||||
import * as webpack from 'webpack';
|
|
||||||
import { Configuration, WebpackPluginInstance } from 'webpack';
|
|
||||||
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
|
||||||
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
|
|
||||||
import { getOutputHashFormat } from './hash-format';
|
|
||||||
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
|
||||||
import { ExecutorContext } from '@nrwl/devkit';
|
import { ExecutorContext } from '@nrwl/devkit';
|
||||||
import { loadTsTransformers } from '@nrwl/js';
|
|
||||||
|
|
||||||
import {
|
import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema';
|
||||||
AssetGlobPattern,
|
import { withNx } from './with-nx';
|
||||||
NormalizedWebpackExecutorOptions,
|
import { withWeb } from './with-web';
|
||||||
} from '../executors/webpack/schema';
|
|
||||||
import { GeneratePackageJsonWebpackPlugin } from './generate-package-json-webpack-plugin';
|
|
||||||
import nodeExternals = require('webpack-node-externals');
|
|
||||||
import TerserPlugin = require('terser-webpack-plugin');
|
|
||||||
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
|
||||||
|
|
||||||
const IGNORED_WEBPACK_WARNINGS = [
|
|
||||||
/The comment file/i,
|
|
||||||
/could not find any license/i,
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface InternalBuildOptions {
|
|
||||||
isScriptOptimizeOn?: boolean;
|
|
||||||
emitDecoratorMetadata?: boolean;
|
|
||||||
configuration?: string;
|
|
||||||
skipTypeCheck?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
|
||||||
|
|
||||||
|
/** @deprecated use withNx and withWeb plugins directly */
|
||||||
export function getBaseWebpackPartial(
|
export function getBaseWebpackPartial(
|
||||||
options: NormalizedWebpackExecutorOptions,
|
options: NormalizedWebpackExecutorOptions,
|
||||||
internalOptions: InternalBuildOptions,
|
|
||||||
context?: ExecutorContext
|
context?: ExecutorContext
|
||||||
): Configuration {
|
): Configuration {
|
||||||
// If the function is called directly and not through `@nrwl/webpack:webpack` then this target may not be set.
|
const config: Configuration = {};
|
||||||
options.target ??= 'web';
|
const configure = composePlugins(withNx(), withWeb());
|
||||||
|
return configure(config, { options, context });
|
||||||
const mainFields = ['es2015', 'module', 'main'];
|
|
||||||
const hashFormat = getOutputHashFormat(options.outputHashing);
|
|
||||||
const filename = internalOptions.isScriptOptimizeOn
|
|
||||||
? `[name]${hashFormat.script}.js`
|
|
||||||
: '[name].js';
|
|
||||||
const chunkFilename = internalOptions.isScriptOptimizeOn
|
|
||||||
? `[name]${hashFormat.chunk}.js`
|
|
||||||
: '[name].js';
|
|
||||||
const mode = internalOptions.isScriptOptimizeOn
|
|
||||||
? 'production'
|
|
||||||
: 'development';
|
|
||||||
|
|
||||||
let mainEntry = 'main';
|
|
||||||
if (options.outputFileName) {
|
|
||||||
mainEntry = parse(options.outputFileName).name;
|
|
||||||
}
|
|
||||||
const additionalEntryPoints =
|
|
||||||
options.additionalEntryPoints?.reduce(
|
|
||||||
(obj, current) => ({
|
|
||||||
...obj,
|
|
||||||
[current.entryName]: current.entryPath,
|
|
||||||
}),
|
|
||||||
{} as { [entryName: string]: string }
|
|
||||||
) ?? {};
|
|
||||||
|
|
||||||
const webpackConfig: Configuration = {
|
|
||||||
target: options.target,
|
|
||||||
entry: {
|
|
||||||
[mainEntry]: [options.main],
|
|
||||||
...additionalEntryPoints,
|
|
||||||
},
|
|
||||||
devtool:
|
|
||||||
options.sourceMap === 'hidden'
|
|
||||||
? 'hidden-source-map'
|
|
||||||
: options.sourceMap
|
|
||||||
? 'source-map'
|
|
||||||
: false,
|
|
||||||
mode,
|
|
||||||
output: {
|
|
||||||
path: options.outputPath,
|
|
||||||
filename,
|
|
||||||
chunkFilename,
|
|
||||||
hashFunction: 'xxhash64',
|
|
||||||
// Disabled for performance
|
|
||||||
pathinfo: false,
|
|
||||||
scriptType: 'module',
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
// Enabled for performance
|
|
||||||
unsafeCache: true,
|
|
||||||
rules: [
|
|
||||||
options.target === 'web' && {
|
|
||||||
test: /\.(bmp|png|jpe?g|gif|webp|avif)$/,
|
|
||||||
type: 'asset',
|
|
||||||
parser: {
|
|
||||||
dataUrlCondition: {
|
|
||||||
maxSize: 10_000, // 10 kB
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// There's an issue resolving paths without fully specified extensions
|
|
||||||
// See: https://github.com/graphql/graphql-js/issues/2721
|
|
||||||
// TODO(jack): Add a flag to turn this option on like Next.js does via experimental flag.
|
|
||||||
// See: https://github.com/vercel/next.js/pull/29880
|
|
||||||
test: /\.m?jsx?$/,
|
|
||||||
resolve: {
|
|
||||||
fullySpecified: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// There's an issue when using buildable libs and .js files (instead of .ts files),
|
|
||||||
// where the wrong type is used (commonjs vs esm) resulting in export-imports throwing errors.
|
|
||||||
// See: https://github.com/nrwl/nx/issues/10990
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
type: 'javascript/auto',
|
|
||||||
},
|
|
||||||
createLoaderFromCompiler(options, internalOptions),
|
|
||||||
].filter(Boolean),
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions,
|
|
||||||
alias: getAliases(options),
|
|
||||||
plugins: [
|
|
||||||
new TsconfigPathsPlugin({
|
|
||||||
configFile: options.tsConfig,
|
|
||||||
extensions,
|
|
||||||
mainFields,
|
|
||||||
}) as never, // TODO: Remove never type when 'tsconfig-paths-webpack-plugin' types fixed
|
|
||||||
],
|
|
||||||
mainFields,
|
|
||||||
},
|
|
||||||
performance: {
|
|
||||||
hints: false,
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
watch: options.watch,
|
|
||||||
watchOptions: {
|
|
||||||
poll: options.poll,
|
|
||||||
},
|
|
||||||
stats: getStatsConfig(options),
|
|
||||||
ignoreWarnings: [
|
|
||||||
(x) =>
|
|
||||||
IGNORED_WEBPACK_WARNINGS.some((r) =>
|
|
||||||
typeof x === 'string' ? r.test(x) : r.test(x.message)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
experiments: {
|
|
||||||
cacheUnaffected: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.target === 'node') {
|
|
||||||
webpackConfig.output.libraryTarget = 'commonjs';
|
|
||||||
webpackConfig.node = false;
|
|
||||||
|
|
||||||
// could be an object { scripts: boolean; styles: boolean }
|
|
||||||
if (internalOptions.isScriptOptimizeOn) {
|
|
||||||
webpackConfig.optimization = {
|
|
||||||
minimize: true,
|
|
||||||
minimizer: [
|
|
||||||
new TerserPlugin({
|
|
||||||
terserOptions: {
|
|
||||||
mangle: false,
|
|
||||||
keep_classnames: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
concatenateModules: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
webpackConfig.plugins.push(
|
|
||||||
new webpack.DefinePlugin(getClientEnvironment(mode).stringified)
|
|
||||||
);
|
|
||||||
webpackConfig.optimization ??= {};
|
|
||||||
webpackConfig.optimization.nodeEnv = process.env.NODE_ENV ?? mode;
|
|
||||||
|
|
||||||
if (internalOptions.isScriptOptimizeOn) {
|
|
||||||
// Always check sideEffects field in package.json for tree-shaking to work.
|
|
||||||
webpackConfig.optimization.sideEffects = true;
|
|
||||||
|
|
||||||
if (options.compiler !== 'swc') {
|
|
||||||
webpackConfig.optimization = {
|
|
||||||
sideEffects: true,
|
|
||||||
minimizer: [
|
|
||||||
new TerserPlugin({
|
|
||||||
parallel: true,
|
|
||||||
terserOptions: {
|
|
||||||
ecma: 2020,
|
|
||||||
safari10: true,
|
|
||||||
output: {
|
|
||||||
ascii_only: true,
|
|
||||||
comments: false,
|
|
||||||
webkit: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
runtimeChunk: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const extraPlugins: WebpackPluginInstance[] = [];
|
|
||||||
|
|
||||||
if (!internalOptions.skipTypeCheck) {
|
|
||||||
extraPlugins.push(
|
|
||||||
new ForkTsCheckerWebpackPlugin({
|
|
||||||
typescript: {
|
|
||||||
configFile: options.tsConfig,
|
|
||||||
memoryLimit: options.memoryLimit || 2018,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.progress) {
|
|
||||||
extraPlugins.push(new webpack.ProgressPlugin());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO LicenseWebpackPlugin needs a PR for proper typing
|
|
||||||
if (options.extractLicenses) {
|
|
||||||
extraPlugins.push(
|
|
||||||
new LicenseWebpackPlugin({
|
|
||||||
stats: {
|
|
||||||
errors: false,
|
|
||||||
},
|
|
||||||
perChunkOutput: false,
|
|
||||||
outputFilename: `3rdpartylicenses.txt`,
|
|
||||||
}) as unknown as WebpackPluginInstance
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(options.assets) && options.assets.length > 0) {
|
|
||||||
extraPlugins.push(createCopyPlugin(options.assets));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
options.target === 'node' &&
|
|
||||||
options.externalDependencies === 'all' &&
|
|
||||||
context
|
|
||||||
) {
|
|
||||||
const modulesDir = `${context.root}/node_modules`;
|
|
||||||
webpackConfig.externals = [nodeExternals({ modulesDir })];
|
|
||||||
} else if (Array.isArray(options.externalDependencies)) {
|
|
||||||
webpackConfig.externals = [
|
|
||||||
function (context, callback: Function) {
|
|
||||||
if (options.externalDependencies.includes(context.request)) {
|
|
||||||
// not bundled
|
|
||||||
return callback(null, `commonjs ${context.request}`);
|
|
||||||
}
|
|
||||||
// bundled
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.generatePackageJson && context) {
|
|
||||||
extraPlugins.push(new GeneratePackageJsonWebpackPlugin(context, options));
|
|
||||||
}
|
|
||||||
|
|
||||||
webpackConfig.plugins = [...webpackConfig.plugins, ...extraPlugins];
|
|
||||||
|
|
||||||
return webpackConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAliases(options: NormalizedWebpackExecutorOptions): {
|
export type NxWebpackPlugin = (
|
||||||
[key: string]: string;
|
config: Configuration,
|
||||||
} {
|
ctx?: {
|
||||||
return options.fileReplacements.reduce(
|
options: NormalizedWebpackExecutorOptions;
|
||||||
(aliases, replacement) => ({
|
context?: ExecutorContext;
|
||||||
...aliases,
|
|
||||||
[replacement.replace]: replacement.with,
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStatsConfig(options: NormalizedWebpackExecutorOptions) {
|
|
||||||
return {
|
|
||||||
hash: true,
|
|
||||||
timings: false,
|
|
||||||
cached: false,
|
|
||||||
cachedAssets: false,
|
|
||||||
modules: false,
|
|
||||||
warnings: true,
|
|
||||||
errors: true,
|
|
||||||
colors: !options.verbose && !options.statsJson,
|
|
||||||
chunks: !options.verbose,
|
|
||||||
assets: !!options.verbose,
|
|
||||||
chunkOrigins: !!options.verbose,
|
|
||||||
chunkModules: !!options.verbose,
|
|
||||||
children: !!options.verbose,
|
|
||||||
reasons: !!options.verbose,
|
|
||||||
version: !!options.verbose,
|
|
||||||
errorDetails: !!options.verbose,
|
|
||||||
moduleTrace: !!options.verbose,
|
|
||||||
usedExports: !!options.verbose,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getClientEnvironment(mode) {
|
|
||||||
// Grab NODE_ENV and NX_* environment variables and prepare them to be
|
|
||||||
// injected into the application via DefinePlugin in webpack configuration.
|
|
||||||
const NX_APP = /^NX_/i;
|
|
||||||
|
|
||||||
const raw = Object.keys(process.env)
|
|
||||||
.filter((key) => NX_APP.test(key))
|
|
||||||
.reduce(
|
|
||||||
(env, key) => {
|
|
||||||
env[key] = process.env[key];
|
|
||||||
return env;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Useful for determining whether we’re running in production mode.
|
|
||||||
NODE_ENV: process.env.NODE_ENV || mode,
|
|
||||||
}
|
}
|
||||||
);
|
) => Configuration;
|
||||||
|
|
||||||
// Stringify all values so we can feed into webpack DefinePlugin
|
export function composePlugins(...plugins: NxWebpackPlugin[]) {
|
||||||
const stringified = {
|
return function combined(
|
||||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
config: Configuration,
|
||||||
env[key] = JSON.stringify(raw[key]);
|
ctx?: {
|
||||||
return env;
|
options: NormalizedWebpackExecutorOptions;
|
||||||
}, {}),
|
context?: ExecutorContext;
|
||||||
};
|
|
||||||
|
|
||||||
return { stringified };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createCopyPlugin(assets: AssetGlobPattern[]) {
|
|
||||||
return new CopyWebpackPlugin({
|
|
||||||
patterns: assets.map((asset) => {
|
|
||||||
return {
|
|
||||||
context: asset.input,
|
|
||||||
// Now we remove starting slash to make Webpack place it from the output root.
|
|
||||||
to: asset.output,
|
|
||||||
from: asset.glob,
|
|
||||||
globOptions: {
|
|
||||||
ignore: [
|
|
||||||
'.gitkeep',
|
|
||||||
'**/.DS_Store',
|
|
||||||
'**/Thumbs.db',
|
|
||||||
...(asset.ignore ?? []),
|
|
||||||
],
|
|
||||||
dot: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createLoaderFromCompiler(
|
|
||||||
options: NormalizedWebpackExecutorOptions,
|
|
||||||
extraOptions: InternalBuildOptions
|
|
||||||
) {
|
|
||||||
switch (options.compiler) {
|
|
||||||
case 'swc':
|
|
||||||
return {
|
|
||||||
test: /\.([jt])sx?$/,
|
|
||||||
loader: require.resolve('swc-loader'),
|
|
||||||
exclude: /node_modules/,
|
|
||||||
options: {
|
|
||||||
jsc: {
|
|
||||||
parser: {
|
|
||||||
syntax: 'typescript',
|
|
||||||
decorators: true,
|
|
||||||
tsx: true,
|
|
||||||
},
|
|
||||||
transform: {
|
|
||||||
react: {
|
|
||||||
runtime: 'automatic',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
loose: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'tsc':
|
|
||||||
const { compilerPluginHooks, hasPlugin } = loadTsTransformers(
|
|
||||||
options.transformers
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
test: /\.([jt])sx?$/,
|
|
||||||
loader: require.resolve(`ts-loader`),
|
|
||||||
exclude: /node_modules/,
|
|
||||||
options: {
|
|
||||||
configFile: options.tsConfig,
|
|
||||||
transpileOnly: !hasPlugin,
|
|
||||||
// https://github.com/TypeStrong/ts-loader/pull/685
|
|
||||||
experimentalWatchApi: true,
|
|
||||||
getCustomTransformers: (program) => ({
|
|
||||||
before: compilerPluginHooks.beforeHooks.map((hook) =>
|
|
||||||
hook(program)
|
|
||||||
),
|
|
||||||
after: compilerPluginHooks.afterHooks.map((hook) => hook(program)),
|
|
||||||
afterDeclarations: compilerPluginHooks.afterDeclarationsHooks.map(
|
|
||||||
(hook) => hook(program)
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'babel':
|
|
||||||
return {
|
|
||||||
test: /\.([jt])sx?$/,
|
|
||||||
loader: join(__dirname, 'web-babel-loader'),
|
|
||||||
exclude: /node_modules/,
|
|
||||||
options: {
|
|
||||||
rootMode: 'upward',
|
|
||||||
cwd: join(options.root, options.sourceRoot),
|
|
||||||
emitDecoratorMetadata: extraOptions.emitDecoratorMetadata,
|
|
||||||
isModern: true,
|
|
||||||
envName: extraOptions.isScriptOptimizeOn
|
|
||||||
? 'production'
|
|
||||||
: extraOptions.configuration,
|
|
||||||
babelrc: true,
|
|
||||||
cacheDirectory: true,
|
|
||||||
cacheCompression: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
): Configuration {
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
config = plugin(config, ctx);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
24
packages/webpack/src/utils/create-copy-plugin.ts
Normal file
24
packages/webpack/src/utils/create-copy-plugin.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||||
|
import { AssetGlobPattern } from '../executors/webpack/schema';
|
||||||
|
|
||||||
|
export function createCopyPlugin(assets: AssetGlobPattern[]) {
|
||||||
|
return new CopyWebpackPlugin({
|
||||||
|
patterns: assets.map((asset) => {
|
||||||
|
return {
|
||||||
|
context: asset.input,
|
||||||
|
// Now we remove starting slash to make Webpack place it from the output root.
|
||||||
|
to: asset.output,
|
||||||
|
from: asset.glob,
|
||||||
|
globOptions: {
|
||||||
|
ignore: [
|
||||||
|
'.gitkeep',
|
||||||
|
'**/.DS_Store',
|
||||||
|
'**/Thumbs.db',
|
||||||
|
...(asset.ignore ?? []),
|
||||||
|
],
|
||||||
|
dot: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
30
packages/webpack/src/utils/get-client-environment.ts
Normal file
30
packages/webpack/src/utils/get-client-environment.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export function getClientEnvironment(mode?: string) {
|
||||||
|
// Grab NODE_ENV and NX_* environment variables and prepare them to be
|
||||||
|
// injected into the application via DefinePlugin in webpack configuration.
|
||||||
|
const NX_APP = /^NX_/i;
|
||||||
|
|
||||||
|
const raw = Object.keys(process.env)
|
||||||
|
.filter((key) => NX_APP.test(key))
|
||||||
|
.reduce(
|
||||||
|
(env, key) => {
|
||||||
|
env[key] = process.env[key];
|
||||||
|
return env;
|
||||||
|
},
|
||||||
|
// If mode is undefined or is dev or prod then webpack already defines this variable for us.
|
||||||
|
!mode || mode === 'development' || mode === 'production'
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stringify all values so we can feed into webpack DefinePlugin
|
||||||
|
const stringified = {
|
||||||
|
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||||
|
env[key] = JSON.stringify(raw[key]);
|
||||||
|
return env;
|
||||||
|
}, {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { stringified };
|
||||||
|
}
|
||||||
31
packages/webpack/src/utils/get-css-module-local-ident.ts
Normal file
31
packages/webpack/src/utils/get-css-module-local-ident.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { posix } from 'path';
|
||||||
|
import { getHashDigest, interpolateName } from 'loader-utils';
|
||||||
|
|
||||||
|
export function getCSSModuleLocalIdent(
|
||||||
|
ctx,
|
||||||
|
localIdentName,
|
||||||
|
localName,
|
||||||
|
options
|
||||||
|
) {
|
||||||
|
// Use the filename or folder name, based on some uses the index.js / index.module.(css|scss|sass) project style
|
||||||
|
const fileNameOrFolder = ctx.resourcePath.match(
|
||||||
|
/index\.module\.(css|scss|sass|styl)$/
|
||||||
|
)
|
||||||
|
? '[folder]'
|
||||||
|
: '[name]';
|
||||||
|
// Create a hash based on a the file location and class name. Will be unique across a project, and close to globally unique.
|
||||||
|
const hash = getHashDigest(
|
||||||
|
posix.relative(ctx.rootContext, ctx.resourcePath) + localName,
|
||||||
|
'md5',
|
||||||
|
'base64',
|
||||||
|
5
|
||||||
|
);
|
||||||
|
// Use loaderUtils to find the file or folder name
|
||||||
|
const className = interpolateName(
|
||||||
|
ctx,
|
||||||
|
`${fileNameOrFolder}_${localName}__${hash}`,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
// Remove the .module that appears in every classname when based on the file and replace all "." with "_".
|
||||||
|
return className.replace('.module_', '_').replace(/\./g, '_');
|
||||||
|
}
|
||||||
@ -4,10 +4,9 @@ export interface ExtraEntryPointClass {
|
|||||||
bundleName?: string;
|
bundleName?: string;
|
||||||
inject?: boolean;
|
inject?: boolean;
|
||||||
input: string;
|
input: string;
|
||||||
lazy?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NormalizedEntryPoint = Required<Omit<ExtraEntryPointClass, 'lazy'>>;
|
export type NormalizedEntryPoint = Required<ExtraEntryPointClass>;
|
||||||
|
|
||||||
export interface EmittedFile {
|
export interface EmittedFile {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
import { basename } from 'path';
|
|
||||||
import { normalizePath } from '@nrwl/devkit';
|
|
||||||
|
|
||||||
import { ExtraEntryPoint, NormalizedEntryPoint } from '../models';
|
import { ExtraEntryPoint, NormalizedEntryPoint } from '../models';
|
||||||
|
|
||||||
export function normalizeExtraEntryPoints(
|
export function normalizeExtraEntryPoints(
|
||||||
@ -16,24 +13,16 @@ export function normalizeExtraEntryPoints(
|
|||||||
bundleName: defaultBundleName,
|
bundleName: defaultBundleName,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const { lazy, inject = true, ...newEntry } = entry;
|
const { inject = true, ...newEntry } = entry;
|
||||||
const injectNormalized = entry.lazy !== undefined ? !entry.lazy : inject;
|
|
||||||
let bundleName;
|
let bundleName;
|
||||||
|
|
||||||
if (entry.bundleName) {
|
if (entry.bundleName) {
|
||||||
bundleName = entry.bundleName;
|
bundleName = entry.bundleName;
|
||||||
} else if (!injectNormalized) {
|
|
||||||
// Lazy entry points use the file name as bundle name.
|
|
||||||
bundleName = basename(
|
|
||||||
normalizePath(
|
|
||||||
entry.input.replace(/\.(js|css|scss|sass|less|styl)$/i, '')
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
bundleName = defaultBundleName;
|
bundleName = defaultBundleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizedEntry = { ...newEntry, inject: injectNormalized, bundleName };
|
normalizedEntry = { ...newEntry, bundleName };
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizedEntry;
|
return normalizedEntry;
|
||||||
|
|||||||
@ -13,9 +13,7 @@ export function generateEntryPoints(appConfig: {
|
|||||||
const entryPoints = normalizeExtraEntryPoints(
|
const entryPoints = normalizeExtraEntryPoints(
|
||||||
extraEntryPoints,
|
extraEntryPoints,
|
||||||
defaultBundleName
|
defaultBundleName
|
||||||
)
|
).map((entry) => entry.bundleName);
|
||||||
.filter((entry) => entry.inject)
|
|
||||||
.map((entry) => entry.bundleName);
|
|
||||||
|
|
||||||
// remove duplicates
|
// remove duplicates
|
||||||
return [...new Set(entryPoints)];
|
return [...new Set(entryPoints)];
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
|
||||||
import { ids } from 'webpack';
|
|
||||||
import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
|
|
||||||
import { CreateWebpackConfigOptions } from '../../models';
|
|
||||||
import CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
|
||||||
|
|
||||||
export function getBrowserConfig(wco: CreateWebpackConfigOptions) {
|
|
||||||
const { buildOptions } = wco;
|
|
||||||
const extraPlugins = [];
|
|
||||||
|
|
||||||
const stylesOptimization =
|
|
||||||
typeof buildOptions.optimization === 'object'
|
|
||||||
? buildOptions.optimization.styles
|
|
||||||
: buildOptions.optimization;
|
|
||||||
|
|
||||||
if (buildOptions.subresourceIntegrity) {
|
|
||||||
extraPlugins.push(new SubresourceIntegrityPlugin());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildOptions.extractLicenses) {
|
|
||||||
extraPlugins.push(
|
|
||||||
new LicenseWebpackPlugin({
|
|
||||||
stats: {
|
|
||||||
warnings: false,
|
|
||||||
errors: false,
|
|
||||||
},
|
|
||||||
perChunkOutput: false,
|
|
||||||
outputFilename: `3rdpartylicenses.txt`,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const extraMinimizers = [];
|
|
||||||
if (stylesOptimization) {
|
|
||||||
extraMinimizers.push(
|
|
||||||
new CssMinimizerPlugin({
|
|
||||||
test: /\.(?:css|scss|sass|less|styl)$/,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
resolve: {
|
|
||||||
mainFields: ['browser', 'module', 'main'],
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
crossOriginLoading: buildOptions.subresourceIntegrity
|
|
||||||
? ('anonymous' as const)
|
|
||||||
: (false as const),
|
|
||||||
},
|
|
||||||
// context needs to be set for babel to pick up correct babelrc
|
|
||||||
context: wco.projectRoot,
|
|
||||||
optimization: {
|
|
||||||
minimizer: [new ids.HashedModuleIdsPlugin(), ...extraMinimizers],
|
|
||||||
emitOnErrors: false,
|
|
||||||
moduleIds: 'deterministic' as const,
|
|
||||||
runtimeChunk: buildOptions.runtimeChunk ? ('single' as const) : false,
|
|
||||||
splitChunks: {
|
|
||||||
maxAsyncRequests: Infinity,
|
|
||||||
cacheGroups: {
|
|
||||||
default: !!buildOptions.commonChunk && {
|
|
||||||
chunks: 'async' as const,
|
|
||||||
minChunks: 2,
|
|
||||||
priority: 10,
|
|
||||||
},
|
|
||||||
common: !!buildOptions.commonChunk && {
|
|
||||||
name: 'common',
|
|
||||||
chunks: 'async' as const,
|
|
||||||
minChunks: 2,
|
|
||||||
enforce: true,
|
|
||||||
priority: 5,
|
|
||||||
},
|
|
||||||
vendors: false as const,
|
|
||||||
vendor: !!buildOptions.vendorChunk && {
|
|
||||||
name: 'vendor',
|
|
||||||
chunks: (chunk) => chunk.name === 'main',
|
|
||||||
enforce: true,
|
|
||||||
test: /[\\/]node_modules[\\/]/,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: extraPlugins,
|
|
||||||
node: false as false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,191 +0,0 @@
|
|||||||
import { basename, resolve } from 'path';
|
|
||||||
import type { Compiler, Configuration } from 'webpack';
|
|
||||||
import { ProgressPlugin, sources } from 'webpack';
|
|
||||||
|
|
||||||
import { normalizeExtraEntryPoints } from '../normalize-entry';
|
|
||||||
import { ScriptsWebpackPlugin } from '../plugins/scripts-webpack-plugin';
|
|
||||||
import { getOutputHashFormat } from '../../hash-format';
|
|
||||||
import { findAllNodeModules, findUp } from '../../fs';
|
|
||||||
import type { CreateWebpackConfigOptions } from '../../models';
|
|
||||||
|
|
||||||
export function getCommonConfig(
|
|
||||||
wco: CreateWebpackConfigOptions
|
|
||||||
): Configuration {
|
|
||||||
const { root, projectRoot, sourceRoot, buildOptions } = wco;
|
|
||||||
|
|
||||||
let stylesOptimization: boolean;
|
|
||||||
let scriptsOptimization: boolean;
|
|
||||||
if (typeof buildOptions.optimization === 'object') {
|
|
||||||
scriptsOptimization = buildOptions.optimization.scripts;
|
|
||||||
stylesOptimization = buildOptions.optimization.styles;
|
|
||||||
} else {
|
|
||||||
scriptsOptimization = stylesOptimization = !!buildOptions.optimization;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeModules = findUp('node_modules', projectRoot);
|
|
||||||
if (!nodeModules) {
|
|
||||||
throw new Error('Cannot locate node_modules directory.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// tslint:disable-next-line:no-any
|
|
||||||
const extraPlugins: any[] = [];
|
|
||||||
const entryPoints: { [key: string]: string[] } = {};
|
|
||||||
|
|
||||||
if (buildOptions.main) {
|
|
||||||
entryPoints['main'] = [resolve(root, buildOptions.main)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildOptions.polyfills) {
|
|
||||||
entryPoints['polyfills'] = [
|
|
||||||
...(entryPoints['polyfills'] || []),
|
|
||||||
resolve(root, buildOptions.polyfills),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine hashing format
|
|
||||||
const hashFormat = getOutputHashFormat(buildOptions.outputHashing || 'none');
|
|
||||||
|
|
||||||
// process global scripts
|
|
||||||
const globalScriptsByBundleName = normalizeExtraEntryPoints(
|
|
||||||
buildOptions.scripts || [],
|
|
||||||
'scripts'
|
|
||||||
).reduce(
|
|
||||||
(
|
|
||||||
prev: { bundleName: string; paths: string[]; inject: boolean }[],
|
|
||||||
curr
|
|
||||||
) => {
|
|
||||||
const bundleName = curr.bundleName;
|
|
||||||
const resolvedPath = resolve(root, curr.input);
|
|
||||||
const existingEntry = prev.find((el) => el.bundleName === bundleName);
|
|
||||||
if (existingEntry) {
|
|
||||||
if (existingEntry.inject && !curr.inject) {
|
|
||||||
// All entries have to be lazy for the bundle to be lazy.
|
|
||||||
throw new Error(
|
|
||||||
`The ${curr.bundleName} bundle is mixing injected and non-injected scripts.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
existingEntry.paths.push(resolvedPath);
|
|
||||||
} else {
|
|
||||||
prev.push({
|
|
||||||
bundleName,
|
|
||||||
paths: [resolvedPath],
|
|
||||||
inject: curr.inject,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (globalScriptsByBundleName.length > 0) {
|
|
||||||
// Add a new asset for each entry.
|
|
||||||
globalScriptsByBundleName.forEach((script) => {
|
|
||||||
// Lazy scripts don't get a hash, otherwise they can't be loaded by name.
|
|
||||||
const hash = script.inject ? hashFormat.script : '';
|
|
||||||
const bundleName = script.bundleName;
|
|
||||||
|
|
||||||
extraPlugins.push(
|
|
||||||
new ScriptsWebpackPlugin({
|
|
||||||
name: bundleName,
|
|
||||||
sourceMap: !!buildOptions.sourceMap,
|
|
||||||
filename: `${basename(bundleName)}${hash}.js`,
|
|
||||||
scripts: script.paths,
|
|
||||||
basePath: sourceRoot,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildOptions.progress) {
|
|
||||||
extraPlugins.push(new ProgressPlugin({ profile: buildOptions.verbose }));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Needs source exported from webpack
|
|
||||||
if (buildOptions.statsJson) {
|
|
||||||
extraPlugins.push(
|
|
||||||
new (class {
|
|
||||||
apply(compiler: Compiler) {
|
|
||||||
compiler.hooks.emit.tap('angular-cli-stats', (compilation) => {
|
|
||||||
const data = JSON.stringify(
|
|
||||||
compilation.getStats().toJson('verbose')
|
|
||||||
);
|
|
||||||
compilation.assets[`stats.json`] = new sources.RawSource(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sourceMapUseRule;
|
|
||||||
if (!!buildOptions.sourceMap) {
|
|
||||||
sourceMapUseRule = {
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('source-map-loader'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow loaders to be in a node_modules nested inside the devkit/build-angular package.
|
|
||||||
// This is important in case loaders do not get hoisted.
|
|
||||||
// If this file moves to another location, alter potentialNodeModules as well.
|
|
||||||
const loaderNodeModules = findAllNodeModules(__dirname, projectRoot);
|
|
||||||
loaderNodeModules.unshift('node_modules');
|
|
||||||
|
|
||||||
return {
|
|
||||||
profile: buildOptions.statsJson,
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.tsx', '.mjs', '.js'],
|
|
||||||
symlinks: true,
|
|
||||||
modules: [wco.tsConfig.options.baseUrl || projectRoot, 'node_modules'],
|
|
||||||
},
|
|
||||||
resolveLoader: {
|
|
||||||
modules: loaderNodeModules,
|
|
||||||
},
|
|
||||||
entry: entryPoints,
|
|
||||||
output: {
|
|
||||||
path: resolve(root, buildOptions.outputPath as string),
|
|
||||||
publicPath: buildOptions.deployUrl,
|
|
||||||
},
|
|
||||||
watch: buildOptions.watch,
|
|
||||||
performance: {
|
|
||||||
hints: false,
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
// Show an error for missing exports instead of a warning.
|
|
||||||
strictExportPresence: true,
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
|
|
||||||
loader: require.resolve('file-loader'),
|
|
||||||
options: {
|
|
||||||
name: `[name]${hashFormat.file}.[ext]`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /[\/\\]hot[\/\\]emitter\.js$/,
|
|
||||||
parser: { node: { events: true } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /[\/\\]webpack-dev-server[\/\\]client[\/\\]utils[\/\\]createSocketUrl\.js$/,
|
|
||||||
parser: { node: { querystring: true } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
// Factory files are processed by BO in the rules added in typescript.ts.
|
|
||||||
exclude: /(ngfactory|ngstyle)\.js$/,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
exclude: /(ngfactory|ngstyle)\.js$/,
|
|
||||||
enforce: 'pre',
|
|
||||||
...sourceMapUseRule,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: extraPlugins,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
import * as path from 'path';
|
|
||||||
import { RuleSetRule } from 'webpack';
|
|
||||||
|
|
||||||
import { RemoveEmptyScriptsPlugin } from '../plugins/remove-empty-scripts-plugin';
|
|
||||||
import { getOutputHashFormat } from '../../hash-format';
|
|
||||||
import { PostcssCliResources } from '../plugins/postcss-cli-resources';
|
|
||||||
import { RemoveHashPlugin } from '../plugins/remove-hash-plugin';
|
|
||||||
import { NormalizedWebpackExecutorOptions } from '../../../executors/webpack/schema';
|
|
||||||
import { normalizeExtraEntryPoints } from '../normalize-entry';
|
|
||||||
import MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
||||||
|
|
||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
const postcssImports = require('postcss-import');
|
|
||||||
|
|
||||||
export function getStylesConfig(
|
|
||||||
root: string,
|
|
||||||
buildOptions: NormalizedWebpackExecutorOptions,
|
|
||||||
includePaths: string[]
|
|
||||||
) {
|
|
||||||
const RawCssLoader = require.resolve(
|
|
||||||
path.join(__dirname, '../plugins/raw-css-loader.js')
|
|
||||||
);
|
|
||||||
|
|
||||||
const entryPoints: { [key: string]: string[] } = {};
|
|
||||||
const globalStylePaths: string[] = [];
|
|
||||||
const extraPlugins = [];
|
|
||||||
|
|
||||||
const cssSourceMap = !!buildOptions.sourceMap;
|
|
||||||
|
|
||||||
// Determine hashing format.
|
|
||||||
const hashFormat = getOutputHashFormat(buildOptions.outputHashing as string);
|
|
||||||
|
|
||||||
const postcssOptionsCreator = (sourceMap: boolean) => {
|
|
||||||
return (loader) => ({
|
|
||||||
map: sourceMap && {
|
|
||||||
inline: true,
|
|
||||||
annotation: false,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
postcssImports({
|
|
||||||
addModulesDirectories: includePaths,
|
|
||||||
resolve: (url: string) => (url.startsWith('~') ? url.slice(1) : url),
|
|
||||||
}),
|
|
||||||
PostcssCliResources({
|
|
||||||
baseHref: buildOptions.baseHref,
|
|
||||||
deployUrl: buildOptions.deployUrl,
|
|
||||||
loader,
|
|
||||||
filename: `[name]${hashFormat.file}.[ext]`,
|
|
||||||
}),
|
|
||||||
autoprefixer(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let lessPathOptions: { paths?: string[] } = {};
|
|
||||||
|
|
||||||
if (includePaths.length > 0) {
|
|
||||||
lessPathOptions = {
|
|
||||||
paths: includePaths,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process global styles.
|
|
||||||
if (buildOptions.styles.length > 0) {
|
|
||||||
const chunkNames: string[] = [];
|
|
||||||
|
|
||||||
normalizeExtraEntryPoints(buildOptions.styles, 'styles').forEach(
|
|
||||||
(style) => {
|
|
||||||
const resolvedPath = path.resolve(root, style.input);
|
|
||||||
// Add style entry points.
|
|
||||||
if (entryPoints[style.bundleName]) {
|
|
||||||
entryPoints[style.bundleName].push(resolvedPath);
|
|
||||||
} else {
|
|
||||||
entryPoints[style.bundleName] = [resolvedPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add non injected styles to the list.
|
|
||||||
if (!style.inject) {
|
|
||||||
chunkNames.push(style.bundleName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add global css paths.
|
|
||||||
globalStylePaths.push(resolvedPath);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (chunkNames.length > 0) {
|
|
||||||
// Add plugin to remove hashes from lazy styles.
|
|
||||||
extraPlugins.push(new RemoveHashPlugin({ chunkNames, hashFormat }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set base rules to derive final rules from
|
|
||||||
const baseRules: RuleSetRule[] = [
|
|
||||||
{ test: /\.css$/, use: [] },
|
|
||||||
{
|
|
||||||
test: /\.scss$|\.sass$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('sass-loader'),
|
|
||||||
options: {
|
|
||||||
implementation: require('sass'),
|
|
||||||
sourceMap: cssSourceMap,
|
|
||||||
sassOptions: {
|
|
||||||
fiber: false,
|
|
||||||
// bootstrap-sass requires a minimum precision of 8
|
|
||||||
precision: 8,
|
|
||||||
includePaths,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.less$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('less-loader'),
|
|
||||||
options: {
|
|
||||||
sourceMap: cssSourceMap,
|
|
||||||
lessOptions: {
|
|
||||||
javascriptEnabled: true,
|
|
||||||
...lessPathOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.styl$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('stylus-loader'),
|
|
||||||
options: {
|
|
||||||
sourceMap: cssSourceMap,
|
|
||||||
stylusOptions: {
|
|
||||||
include: includePaths,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// load component css as raw strings
|
|
||||||
const componentsSourceMap = !!(cssSourceMap &&
|
|
||||||
// Never use component css sourcemap when style optimizations are on.
|
|
||||||
// It will just increase bundle size without offering good debug experience.
|
|
||||||
typeof buildOptions.optimization === 'undefined'
|
|
||||||
? true
|
|
||||||
: typeof buildOptions.optimization === 'boolean'
|
|
||||||
? !buildOptions.optimization
|
|
||||||
: buildOptions.optimization?.styles &&
|
|
||||||
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
|
|
||||||
// for component css.
|
|
||||||
buildOptions.sourceMap !== 'hidden');
|
|
||||||
|
|
||||||
const rules: RuleSetRule[] = baseRules.map(({ test, use }) => ({
|
|
||||||
exclude: globalStylePaths,
|
|
||||||
test,
|
|
||||||
use: [
|
|
||||||
{ loader: require.resolve('raw-loader') },
|
|
||||||
{ loader: RawCssLoader },
|
|
||||||
{
|
|
||||||
loader: require.resolve('postcss-loader'),
|
|
||||||
options: {
|
|
||||||
implementation: require('postcss'),
|
|
||||||
postcssOptions: postcssOptionsCreator(componentsSourceMap),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...(Array.isArray(use) ? use : []),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
|
|
||||||
// load global css as css files
|
|
||||||
if (globalStylePaths.length > 0) {
|
|
||||||
const globalSourceMap =
|
|
||||||
!!cssSourceMap && buildOptions.sourceMap !== 'hidden';
|
|
||||||
|
|
||||||
rules.push(
|
|
||||||
...baseRules.map(({ test, use }) => {
|
|
||||||
return {
|
|
||||||
include: globalStylePaths,
|
|
||||||
test,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: MiniCssExtractPlugin.loader,
|
|
||||||
options: { esModule: true },
|
|
||||||
},
|
|
||||||
{ loader: RawCssLoader },
|
|
||||||
{
|
|
||||||
loader: require.resolve('postcss-loader'),
|
|
||||||
options: {
|
|
||||||
implementation: require('postcss'),
|
|
||||||
postcssOptions: postcssOptionsCreator(globalSourceMap),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...(use as any[]),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
extraPlugins.push(
|
|
||||||
// extract global css from js files into own css file
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: `[name]${hashFormat.extract}.css`,
|
|
||||||
}),
|
|
||||||
// suppress empty .js files in css only entry points
|
|
||||||
new RemoveEmptyScriptsPlugin()
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
entry: entryPoints,
|
|
||||||
module: { rules },
|
|
||||||
plugins: extraPlugins,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export default function RawCssLoader(content: string, map: object) {
|
|
||||||
const stringifiedContent = JSON.stringify(content);
|
|
||||||
const stringifiedMap = map ? JSON.stringify(map) : `''`;
|
|
||||||
return `module.exports = [[module.id, ${stringifiedContent}, '', ${stringifiedMap}]]`;
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
// Removes empty script bundles (e.g. styles.js)
|
|
||||||
// FROM: https://github.com/webdiscus/webpack-remove-empty-scripts/blame/1ff513bd6146a6b2d01fdc7f7da15c5b14fed14b/index.js
|
|
||||||
|
|
||||||
const NAME = 'webpack-remove-empty-scripts';
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
verbose: false,
|
|
||||||
extensions: ['css', 'scss', 'sass', 'less', 'styl'],
|
|
||||||
scriptExtensions: ['js', 'mjs'],
|
|
||||||
ignore: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save unique id in dependency object as marker of 'analysed module'
|
|
||||||
// to avoid the infinite recursion by collect of resources.
|
|
||||||
let dependencyId = 1;
|
|
||||||
|
|
||||||
export class RemoveEmptyScriptsPlugin {
|
|
||||||
constructor(private options: any = {}) {
|
|
||||||
this.apply = this.apply.bind(this);
|
|
||||||
Object.assign(this.options, defaultOptions, this.options);
|
|
||||||
|
|
||||||
// Deprecation of option `silent`.
|
|
||||||
if (options && options.hasOwnProperty('silent')) {
|
|
||||||
this.options.verbose = !options.silent;
|
|
||||||
console.warn(
|
|
||||||
'[DEPRECATION] the `silent` option is deprecated and will be removed on Juni 30, 2021. Use option `verbose: true` to show in console each removed empty file. Defaults, `verbose: false`.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if by assigned option the `ignore` was not array, then set as array
|
|
||||||
if (!Array.isArray(this.options.ignore)) {
|
|
||||||
this.options.ignore = [this.options.ignore];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(compiler) {
|
|
||||||
const customIgnore = this.options.ignore;
|
|
||||||
|
|
||||||
const extensionsWithoutDots = this.options.extensions.map((e) =>
|
|
||||||
e[0] === '.' ? e.substring(1) : e
|
|
||||||
);
|
|
||||||
|
|
||||||
const patternOneOfExtensions = extensionsWithoutDots
|
|
||||||
.map((ext) => escapeRegExp(ext))
|
|
||||||
.join('|');
|
|
||||||
|
|
||||||
const reStylesResource = new RegExp(
|
|
||||||
`[.](${patternOneOfExtensions})([?].*)?$`
|
|
||||||
);
|
|
||||||
|
|
||||||
compiler.hooks.compilation.tap(NAME, (compilation) => {
|
|
||||||
const resourcesCache = [];
|
|
||||||
|
|
||||||
compilation.hooks.chunkAsset.tap(NAME, (chunk, file) => {
|
|
||||||
const isNotScript = defaultOptions.scriptExtensions.every(
|
|
||||||
(ext) => file.lastIndexOf('.' + ext) < 0
|
|
||||||
);
|
|
||||||
if (isNotScript) return;
|
|
||||||
|
|
||||||
const chunkGraph = compilation.chunkGraph;
|
|
||||||
let entryResources = [];
|
|
||||||
|
|
||||||
for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
|
|
||||||
if (!compilation.modules.has(module)) {
|
|
||||||
throw new Error(
|
|
||||||
'checkConstraints: entry module in chunk but not in compilation ' +
|
|
||||||
` ${chunk.debugId} ${module.debugId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleResources = collectEntryResources(
|
|
||||||
compilation,
|
|
||||||
module,
|
|
||||||
resourcesCache
|
|
||||||
);
|
|
||||||
entryResources = entryResources.concat(moduleResources);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resources =
|
|
||||||
customIgnore.length > 0
|
|
||||||
? entryResources.filter((res) =>
|
|
||||||
customIgnore.every((ignore) => !res.match(ignore))
|
|
||||||
)
|
|
||||||
: entryResources;
|
|
||||||
|
|
||||||
const isStyleOnly =
|
|
||||||
resources.length &&
|
|
||||||
resources.every((resource) => reStylesResource.test(resource));
|
|
||||||
|
|
||||||
if (isStyleOnly) {
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log('[remove-empty-scripts] remove empty js file: ' + file);
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk.files.delete(file);
|
|
||||||
compilation.deleteAsset(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectEntryResources(compilation, module, cache) {
|
|
||||||
const moduleGraph = compilation.moduleGraph,
|
|
||||||
index = moduleGraph.getPreOrderIndex(module),
|
|
||||||
propNameDependencyId = '__dependencyWebpackRemoveEmptyScriptsUniqueId',
|
|
||||||
resources = [];
|
|
||||||
|
|
||||||
// the index can be null
|
|
||||||
if (index == null) {
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
// index of module is unique per compilation
|
|
||||||
// module.id can be null, not used here
|
|
||||||
if (cache[index] !== undefined) {
|
|
||||||
return cache[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof module.resource === 'string') {
|
|
||||||
const resources = [module.resource];
|
|
||||||
cache[index] = resources;
|
|
||||||
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.dependencies) {
|
|
||||||
module.dependencies.forEach((dependency) => {
|
|
||||||
let module = moduleGraph.getModule(dependency),
|
|
||||||
originModule = moduleGraph.getParentModule(dependency),
|
|
||||||
nextModule = module || originModule,
|
|
||||||
useNextModule = false;
|
|
||||||
|
|
||||||
if (!dependency.hasOwnProperty(propNameDependencyId)) {
|
|
||||||
dependency[propNameDependencyId] = dependencyId++;
|
|
||||||
useNextModule = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// debug info
|
|
||||||
//console.log('::: module ::: ', useNextModule ? '' : '-----', dependency[propNameDependencyId]);
|
|
||||||
|
|
||||||
if (nextModule && useNextModule) {
|
|
||||||
const dependencyResources = collectEntryResources(
|
|
||||||
compilation,
|
|
||||||
nextModule,
|
|
||||||
cache
|
|
||||||
);
|
|
||||||
|
|
||||||
for (
|
|
||||||
let i = 0, length = dependencyResources.length;
|
|
||||||
i !== length;
|
|
||||||
i++
|
|
||||||
) {
|
|
||||||
const file = dependencyResources[i];
|
|
||||||
if (resources.indexOf(file) < 0) {
|
|
||||||
resources.push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resources.length > 0) {
|
|
||||||
cache[index] = resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
|
|
||||||
const reHasRegExpChar = RegExp(reRegExpChar.source);
|
|
||||||
|
|
||||||
function escapeRegExp(string) {
|
|
||||||
string = String(string);
|
|
||||||
|
|
||||||
return string && reHasRegExpChar.test(string)
|
|
||||||
? string.replace(reRegExpChar, '\\$&')
|
|
||||||
: string;
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var check = document.createElement('script');
|
|
||||||
if (!('noModule' in check) && 'onbeforeload' in check) {
|
|
||||||
var support = false;
|
|
||||||
document.addEventListener(
|
|
||||||
'beforeload',
|
|
||||||
function (e) {
|
|
||||||
if (e.target === check) {
|
|
||||||
support = true;
|
|
||||||
} else if (!e.target.hasAttribute('nomodule') || !support) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
check.type = 'module';
|
|
||||||
check.src = '.';
|
|
||||||
document.head.appendChild(check);
|
|
||||||
check.remove();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
345
packages/webpack/src/utils/with-nx.ts
Normal file
345
packages/webpack/src/utils/with-nx.ts
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { Configuration, WebpackPluginInstance, ProgressPlugin } from 'webpack';
|
||||||
|
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
|
||||||
|
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
||||||
|
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
||||||
|
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
||||||
|
import TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
import nodeExternals = require('webpack-node-externals');
|
||||||
|
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
|
|
||||||
|
import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema';
|
||||||
|
import { StatsJsonPlugin } from '../plugins/stats-json-plugin';
|
||||||
|
import { createCopyPlugin } from './create-copy-plugin';
|
||||||
|
import { GeneratePackageJsonWebpackPlugin } from '../plugins/generate-package-json-webpack-plugin';
|
||||||
|
import { getOutputHashFormat } from './hash-format';
|
||||||
|
|
||||||
|
const IGNORED_WEBPACK_WARNINGS = [
|
||||||
|
/The comment file/i,
|
||||||
|
/could not find any license/i,
|
||||||
|
];
|
||||||
|
|
||||||
|
const extensions = ['.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
||||||
|
const mainFields = ['main', 'module'];
|
||||||
|
|
||||||
|
export function withNx(opts?: { skipTypeChecking?: boolean }) {
|
||||||
|
return function configure(
|
||||||
|
config: Configuration,
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
context,
|
||||||
|
}: {
|
||||||
|
options: NormalizedWebpackExecutorOptions;
|
||||||
|
context: ExecutorContext;
|
||||||
|
}
|
||||||
|
): Configuration {
|
||||||
|
const plugins: WebpackPluginInstance[] = [];
|
||||||
|
if (!opts?.skipTypeChecking) {
|
||||||
|
plugins.push(
|
||||||
|
new ForkTsCheckerWebpackPlugin({
|
||||||
|
typescript: {
|
||||||
|
configFile: options.tsConfig,
|
||||||
|
memoryLimit: options.memoryLimit || 2018,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const entry = {};
|
||||||
|
|
||||||
|
if (options.main) {
|
||||||
|
const mainEntry = options.outputFileName
|
||||||
|
? path.parse(options.outputFileName).name
|
||||||
|
: 'main';
|
||||||
|
entry[mainEntry] = [options.main];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.additionalEntryPoints) {
|
||||||
|
for (const { entryName, entryPath } of options.additionalEntryPoints) {
|
||||||
|
entry[entryName] = entryPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.polyfills) {
|
||||||
|
entry['polyfills'] = [
|
||||||
|
...(entry['polyfills'] || []),
|
||||||
|
path.resolve(options.root, options.polyfills),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.progress) {
|
||||||
|
plugins.push(new ProgressPlugin({ profile: options.verbose }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.extractLicenses) {
|
||||||
|
plugins.push(
|
||||||
|
new LicenseWebpackPlugin({
|
||||||
|
stats: {
|
||||||
|
warnings: false,
|
||||||
|
errors: false,
|
||||||
|
},
|
||||||
|
perChunkOutput: false,
|
||||||
|
outputFilename: `3rdpartylicenses.txt`,
|
||||||
|
}) as unknown as WebpackPluginInstance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(options.assets) && options.assets.length > 0) {
|
||||||
|
plugins.push(createCopyPlugin(options.assets));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.generatePackageJson && context) {
|
||||||
|
plugins.push(new GeneratePackageJsonWebpackPlugin(context, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.statsJson) {
|
||||||
|
plugins.push(new StatsJsonPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
let externals = [];
|
||||||
|
if (options.target === 'node' && options.externalDependencies === 'all') {
|
||||||
|
const modulesDir = `${options.root}/node_modules`;
|
||||||
|
externals.push(nodeExternals({ modulesDir }));
|
||||||
|
} else if (Array.isArray(options.externalDependencies)) {
|
||||||
|
externals.push(function (ctx, callback: Function) {
|
||||||
|
if (options.externalDependencies.includes(ctx.request)) {
|
||||||
|
// not bundled
|
||||||
|
return callback(null, `commonjs ${ctx.request}`);
|
||||||
|
}
|
||||||
|
// bundled
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashFormat = getOutputHashFormat(options.outputHashing as string);
|
||||||
|
const filename = options.outputHashing
|
||||||
|
? `[name]${hashFormat.script}.js`
|
||||||
|
: '[name].js';
|
||||||
|
const chunkFilename = options.outputHashing
|
||||||
|
? `[name]${hashFormat.chunk}.js`
|
||||||
|
: '[name].js';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
target: options.target,
|
||||||
|
node: false as const,
|
||||||
|
// When mode is development or production, webpack will automatically
|
||||||
|
// configure DefinePlugin to replace `process.env.NODE_ENV` with the
|
||||||
|
// build-time value. Thus, we need to make sure it's the same value to
|
||||||
|
// avoid conflicts.
|
||||||
|
//
|
||||||
|
// When the NODE_ENV is something else (e.g. test), then set it to none
|
||||||
|
// to prevent extra behavior from webpack.
|
||||||
|
mode:
|
||||||
|
process.env.NODE_ENV === 'development' ||
|
||||||
|
process.env.NODE_ENV === 'production'
|
||||||
|
? process.env.NODE_ENV
|
||||||
|
: 'none',
|
||||||
|
devtool:
|
||||||
|
options.sourceMap === 'hidden'
|
||||||
|
? 'hidden-source-map'
|
||||||
|
: options.sourceMap
|
||||||
|
? 'source-map'
|
||||||
|
: false,
|
||||||
|
entry,
|
||||||
|
output: {
|
||||||
|
...config.output,
|
||||||
|
libraryTarget: options.target === 'node' ? 'commonjs' : undefined,
|
||||||
|
path: options.outputPath,
|
||||||
|
filename,
|
||||||
|
chunkFilename,
|
||||||
|
hashFunction: 'xxhash64',
|
||||||
|
// Disabled for performance
|
||||||
|
pathinfo: false,
|
||||||
|
scriptType: 'module',
|
||||||
|
},
|
||||||
|
watch: options.watch,
|
||||||
|
watchOptions: {
|
||||||
|
poll: options.poll,
|
||||||
|
},
|
||||||
|
profile: options.statsJson,
|
||||||
|
resolve: {
|
||||||
|
...config.resolve,
|
||||||
|
extensions,
|
||||||
|
alias: options.fileReplacements.reduce(
|
||||||
|
(aliases, replacement) => ({
|
||||||
|
...aliases,
|
||||||
|
[replacement.replace]: replacement.with,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
plugins: [
|
||||||
|
new TsconfigPathsPlugin({
|
||||||
|
configFile: options.tsConfig,
|
||||||
|
extensions,
|
||||||
|
mainFields,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
mainFields,
|
||||||
|
},
|
||||||
|
externals,
|
||||||
|
optimization: {
|
||||||
|
...config.optimization,
|
||||||
|
sideEffects: true,
|
||||||
|
minimize: !!options.optimization,
|
||||||
|
minimizer: [
|
||||||
|
options.compiler !== 'swc'
|
||||||
|
? new TerserPlugin({
|
||||||
|
parallel: true,
|
||||||
|
terserOptions: {
|
||||||
|
ecma: 2020,
|
||||||
|
safari10: true,
|
||||||
|
output: {
|
||||||
|
ascii_only: true,
|
||||||
|
comments: false,
|
||||||
|
webkit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: new TerserPlugin({
|
||||||
|
minify: TerserPlugin.swcMinify,
|
||||||
|
// `terserOptions` options will be passed to `swc`
|
||||||
|
terserOptions: {
|
||||||
|
mangle: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
runtimeChunk: false,
|
||||||
|
concatenateModules: true,
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
...config.performance,
|
||||||
|
hints: false,
|
||||||
|
},
|
||||||
|
experiments: { ...config.experiments, cacheUnaffected: true },
|
||||||
|
ignoreWarnings: [
|
||||||
|
(x) =>
|
||||||
|
IGNORED_WEBPACK_WARNINGS.some((r) =>
|
||||||
|
typeof x === 'string' ? r.test(x) : r.test(x.message)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
...config.module,
|
||||||
|
// Enabled for performance
|
||||||
|
unsafeCache: true,
|
||||||
|
rules: [
|
||||||
|
options.sourceMap && {
|
||||||
|
test: /\.js$/,
|
||||||
|
enforce: 'pre' as const,
|
||||||
|
loader: require.resolve('source-map-loader'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// There's an issue resolving paths without fully specified extensions
|
||||||
|
// See: https://github.com/graphql/graphql-js/issues/2721
|
||||||
|
// TODO(jack): Add a flag to turn this option on like Next.js does via experimental flag.
|
||||||
|
// See: https://github.com/vercel/next.js/pull/29880
|
||||||
|
test: /\.m?jsx?$/,
|
||||||
|
resolve: {
|
||||||
|
fullySpecified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// There's an issue when using buildable libs and .js files (instead of .ts files),
|
||||||
|
// where the wrong type is used (commonjs vs esm) resulting in export-imports throwing errors.
|
||||||
|
// See: https://github.com/nrwl/nx/issues/10990
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
type: 'javascript/auto',
|
||||||
|
},
|
||||||
|
createLoaderFromCompiler(options),
|
||||||
|
].filter((r) => !!r),
|
||||||
|
},
|
||||||
|
plugins: (config.plugins ?? []).concat(plugins),
|
||||||
|
stats: {
|
||||||
|
hash: true,
|
||||||
|
timings: false,
|
||||||
|
cached: false,
|
||||||
|
cachedAssets: false,
|
||||||
|
modules: false,
|
||||||
|
warnings: true,
|
||||||
|
errors: true,
|
||||||
|
colors: !options.verbose && !options.statsJson,
|
||||||
|
chunks: !options.verbose,
|
||||||
|
assets: !!options.verbose,
|
||||||
|
chunkOrigins: !!options.verbose,
|
||||||
|
chunkModules: !!options.verbose,
|
||||||
|
children: !!options.verbose,
|
||||||
|
reasons: !!options.verbose,
|
||||||
|
version: !!options.verbose,
|
||||||
|
errorDetails: !!options.verbose,
|
||||||
|
moduleTrace: !!options.verbose,
|
||||||
|
usedExports: !!options.verbose,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLoaderFromCompiler(
|
||||||
|
options: NormalizedWebpackExecutorOptions
|
||||||
|
) {
|
||||||
|
switch (options.compiler) {
|
||||||
|
case 'swc':
|
||||||
|
return {
|
||||||
|
test: /\.([jt])sx?$/,
|
||||||
|
loader: require.resolve('swc-loader'),
|
||||||
|
exclude: /node_modules/,
|
||||||
|
options: {
|
||||||
|
jsc: {
|
||||||
|
parser: {
|
||||||
|
syntax: 'typescript',
|
||||||
|
decorators: true,
|
||||||
|
tsx: true,
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
react: {
|
||||||
|
runtime: 'automatic',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
loose: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'tsc':
|
||||||
|
const { loadTsTransformers } = require('@nrwl/js');
|
||||||
|
const { compilerPluginHooks, hasPlugin } = loadTsTransformers(
|
||||||
|
options.transformers
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
test: /\.([jt])sx?$/,
|
||||||
|
loader: require.resolve(`ts-loader`),
|
||||||
|
exclude: /node_modules/,
|
||||||
|
options: {
|
||||||
|
configFile: options.tsConfig,
|
||||||
|
transpileOnly: !hasPlugin,
|
||||||
|
// https://github.com/TypeStrong/ts-loader/pull/685
|
||||||
|
experimentalWatchApi: true,
|
||||||
|
getCustomTransformers: (program) => ({
|
||||||
|
before: compilerPluginHooks.beforeHooks.map((hook) =>
|
||||||
|
hook(program)
|
||||||
|
),
|
||||||
|
after: compilerPluginHooks.afterHooks.map((hook) => hook(program)),
|
||||||
|
afterDeclarations: compilerPluginHooks.afterDeclarationsHooks.map(
|
||||||
|
(hook) => hook(program)
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'babel':
|
||||||
|
const tsConfig = readTsConfig(options.tsConfig);
|
||||||
|
return {
|
||||||
|
test: /\.([jt])sx?$/,
|
||||||
|
loader: path.join(__dirname, './web-babel-loader'),
|
||||||
|
exclude: /node_modules/,
|
||||||
|
options: {
|
||||||
|
rootMode: 'upward',
|
||||||
|
cwd: path.join(options.root, options.sourceRoot),
|
||||||
|
emitDecoratorMetadata: tsConfig.options.emitDecoratorMetadata,
|
||||||
|
isModern: true,
|
||||||
|
envName: process.env.NODE_ENV,
|
||||||
|
babelrc: true,
|
||||||
|
cacheDirectory: true,
|
||||||
|
cacheCompression: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
477
packages/webpack/src/utils/with-web.ts
Normal file
477
packages/webpack/src/utils/with-web.ts
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
import * as webpack from 'webpack';
|
||||||
|
import {
|
||||||
|
Configuration,
|
||||||
|
ids,
|
||||||
|
RuleSetRule,
|
||||||
|
WebpackPluginInstance,
|
||||||
|
} from 'webpack';
|
||||||
|
import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { getOutputHashFormat } from '@nrwl/webpack/src/utils/hash-format';
|
||||||
|
import { PostcssCliResources } from '@nrwl/webpack/src/utils/webpack/plugins/postcss-cli-resources';
|
||||||
|
import { normalizeExtraEntryPoints } from '@nrwl/webpack/src/utils/webpack/normalize-entry';
|
||||||
|
|
||||||
|
import { NormalizedWebpackExecutorOptions } from '../executors/webpack/schema';
|
||||||
|
import { getClientEnvironment } from './get-client-environment';
|
||||||
|
import { RemoveHashPlugin } from './webpack/plugins/remove-hash-plugin';
|
||||||
|
import { ScriptsWebpackPlugin } from './webpack/plugins/scripts-webpack-plugin';
|
||||||
|
import { getCSSModuleLocalIdent } from './get-css-module-local-ident';
|
||||||
|
import CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||||
|
import MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
import autoprefixer = require('autoprefixer');
|
||||||
|
import postcssImports = require('postcss-import');
|
||||||
|
import { Postcss } from 'postcss';
|
||||||
|
import { basename } from 'path';
|
||||||
|
|
||||||
|
interface PostcssOptions {
|
||||||
|
(loader: any): any;
|
||||||
|
|
||||||
|
config?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withWeb() {
|
||||||
|
return function configure(
|
||||||
|
config: Configuration,
|
||||||
|
{ options }: { options: NormalizedWebpackExecutorOptions }
|
||||||
|
): Configuration {
|
||||||
|
const plugins = [];
|
||||||
|
|
||||||
|
const stylesOptimization =
|
||||||
|
typeof options.optimization === 'object'
|
||||||
|
? options.optimization.styles
|
||||||
|
: options.optimization;
|
||||||
|
|
||||||
|
if (Array.isArray(options.scripts)) {
|
||||||
|
plugins.push(...createScriptsPlugin(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.subresourceIntegrity) {
|
||||||
|
plugins.push(new SubresourceIntegrityPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimizer: WebpackPluginInstance[] = [
|
||||||
|
new ids.HashedModuleIdsPlugin(),
|
||||||
|
];
|
||||||
|
if (stylesOptimization) {
|
||||||
|
minimizer.push(
|
||||||
|
new CssMinimizerPlugin({
|
||||||
|
test: /\.(?:css|scss|sass|less|styl)$/,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
plugins.push(
|
||||||
|
new webpack.DefinePlugin(
|
||||||
|
getClientEnvironment(process.env.NODE_ENV).stringified
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const entry: { [key: string]: string[] } = {};
|
||||||
|
const globalStylePaths: string[] = [];
|
||||||
|
|
||||||
|
// Determine hashing format.
|
||||||
|
const hashFormat = getOutputHashFormat(options.outputHashing as string);
|
||||||
|
|
||||||
|
const includePaths: string[] = [];
|
||||||
|
if (options?.stylePreprocessorOptions?.includePaths?.length > 0) {
|
||||||
|
options.stylePreprocessorOptions.includePaths.forEach(
|
||||||
|
(includePath: string) =>
|
||||||
|
includePaths.push(path.resolve(options.root, includePath))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lessPathOptions: { paths?: string[] } = {};
|
||||||
|
|
||||||
|
if (includePaths.length > 0) {
|
||||||
|
lessPathOptions = {
|
||||||
|
paths: includePaths,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process global styles.
|
||||||
|
if (options.styles.length > 0) {
|
||||||
|
const chunkNames: string[] = [];
|
||||||
|
normalizeExtraEntryPoints(options.styles, 'styles').forEach((style) => {
|
||||||
|
const resolvedPath = path.resolve(options.root, style.input);
|
||||||
|
// Add style entry points.
|
||||||
|
if (entry[style.bundleName]) {
|
||||||
|
entry[style.bundleName].push(resolvedPath);
|
||||||
|
} else {
|
||||||
|
entry[style.bundleName] = [resolvedPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add global css paths.
|
||||||
|
globalStylePaths.push(resolvedPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chunkNames.length > 0) {
|
||||||
|
// Add plugin to remove hashes from lazy styles.
|
||||||
|
plugins.push(new RemoveHashPlugin({ chunkNames, hashFormat }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssModuleRules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.module\.css$/,
|
||||||
|
use: getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.(scss|sass)$/,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('sass-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('sass'),
|
||||||
|
sassOptions: {
|
||||||
|
fiber: false,
|
||||||
|
precision: 8,
|
||||||
|
includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.less$/,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('less-loader'),
|
||||||
|
options: {
|
||||||
|
lessOptions: {
|
||||||
|
paths: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.styl$/,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('stylus-loader'),
|
||||||
|
options: {
|
||||||
|
stylusOptions: {
|
||||||
|
include: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const globalCssRules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$|\.sass$/,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('sass-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('sass'),
|
||||||
|
sourceMap: options.sourceMap,
|
||||||
|
sassOptions: {
|
||||||
|
fiber: false,
|
||||||
|
// bootstrap-sass requires a minimum precision of 8
|
||||||
|
precision: 8,
|
||||||
|
includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('less-loader'),
|
||||||
|
options: {
|
||||||
|
sourceMap: options.sourceMap,
|
||||||
|
lessOptions: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
...lessPathOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.styl$/,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('stylus-loader'),
|
||||||
|
options: {
|
||||||
|
sourceMap: options.sourceMap,
|
||||||
|
stylusOptions: {
|
||||||
|
include: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.css$|\.scss$|\.sass$|\.less$|\.styl$/,
|
||||||
|
oneOf: [
|
||||||
|
...cssModuleRules,
|
||||||
|
...globalCssRules,
|
||||||
|
// load global css as css files
|
||||||
|
{
|
||||||
|
test: /\.css$|\.scss$|\.sass$|\.less$|\.styl$/,
|
||||||
|
include: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
options: { esModule: true },
|
||||||
|
},
|
||||||
|
{ loader: require.resolve('css-loader') },
|
||||||
|
{
|
||||||
|
loader: require.resolve('postcss-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('postcss'),
|
||||||
|
postcssOptions: postcssOptionsCreator(options, includePaths),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
// extract global css from js files into own css file
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: `[name]${hashFormat.extract}.css`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// context needs to be set for babel to pick up correct babelrc
|
||||||
|
config.context = path.join(options.root, options.projectRoot);
|
||||||
|
|
||||||
|
config.output = {
|
||||||
|
...config.output,
|
||||||
|
crossOriginLoading: options.subresourceIntegrity
|
||||||
|
? ('anonymous' as const)
|
||||||
|
: (false as const),
|
||||||
|
};
|
||||||
|
|
||||||
|
// In case users customize their webpack config with unsupported entry.
|
||||||
|
if (typeof config.entry === 'function')
|
||||||
|
throw new Error('Entry function is not supported. Use an object.');
|
||||||
|
if (typeof config.entry === 'string')
|
||||||
|
throw new Error('Entry string is not supported. Use an object.');
|
||||||
|
if (Array.isArray(config.entry))
|
||||||
|
throw new Error('Entry array is not supported. Use an object.');
|
||||||
|
|
||||||
|
config.entry = { ...config.entry, ...entry };
|
||||||
|
|
||||||
|
config.optimization = {
|
||||||
|
...config.optimization,
|
||||||
|
minimizer,
|
||||||
|
emitOnErrors: false,
|
||||||
|
moduleIds: 'deterministic' as const,
|
||||||
|
runtimeChunk: options.runtimeChunk ? ('single' as const) : false,
|
||||||
|
splitChunks: {
|
||||||
|
maxAsyncRequests: Infinity,
|
||||||
|
cacheGroups: {
|
||||||
|
default: !!options.commonChunk && {
|
||||||
|
chunks: 'async' as const,
|
||||||
|
minChunks: 2,
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
common: !!options.commonChunk && {
|
||||||
|
name: 'common',
|
||||||
|
chunks: 'async' as const,
|
||||||
|
minChunks: 2,
|
||||||
|
enforce: true,
|
||||||
|
priority: 5,
|
||||||
|
},
|
||||||
|
vendors: false as const,
|
||||||
|
vendor: !!options.vendorChunk && {
|
||||||
|
name: 'vendor',
|
||||||
|
chunks: (chunk) => chunk.name === 'main',
|
||||||
|
enforce: true,
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
config.plugins.push(...plugins);
|
||||||
|
|
||||||
|
config.module = {
|
||||||
|
...config.module,
|
||||||
|
rules: [
|
||||||
|
...config.module.rules,
|
||||||
|
...rules,
|
||||||
|
{
|
||||||
|
test: /\.(bmp|png|jpe?g|gif|webp|avif)$/,
|
||||||
|
type: 'asset',
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 10_000, // 10 kB
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
|
||||||
|
loader: require.resolve('file-loader'),
|
||||||
|
options: {
|
||||||
|
name: `[name]${hashFormat.file}.[ext]`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScriptsPlugin(
|
||||||
|
options: NormalizedWebpackExecutorOptions
|
||||||
|
): WebpackPluginInstance[] {
|
||||||
|
// process global scripts
|
||||||
|
const globalScriptsByBundleName = normalizeExtraEntryPoints(
|
||||||
|
options.scripts || [],
|
||||||
|
'scripts'
|
||||||
|
).reduce(
|
||||||
|
(
|
||||||
|
prev: { inject: boolean; bundleName: string; paths: string[] }[],
|
||||||
|
curr
|
||||||
|
) => {
|
||||||
|
const bundleName = curr.bundleName;
|
||||||
|
const resolvedPath = path.resolve(options.root, curr.input);
|
||||||
|
const existingEntry = prev.find((el) => el.bundleName === bundleName);
|
||||||
|
if (existingEntry) {
|
||||||
|
existingEntry.paths.push(resolvedPath);
|
||||||
|
} else {
|
||||||
|
prev.push({
|
||||||
|
inject: curr.inject,
|
||||||
|
bundleName,
|
||||||
|
paths: [resolvedPath],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hashFormat = getOutputHashFormat(options.outputHashing as string);
|
||||||
|
const plugins = [];
|
||||||
|
// Add a new asset for each entry.
|
||||||
|
globalScriptsByBundleName.forEach((script) => {
|
||||||
|
const hash = script.inject ? hashFormat.script : '';
|
||||||
|
const bundleName = script.bundleName;
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
new ScriptsWebpackPlugin({
|
||||||
|
name: bundleName,
|
||||||
|
sourceMap: !!options.sourceMap,
|
||||||
|
filename: `${basename(bundleName)}${hash}.js`,
|
||||||
|
scripts: script.paths,
|
||||||
|
basePath: options.sourceRoot,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommonLoadersForCssModules(
|
||||||
|
options: NormalizedWebpackExecutorOptions,
|
||||||
|
includePaths: string[]
|
||||||
|
) {
|
||||||
|
// load component css as raw strings
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
loader: options.extractCss
|
||||||
|
? MiniCssExtractPlugin.loader
|
||||||
|
: require.resolve('style-loader'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: require.resolve('css-loader'),
|
||||||
|
options: {
|
||||||
|
modules: {
|
||||||
|
mode: 'local',
|
||||||
|
getLocalIdent: getCSSModuleLocalIdent,
|
||||||
|
},
|
||||||
|
importLoaders: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: require.resolve('postcss-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('postcss'),
|
||||||
|
postcssOptions: postcssOptionsCreator(options, includePaths),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommonLoadersForGlobalCss(
|
||||||
|
options: NormalizedWebpackExecutorOptions,
|
||||||
|
includePaths: string[]
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
loader: options.extractCss
|
||||||
|
? MiniCssExtractPlugin.loader
|
||||||
|
: require.resolve('style-loader'),
|
||||||
|
},
|
||||||
|
{ loader: require.resolve('css-loader') },
|
||||||
|
{
|
||||||
|
loader: require.resolve('postcss-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('postcss'),
|
||||||
|
postcssOptions: postcssOptionsCreator(options, includePaths),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function postcssOptionsCreator(
|
||||||
|
options: NormalizedWebpackExecutorOptions,
|
||||||
|
includePaths: string[]
|
||||||
|
) {
|
||||||
|
const hashFormat = getOutputHashFormat(options.outputHashing as string);
|
||||||
|
// 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
|
||||||
|
|
||||||
|
const postcssOptions: PostcssOptions = (loader) => ({
|
||||||
|
map: options.sourceMap &&
|
||||||
|
options.sourceMap !== 'hidden' && {
|
||||||
|
inline: true,
|
||||||
|
annotation: false,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
postcssImports({
|
||||||
|
addModulesDirectories: includePaths,
|
||||||
|
resolve: (url: string) => (url.startsWith('~') ? url.slice(1) : url),
|
||||||
|
}),
|
||||||
|
PostcssCliResources({
|
||||||
|
baseHref: options.baseHref,
|
||||||
|
deployUrl: options.deployUrl,
|
||||||
|
loader,
|
||||||
|
filename: `[name]${hashFormat.file}.[ext]`,
|
||||||
|
}),
|
||||||
|
autoprefixer(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a path to postcssConfig is passed in, set it for app and all libs, otherwise
|
||||||
|
// use automatic detection.
|
||||||
|
if (typeof options.postcssConfig === 'string') {
|
||||||
|
postcssOptions.config = path.join(options.root, options.postcssConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return postcssOptions;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user