feat(rspack): move logic for withWeb to applyWebConfig and bring in line with webpack (#28803)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> `withWeb` from `@nx/rspack` is not reflective of what `@nx/webpack` does. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> Bring `withWeb` in line with `@nx/webpack` ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
2c9fc572f0
commit
fd2e8d0f55
@ -26,15 +26,13 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The tsconfig file to build the project."
|
"description": "The tsconfig file to build the project."
|
||||||
},
|
},
|
||||||
"typeCheck": {
|
"skipTypeChecking": {
|
||||||
|
"alias": "typeCheck",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Skip the type checking."
|
"description": "Skip the type checking. Default is `false`."
|
||||||
},
|
|
||||||
"indexHtml": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The path to the index.html file."
|
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
|
"alias": "indexHtml",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "HTML File which will be contain the application.",
|
"description": "HTML File which will be contain the application.",
|
||||||
"x-completion-type": "file",
|
"x-completion-type": "file",
|
||||||
|
|||||||
@ -65,7 +65,11 @@ describe('rspack e2e', () => {
|
|||||||
});
|
});
|
||||||
expect(result).toContain('Successfully ran target build');
|
expect(result).toContain('Successfully ran target build');
|
||||||
// Make sure expected files are present.
|
// Make sure expected files are present.
|
||||||
expect(listFiles(`dist/${project}`)).toHaveLength(5);
|
/**
|
||||||
|
* The files that are generated are:
|
||||||
|
* ["3rdpartylicenses.txt", "assets", "favicon.ico", "index.html", "main.bf7851e6.js", "runtime.e4294127.js"]
|
||||||
|
*/
|
||||||
|
expect(listFiles(`dist/${project}`)).toHaveLength(6);
|
||||||
|
|
||||||
result = runCLI(`test ${project}`);
|
result = runCLI(`test ${project}`);
|
||||||
expect(result).toContain('Successfully ran target test');
|
expect(result).toContain('Successfully ran target test');
|
||||||
@ -83,7 +87,7 @@ describe('rspack e2e', () => {
|
|||||||
env: { NODE_ENV: 'production' },
|
env: { NODE_ENV: 'production' },
|
||||||
});
|
});
|
||||||
expect(result).toContain('Successfully ran target build');
|
expect(result).toContain('Successfully ran target build');
|
||||||
expect(listFiles(`dist/${project}`)).toHaveLength(5); // same length as before
|
expect(listFiles(`dist/${project}`)).toHaveLength(6); // same length as before
|
||||||
|
|
||||||
// Generate a new app and check that the files are correct
|
// Generate a new app and check that the files are correct
|
||||||
const app2 = uniq('app2');
|
const app2 = uniq('app2');
|
||||||
@ -116,7 +120,7 @@ describe('rspack e2e', () => {
|
|||||||
});
|
});
|
||||||
expect(result).toContain('Successfully ran target build');
|
expect(result).toContain('Successfully ran target build');
|
||||||
// Make sure expected files are present.
|
// Make sure expected files are present.
|
||||||
expect(listFiles(`dist/${app2}`)).toHaveLength(5);
|
expect(listFiles(`dist/${app2}`)).toHaveLength(6);
|
||||||
|
|
||||||
result = runCLI(`test ${app2}`);
|
result = runCLI(`test ${app2}`);
|
||||||
expect(result).toContain('Successfully ran target test');
|
expect(result).toContain('Successfully ran target test');
|
||||||
|
|||||||
@ -39,7 +39,8 @@
|
|||||||
"@nx/workspace",
|
"@nx/workspace",
|
||||||
// Imported types only
|
// Imported types only
|
||||||
"@module-federation/sdk",
|
"@module-federation/sdk",
|
||||||
"@module-federation/enhanced"
|
"@module-federation/enhanced",
|
||||||
|
"css-loader"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -28,20 +28,27 @@
|
|||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/web": "file:../web",
|
"@nx/web": "file:../web",
|
||||||
"@phenomnomnominal/tsquery": "~5.0.1",
|
"@phenomnomnominal/tsquery": "~5.0.1",
|
||||||
|
"@rspack/core": "^1.0.4",
|
||||||
|
"@rspack/dev-server": "^1.0.4",
|
||||||
|
"@rspack/plugin-react-refresh": "^1.0.0",
|
||||||
|
"autoprefixer": "^10.4.9",
|
||||||
|
"chalk": "~4.1.0",
|
||||||
|
"css-loader": "^6.4.0",
|
||||||
"enquirer": "~2.3.6",
|
"enquirer": "~2.3.6",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"http-proxy-middleware": "^3.0.3",
|
"http-proxy-middleware": "^3.0.3",
|
||||||
"less-loader": "11.1.0",
|
"less-loader": "11.1.0",
|
||||||
"license-webpack-plugin": "^4.0.2",
|
"license-webpack-plugin": "^4.0.2",
|
||||||
|
"loader-utils": "^2.0.3",
|
||||||
|
"sass": "^1.42.1",
|
||||||
"sass-loader": "^12.2.0",
|
"sass-loader": "^12.2.0",
|
||||||
"stylus-loader": "^7.1.0",
|
"style-loader": "^3.3.0",
|
||||||
|
"postcss-import": "~14.1.0",
|
||||||
"postcss-loader": "^8.1.1",
|
"postcss-loader": "^8.1.1",
|
||||||
"@rspack/core": "^1.0.4",
|
"postcss": "^8.4.38",
|
||||||
"@rspack/dev-server": "^1.0.4",
|
|
||||||
"@rspack/plugin-react-refresh": "^1.0.0",
|
|
||||||
"chalk": "~4.1.0",
|
|
||||||
"tsconfig-paths": "^4.1.2",
|
"tsconfig-paths": "^4.1.2",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0",
|
||||||
|
"webpack-subresource-integrity": "^5.1.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@module-federation/enhanced": "~0.6.0",
|
"@module-federation/enhanced": "~0.6.0",
|
||||||
|
|||||||
17
packages/rspack/src/executors/rspack/schema.d.ts
vendored
17
packages/rspack/src/executors/rspack/schema.d.ts
vendored
@ -6,8 +6,10 @@ export interface RspackExecutorSchema {
|
|||||||
index?: string;
|
index?: string;
|
||||||
tsConfig?: string;
|
tsConfig?: string;
|
||||||
typeCheck?: boolean;
|
typeCheck?: boolean;
|
||||||
|
skipTypeChecking?: boolean;
|
||||||
outputPath?: string;
|
outputPath?: string;
|
||||||
outputFileName?: string;
|
outputFileName?: string;
|
||||||
|
index?: string;
|
||||||
indexHtml?: string;
|
indexHtml?: string;
|
||||||
mode?: Mode;
|
mode?: Mode;
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
@ -23,6 +25,13 @@ export interface RspackExecutorSchema {
|
|||||||
generatePackageJson?: boolean;
|
generatePackageJson?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssetGlobPattern {
|
||||||
|
glob: string;
|
||||||
|
input: string;
|
||||||
|
output: string;
|
||||||
|
ignore?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface FileReplacement {
|
export interface FileReplacement {
|
||||||
replace: string;
|
replace: string;
|
||||||
with: string;
|
with: string;
|
||||||
@ -32,3 +41,11 @@ export interface OptimizationOptions {
|
|||||||
scripts: boolean;
|
scripts: boolean;
|
||||||
styles: boolean;
|
styles: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NormalizedRspackExecutorSchema extends RspackExecutorSchema {
|
||||||
|
outputFileName: string;
|
||||||
|
assets: AssetGlobPattern[];
|
||||||
|
root: string;
|
||||||
|
projectRoot: string;
|
||||||
|
sourceRoot: string;
|
||||||
|
}
|
||||||
|
|||||||
@ -26,15 +26,13 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The tsconfig file to build the project."
|
"description": "The tsconfig file to build the project."
|
||||||
},
|
},
|
||||||
"typeCheck": {
|
"skipTypeChecking": {
|
||||||
|
"alias": "typeCheck",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Skip the type checking."
|
"description": "Skip the type checking. Default is `false`."
|
||||||
},
|
|
||||||
"indexHtml": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The path to the index.html file."
|
|
||||||
},
|
},
|
||||||
"index": {
|
"index": {
|
||||||
|
"alias": "indexHtml",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "HTML File which will be contain the application.",
|
"description": "HTML File which will be contain the application.",
|
||||||
"x-completion-type": "file",
|
"x-completion-type": "file",
|
||||||
|
|||||||
459
packages/rspack/src/plugins/utils/apply-web-config.ts
Normal file
459
packages/rspack/src/plugins/utils/apply-web-config.ts
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
import {
|
||||||
|
type RspackPluginInstance,
|
||||||
|
type Configuration,
|
||||||
|
type RuleSetRule,
|
||||||
|
LightningCssMinimizerRspackPlugin,
|
||||||
|
DefinePlugin,
|
||||||
|
HtmlRspackPlugin,
|
||||||
|
CssExtractRspackPlugin,
|
||||||
|
EnvironmentPlugin,
|
||||||
|
} from '@rspack/core';
|
||||||
|
import { instantiateScriptPlugins } from './instantiate-script-plugins';
|
||||||
|
import { join, resolve } from 'path';
|
||||||
|
import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity';
|
||||||
|
import { getOutputHashFormat } from './hash-format';
|
||||||
|
import { normalizeExtraEntryPoints } from './normalize-entry';
|
||||||
|
import {
|
||||||
|
getCommonLoadersForCssModules,
|
||||||
|
getCommonLoadersForGlobalCss,
|
||||||
|
getCommonLoadersForGlobalStyle,
|
||||||
|
} from './loaders/stylesheet-loaders';
|
||||||
|
import { NormalizedNxAppRspackPluginOptions } from './models';
|
||||||
|
|
||||||
|
export function applyWebConfig(
|
||||||
|
options: NormalizedNxAppRspackPluginOptions,
|
||||||
|
config: Configuration = {},
|
||||||
|
{
|
||||||
|
useNormalizedEntry,
|
||||||
|
}: {
|
||||||
|
// rspack.Configuration allows arrays to be set on a single entry
|
||||||
|
// rspack then normalizes them to { import: "..." } objects
|
||||||
|
// This option allows use to preserve existing composePlugins behavior where entry.main is an array.
|
||||||
|
useNormalizedEntry?: boolean;
|
||||||
|
} = {}
|
||||||
|
): void {
|
||||||
|
if (global.NX_GRAPH_CREATION) return;
|
||||||
|
|
||||||
|
// Defaults that was applied from executor schema previously.
|
||||||
|
options.runtimeChunk ??= true; // need this for HMR and other things to work
|
||||||
|
options.extractCss ??= true;
|
||||||
|
options.generateIndexHtml ??= true;
|
||||||
|
options.index = options.index
|
||||||
|
? join(options.root, options.index)
|
||||||
|
: join(
|
||||||
|
options.root,
|
||||||
|
options.projectGraph.nodes[options.projectName].data.sourceRoot,
|
||||||
|
'index.html'
|
||||||
|
);
|
||||||
|
options.styles ??= [];
|
||||||
|
options.scripts ??= [];
|
||||||
|
|
||||||
|
const isProd =
|
||||||
|
process.env.NODE_ENV === 'production' || options.mode === 'production';
|
||||||
|
|
||||||
|
const plugins: RspackPluginInstance[] = [
|
||||||
|
new EnvironmentPlugin({
|
||||||
|
NODE_ENV: isProd ? 'production' : 'development',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const stylesOptimization =
|
||||||
|
typeof options.optimization === 'object'
|
||||||
|
? options.optimization.styles
|
||||||
|
: options.optimization;
|
||||||
|
|
||||||
|
if (Array.isArray(options.scripts)) {
|
||||||
|
plugins.push(...instantiateScriptPlugins(options));
|
||||||
|
}
|
||||||
|
if (options.index && options.generateIndexHtml) {
|
||||||
|
plugins.push(
|
||||||
|
new HtmlRspackPlugin({
|
||||||
|
template: options.index,
|
||||||
|
sri: options.subresourceIntegrity ? 'sha256' : undefined,
|
||||||
|
...(options.baseHref ? { base: { href: options.baseHref } } : {}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.subresourceIntegrity) {
|
||||||
|
plugins.push(new SubresourceIntegrityPlugin() as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimizer: RspackPluginInstance[] = [];
|
||||||
|
if (stylesOptimization) {
|
||||||
|
minimizer.push(
|
||||||
|
new LightningCssMinimizerRspackPlugin({
|
||||||
|
test: /\.(?:css|scss|sass|less|styl)$/,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!options.ssr) {
|
||||||
|
plugins.push(
|
||||||
|
new DefinePlugin(getClientEnvironment(process.env.NODE_ENV).stringified)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries: { [key: string]: { import: 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(resolve(options.root, includePath))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lessPathOptions: { paths?: string[] } = {};
|
||||||
|
|
||||||
|
if (includePaths.length > 0) {
|
||||||
|
lessPathOptions = {
|
||||||
|
paths: includePaths,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process global styles.
|
||||||
|
if (options.styles.length > 0) {
|
||||||
|
normalizeExtraEntryPoints(options.styles, 'styles').forEach((style) => {
|
||||||
|
const resolvedPath = style.input.startsWith('.')
|
||||||
|
? style.input
|
||||||
|
: resolve(options.root, style.input);
|
||||||
|
// Add style entry points.
|
||||||
|
if (entries[style.bundleName]) {
|
||||||
|
entries[style.bundleName].import.push(resolvedPath);
|
||||||
|
} else {
|
||||||
|
entries[style.bundleName] = { import: [resolvedPath] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add global css paths.
|
||||||
|
globalStylePaths.push(resolvedPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssModuleRules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.module\.css$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.(scss|sass)$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('sass-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('sass'),
|
||||||
|
sassOptions: {
|
||||||
|
fiber: false,
|
||||||
|
precision: 8,
|
||||||
|
includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.less$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('less-loader'),
|
||||||
|
options: {
|
||||||
|
lessOptions: {
|
||||||
|
paths: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.module\.styl$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForCssModules(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: join(
|
||||||
|
__dirname,
|
||||||
|
'../../../utils/webpack/deprecated-stylus-loader.js'
|
||||||
|
),
|
||||||
|
options: {
|
||||||
|
stylusOptions: {
|
||||||
|
include: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const globalCssRules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$|\.sass$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
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$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('less-loader'),
|
||||||
|
options: {
|
||||||
|
sourceMap: !!options.sourceMap,
|
||||||
|
lessOptions: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
...lessPathOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.styl$/,
|
||||||
|
exclude: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalCss(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: join(
|
||||||
|
__dirname,
|
||||||
|
'../../../utils/webpack/deprecated-stylus-loader.js'
|
||||||
|
),
|
||||||
|
options: {
|
||||||
|
sourceMap: !!options.sourceMap,
|
||||||
|
stylusOptions: {
|
||||||
|
include: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const globalStyleRules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
include: globalStylePaths,
|
||||||
|
use: getCommonLoadersForGlobalStyle(options, includePaths),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$|\.sass$/,
|
||||||
|
include: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalStyle(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$/,
|
||||||
|
include: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalStyle(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: require.resolve('less-loader'),
|
||||||
|
options: {
|
||||||
|
sourceMap: !!options.sourceMap,
|
||||||
|
lessOptions: {
|
||||||
|
javascriptEnabled: true,
|
||||||
|
...lessPathOptions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.styl$/,
|
||||||
|
include: globalStylePaths,
|
||||||
|
use: [
|
||||||
|
...getCommonLoadersForGlobalStyle(options, includePaths),
|
||||||
|
{
|
||||||
|
loader: join(
|
||||||
|
__dirname,
|
||||||
|
'../../../utils/webpack/deprecated-stylus-loader.js'
|
||||||
|
),
|
||||||
|
options: {
|
||||||
|
sourceMap: !!options.sourceMap,
|
||||||
|
stylusOptions: {
|
||||||
|
include: includePaths,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rules: RuleSetRule[] = [
|
||||||
|
{
|
||||||
|
test: /\.css$|\.scss$|\.sass$|\.less$|\.styl$/,
|
||||||
|
oneOf: [...cssModuleRules, ...globalCssRules, ...globalStyleRules],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (options.extractCss) {
|
||||||
|
plugins.push(
|
||||||
|
// extract global css from js files into own css file
|
||||||
|
new CssExtractRspackPlugin({
|
||||||
|
filename: `[name]${hashFormat.extract}.css`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.output = {
|
||||||
|
...(config.output ?? {}),
|
||||||
|
assetModuleFilename: '[name].[contenthash:20][ext]',
|
||||||
|
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.');
|
||||||
|
|
||||||
|
Object.entries(entries).forEach(([entryName, entryData]) => {
|
||||||
|
if (useNormalizedEntry) {
|
||||||
|
config.entry[entryName] = { import: entryData.import };
|
||||||
|
} else {
|
||||||
|
config.entry[entryName] = entryData.import;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
config.optimization = {
|
||||||
|
...(config.optimization ?? {}),
|
||||||
|
minimizer: [...(config.optimization?.minimizer ?? []), ...minimizer],
|
||||||
|
emitOnErrors: false,
|
||||||
|
moduleIds: 'deterministic' as const,
|
||||||
|
runtimeChunk: options.runtimeChunk ? { name: 'runtime' } : false,
|
||||||
|
splitChunks: {
|
||||||
|
defaultSizeTypes:
|
||||||
|
config.optimization?.splitChunks !== false
|
||||||
|
? config.optimization?.splitChunks?.defaultSizeTypes
|
||||||
|
: ['...'],
|
||||||
|
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.resolve.mainFields = ['browser', 'module', 'main'];
|
||||||
|
|
||||||
|
config.module = {
|
||||||
|
...(config.module ?? {}),
|
||||||
|
rules: [
|
||||||
|
...(config.module.rules ?? []),
|
||||||
|
// Images: Inline small images, and emit a separate file otherwise.
|
||||||
|
{
|
||||||
|
test: /\.(avif|bmp|gif|ico|jpe?g|png|webp)$/,
|
||||||
|
type: 'asset',
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 10_000, // 10 kB
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// SVG: same as image but we need to separate it so it can be swapped for SVGR in the React plugin.
|
||||||
|
{
|
||||||
|
test: /\.svg$/,
|
||||||
|
type: 'asset',
|
||||||
|
parser: {
|
||||||
|
dataUrlCondition: {
|
||||||
|
maxSize: 10_000, // 10 kB
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Fonts: Emit separate file and export the URL.
|
||||||
|
{
|
||||||
|
test: /\.(eot|otf|ttf|woff|woff2)$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
},
|
||||||
|
...rules,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
config.plugins ??= [];
|
||||||
|
config.plugins.push(...plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClientEnvironment(mode?: string) {
|
||||||
|
// Grab NODE_ENV and NX_PUBLIC_* environment variables and prepare them to be
|
||||||
|
// injected into the application via DefinePlugin in webpack configuration.
|
||||||
|
const nxPublicKeyRegex = /^NX_PUBLIC_/i;
|
||||||
|
|
||||||
|
const raw = Object.keys(process.env)
|
||||||
|
.filter((key) => nxPublicKeyRegex.test(key))
|
||||||
|
.reduce((env, key) => {
|
||||||
|
env[key] = process.env[key];
|
||||||
|
return 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 };
|
||||||
|
}
|
||||||
@ -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)$/
|
||||||
|
)
|
||||||
|
? '[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, '_');
|
||||||
|
}
|
||||||
26
packages/rspack/src/plugins/utils/hash-format.ts
Normal file
26
packages/rspack/src/plugins/utils/hash-format.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export interface HashFormat {
|
||||||
|
chunk: string;
|
||||||
|
extract: string;
|
||||||
|
file: string;
|
||||||
|
script: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOutputHashFormat(option: string, length = 20): HashFormat {
|
||||||
|
const hashFormats: { [option: string]: HashFormat } = {
|
||||||
|
none: { chunk: '', extract: '', file: '', script: '' },
|
||||||
|
media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' },
|
||||||
|
bundles: {
|
||||||
|
chunk: `.[chunkhash:${length}]`,
|
||||||
|
extract: `.[contenthash:${length}]`,
|
||||||
|
file: '',
|
||||||
|
script: `.[contenthash:${length}]`,
|
||||||
|
},
|
||||||
|
all: {
|
||||||
|
chunk: `.[chunkhash:${length}]`,
|
||||||
|
extract: `.[contenthash:${length}]`,
|
||||||
|
file: `.[contenthash:${length}]`,
|
||||||
|
script: `.[contenthash:${length}]`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return hashFormats[option] || hashFormats['none'];
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import type { RspackPluginInstance } from '@rspack/core';
|
||||||
|
|
||||||
|
import { getOutputHashFormat } from './hash-format';
|
||||||
|
import { ScriptsRspackPlugin } from './plugins/scripts-rspack-plugin';
|
||||||
|
import { normalizeExtraEntryPoints } from './normalize-entry';
|
||||||
|
|
||||||
|
export function instantiateScriptPlugins(options: any): RspackPluginInstance[] {
|
||||||
|
// 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 ScriptsRspackPlugin({
|
||||||
|
name: bundleName,
|
||||||
|
sourceMap: !!options.sourceMap,
|
||||||
|
filename: `${path.basename(bundleName)}${hash}.js`,
|
||||||
|
scripts: script.paths,
|
||||||
|
basePath: options.sourceRoot,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
145
packages/rspack/src/plugins/utils/loaders/stylesheet-loaders.ts
Normal file
145
packages/rspack/src/plugins/utils/loaders/stylesheet-loaders.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import autoprefixer = require('autoprefixer');
|
||||||
|
import postcssImports = require('postcss-import');
|
||||||
|
import { CssExtractRspackPlugin } from '@rspack/core';
|
||||||
|
|
||||||
|
import { getCSSModuleLocalIdent } from '../get-css-module-local-ident';
|
||||||
|
import { getOutputHashFormat } from '../hash-format';
|
||||||
|
import { PostcssCliResources } from '../plugins/postcss-cli-resources';
|
||||||
|
|
||||||
|
interface PostcssOptions {
|
||||||
|
(loader: any): any;
|
||||||
|
|
||||||
|
config?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommonLoadersForCssModules(
|
||||||
|
options: any,
|
||||||
|
includePaths: string[]
|
||||||
|
) {
|
||||||
|
// load component css as raw strings
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
loader: options.extractCss
|
||||||
|
? CssExtractRspackPlugin.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,
|
||||||
|
forCssModules: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommonLoadersForGlobalCss(
|
||||||
|
options: any,
|
||||||
|
includePaths: string[]
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
loader: options.extractCss
|
||||||
|
? CssExtractRspackPlugin.loader
|
||||||
|
: require.resolve('style-loader'),
|
||||||
|
},
|
||||||
|
{ loader: require.resolve('css-loader'), options: { url: false } },
|
||||||
|
{
|
||||||
|
loader: require.resolve('postcss-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('postcss'),
|
||||||
|
postcssOptions: postcssOptionsCreator(options, {
|
||||||
|
includePaths,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommonLoadersForGlobalStyle(
|
||||||
|
options: any,
|
||||||
|
includePaths: string[]
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
loader: options.extractCss
|
||||||
|
? CssExtractRspackPlugin.loader
|
||||||
|
: require.resolve('style-loader'),
|
||||||
|
options: { esModule: true },
|
||||||
|
},
|
||||||
|
{ loader: require.resolve('css-loader'), options: { url: false } },
|
||||||
|
{
|
||||||
|
loader: require.resolve('postcss-loader'),
|
||||||
|
options: {
|
||||||
|
implementation: require('postcss'),
|
||||||
|
postcssOptions: postcssOptionsCreator(options, {
|
||||||
|
includePaths,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function postcssOptionsCreator(
|
||||||
|
options: any,
|
||||||
|
{
|
||||||
|
includePaths,
|
||||||
|
forCssModules = false,
|
||||||
|
}: {
|
||||||
|
includePaths: string[];
|
||||||
|
forCssModules?: boolean;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
...(forCssModules
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
PostcssCliResources({
|
||||||
|
baseHref: options.baseHref,
|
||||||
|
deployUrl: options.deployUrl,
|
||||||
|
loader,
|
||||||
|
filename: `[name]${hashFormat.file}.[ext]`,
|
||||||
|
publicPath: options.publicPath,
|
||||||
|
rebaseRootRelative: options.rebaseRootRelative,
|
||||||
|
}),
|
||||||
|
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;
|
||||||
|
}
|
||||||
241
packages/rspack/src/plugins/utils/models.ts
Normal file
241
packages/rspack/src/plugins/utils/models.ts
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
import type { Mode } from '@rspack/core';
|
||||||
|
import type { ProjectGraph } from '@nx/devkit';
|
||||||
|
import type { AssetGlob } from '@nx/js/src/utils/assets/assets';
|
||||||
|
|
||||||
|
export interface AssetGlobPattern {
|
||||||
|
glob: string;
|
||||||
|
input: string;
|
||||||
|
output: string;
|
||||||
|
ignore?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtraEntryPointClass {
|
||||||
|
bundleName?: string;
|
||||||
|
inject?: boolean;
|
||||||
|
input: string;
|
||||||
|
lazy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileReplacement {
|
||||||
|
replace: string;
|
||||||
|
with: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdditionalEntryPoint {
|
||||||
|
entryName: string;
|
||||||
|
entryPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransformerPlugin {
|
||||||
|
name: string;
|
||||||
|
options: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransformerEntry = string | TransformerPlugin;
|
||||||
|
|
||||||
|
export interface OptimizationOptions {
|
||||||
|
scripts: boolean;
|
||||||
|
styles: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NxAppRspackPluginOptions {
|
||||||
|
/**
|
||||||
|
* The tsconfig file for the project. e.g. `tsconfig.json`
|
||||||
|
*/
|
||||||
|
tsConfig?: string;
|
||||||
|
/**
|
||||||
|
* The entry point for the bundle. e.g. `src/main.ts`
|
||||||
|
*/
|
||||||
|
main?: string;
|
||||||
|
/**
|
||||||
|
* Secondary entry points for the bundle.
|
||||||
|
*/
|
||||||
|
additionalEntryPoints?: AdditionalEntryPoint[];
|
||||||
|
/**
|
||||||
|
* Assets to be copied over to the output path.
|
||||||
|
*/
|
||||||
|
assets?: Array<AssetGlob | string>;
|
||||||
|
/**
|
||||||
|
* Set <base href> for the resulting index.html.
|
||||||
|
*/
|
||||||
|
baseHref?: string;
|
||||||
|
/**
|
||||||
|
* Build the libraries from source. Default is `true`.
|
||||||
|
*/
|
||||||
|
buildLibsFromSource?: boolean;
|
||||||
|
|
||||||
|
commonChunk?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the output path before building.
|
||||||
|
*/
|
||||||
|
deleteOutputPath?: boolean;
|
||||||
|
/**
|
||||||
|
* The deploy path for the application. e.g. `/my-app/`
|
||||||
|
*/
|
||||||
|
deployUrl?: string;
|
||||||
|
/**
|
||||||
|
* Define external packages that will not be bundled.
|
||||||
|
* Use `all` to exclude all 3rd party packages, and `none` to bundle all packages.
|
||||||
|
* Use an array to exclude specific packages from the bundle.
|
||||||
|
* Default is `none`.
|
||||||
|
*/
|
||||||
|
externalDependencies?: 'all' | 'none' | string[];
|
||||||
|
/**
|
||||||
|
* Extract CSS as an external file. Default is `true`.
|
||||||
|
*/
|
||||||
|
extractCss?: boolean;
|
||||||
|
/**
|
||||||
|
* Extract licenses from 3rd party modules and add them to the output.
|
||||||
|
*/
|
||||||
|
extractLicenses?: boolean;
|
||||||
|
/**
|
||||||
|
* Replace files at build time. e.g. `[{ "replace": "src/a.dev.ts", "with": "src/a.prod.ts" }]`
|
||||||
|
*/
|
||||||
|
fileReplacements?: FileReplacement[];
|
||||||
|
/**
|
||||||
|
* Generate an `index.html` file if `index.html` is passed. Default is `true`
|
||||||
|
*/
|
||||||
|
generateIndexHtml?: boolean;
|
||||||
|
/**
|
||||||
|
* Generate a `package.json` file for the bundle. Useful for Node applications.
|
||||||
|
*/
|
||||||
|
generatePackageJson?: boolean;
|
||||||
|
/**
|
||||||
|
* Path to the `index.html`.
|
||||||
|
*/
|
||||||
|
index?: string;
|
||||||
|
/**
|
||||||
|
* Mode to run the build in.
|
||||||
|
*/
|
||||||
|
mode?: Mode;
|
||||||
|
/**
|
||||||
|
* Set the memory limit for the type-checking process. Default is `2048`.
|
||||||
|
*/
|
||||||
|
memoryLimit?: number;
|
||||||
|
/**
|
||||||
|
* Use the source file name in output chunks. Useful for development or for Node.
|
||||||
|
*/
|
||||||
|
namedChunks?: boolean;
|
||||||
|
/**
|
||||||
|
* Optimize the bundle using Terser.
|
||||||
|
*/
|
||||||
|
optimization?: boolean | OptimizationOptions;
|
||||||
|
/**
|
||||||
|
* Specify the output filename for the bundle. Useful for Node applications that use `@nx/js:node` to serve.
|
||||||
|
*/
|
||||||
|
outputFileName?: string;
|
||||||
|
/**
|
||||||
|
* Use file hashes in the output filenames. Recommended for production web applications.
|
||||||
|
*/
|
||||||
|
outputHashing?: any;
|
||||||
|
/**
|
||||||
|
* Override `output.path` in webpack configuration. This setting is not recommended and exists for backwards compatibility.
|
||||||
|
*/
|
||||||
|
outputPath?: string;
|
||||||
|
/**
|
||||||
|
* Override `watchOptions.poll` in webpack configuration. This setting is not recommended and exists for backwards compatibility.
|
||||||
|
*/
|
||||||
|
poll?: number;
|
||||||
|
/**
|
||||||
|
* The polyfill file to use. Useful for supporting legacy browsers. e.g. `src/polyfills.ts`
|
||||||
|
*/
|
||||||
|
polyfills?: string;
|
||||||
|
/**
|
||||||
|
* Manually set the PostCSS configuration file. By default, PostCSS will look for `postcss.config.js` in the directory.
|
||||||
|
*/
|
||||||
|
postcssConfig?: string;
|
||||||
|
/**
|
||||||
|
* Display build progress in the terminal.
|
||||||
|
*/
|
||||||
|
progress?: boolean;
|
||||||
|
/**
|
||||||
|
* Add an additional chunk for the Webpack runtime. Defaults to `true` when `target === 'web'`.
|
||||||
|
*/
|
||||||
|
runtimeChunk?: boolean;
|
||||||
|
/**
|
||||||
|
* External scripts that will be included before the main application entry.
|
||||||
|
*/
|
||||||
|
scripts?: Array<ExtraEntryPointClass | string>;
|
||||||
|
/**
|
||||||
|
* Do not add a `overrides` and `resolutions` entries to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
||||||
|
*/
|
||||||
|
skipOverrides?: boolean;
|
||||||
|
/**
|
||||||
|
* Do not add a `packageManager` entry to the generated package.json file. Only works in conjunction with `generatePackageJson` option.
|
||||||
|
*/
|
||||||
|
skipPackageManager?: boolean;
|
||||||
|
/**
|
||||||
|
* Skip type checking. Default is `false`.
|
||||||
|
*/
|
||||||
|
skipTypeChecking?: boolean;
|
||||||
|
/**
|
||||||
|
* Skip type checking. Default is `false`.
|
||||||
|
*/
|
||||||
|
typeCheck?: boolean;
|
||||||
|
/**
|
||||||
|
* Generate source maps.
|
||||||
|
*/
|
||||||
|
sourceMap?: boolean | string;
|
||||||
|
/**
|
||||||
|
* When `true`, `process.env.NODE_ENV` will be excluded from the bundle. Useful for building a web application to run in a Node environment.
|
||||||
|
*/
|
||||||
|
ssr?: boolean;
|
||||||
|
/**
|
||||||
|
* Generate a `stats.json` file which can be analyzed using tools such as `webpack-bundle-analyzer`.
|
||||||
|
*/
|
||||||
|
statsJson?: boolean;
|
||||||
|
/**
|
||||||
|
* Options for the style preprocessor. e.g. `{ "includePaths": [] }` for SASS.
|
||||||
|
*/
|
||||||
|
stylePreprocessorOptions?: any;
|
||||||
|
/**
|
||||||
|
* External stylesheets that will be included with the application.
|
||||||
|
*/
|
||||||
|
styles?: Array<ExtraEntryPointClass | string>;
|
||||||
|
/**
|
||||||
|
* Enables the use of subresource integrity validation.
|
||||||
|
*/
|
||||||
|
subresourceIntegrity?: boolean;
|
||||||
|
/**
|
||||||
|
* Override the `target` option in webpack configuration. This setting is not recommended and exists for backwards compatibility.
|
||||||
|
*/
|
||||||
|
target?: string | string[];
|
||||||
|
/**
|
||||||
|
* List of TypeScript Compiler Transformers Plugins.
|
||||||
|
*/
|
||||||
|
transformers?: TransformerEntry[];
|
||||||
|
/**
|
||||||
|
* Generate a separate vendor chunk for 3rd party packages.
|
||||||
|
*/
|
||||||
|
vendorChunk?: boolean;
|
||||||
|
/**
|
||||||
|
* Log additional information for debugging purposes.
|
||||||
|
*/
|
||||||
|
verbose?: boolean;
|
||||||
|
/**
|
||||||
|
* Watch for file changes.
|
||||||
|
*/
|
||||||
|
watch?: boolean;
|
||||||
|
/**
|
||||||
|
* Set a public path for assets resources with absolute paths.
|
||||||
|
*/
|
||||||
|
publicPath?: string;
|
||||||
|
/**
|
||||||
|
* Whether to rebase absolute path for assets in postcss cli resources.
|
||||||
|
*/
|
||||||
|
rebaseRootRelative?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NormalizedNxAppRspackPluginOptions
|
||||||
|
extends NxAppRspackPluginOptions {
|
||||||
|
projectName: string;
|
||||||
|
root: string;
|
||||||
|
projectRoot: string;
|
||||||
|
sourceRoot: string;
|
||||||
|
configurationName: string;
|
||||||
|
targetName: string;
|
||||||
|
projectGraph: ProjectGraph;
|
||||||
|
outputFileName: string;
|
||||||
|
assets: AssetGlobPattern[];
|
||||||
|
}
|
||||||
30
packages/rspack/src/plugins/utils/normalize-entry.ts
Normal file
30
packages/rspack/src/plugins/utils/normalize-entry.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ExtraEntryPoint, NormalizedEntryPoint } from '../../utils/model';
|
||||||
|
|
||||||
|
export function normalizeExtraEntryPoints(
|
||||||
|
extraEntryPoints: ExtraEntryPoint[],
|
||||||
|
defaultBundleName: string
|
||||||
|
): NormalizedEntryPoint[] {
|
||||||
|
return extraEntryPoints.map((entry) => {
|
||||||
|
let normalizedEntry;
|
||||||
|
if (typeof entry === 'string') {
|
||||||
|
normalizedEntry = {
|
||||||
|
input: entry,
|
||||||
|
inject: true,
|
||||||
|
bundleName: defaultBundleName,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const { inject = true, ...newEntry } = entry;
|
||||||
|
let bundleName;
|
||||||
|
|
||||||
|
if (entry.bundleName) {
|
||||||
|
bundleName = entry.bundleName;
|
||||||
|
} else {
|
||||||
|
bundleName = defaultBundleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizedEntry = { ...newEntry, bundleName };
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedEntry;
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
import { interpolateName } from 'loader-utils';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { Declaration } from 'postcss';
|
||||||
|
import * as url from 'node:url';
|
||||||
|
import type { LoaderContext } from '@rspack/core';
|
||||||
|
|
||||||
|
function wrapUrl(url: string): string {
|
||||||
|
let wrappedUrl;
|
||||||
|
const hasSingleQuotes = url.indexOf("'") >= 0;
|
||||||
|
if (hasSingleQuotes) {
|
||||||
|
wrappedUrl = `"${url}"`;
|
||||||
|
} else {
|
||||||
|
wrappedUrl = `'${url}'`;
|
||||||
|
}
|
||||||
|
return `url(${wrappedUrl})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostcssCliResourcesOptions {
|
||||||
|
baseHref?: string;
|
||||||
|
deployUrl?: string;
|
||||||
|
resourcesOutputPath?: string;
|
||||||
|
rebaseRootRelative?: boolean;
|
||||||
|
filename: string;
|
||||||
|
loader: LoaderContext<unknown>;
|
||||||
|
publicPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolve(
|
||||||
|
file: string,
|
||||||
|
base: string,
|
||||||
|
resolver: (file: string, base: string) => Promise<boolean | string>
|
||||||
|
): Promise<boolean | string> {
|
||||||
|
try {
|
||||||
|
return await resolver(`./${file}`, base);
|
||||||
|
} catch {
|
||||||
|
return resolver(file, base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.postcss = true;
|
||||||
|
|
||||||
|
export function PostcssCliResources(options: PostcssCliResourcesOptions) {
|
||||||
|
const {
|
||||||
|
deployUrl = '',
|
||||||
|
baseHref = '',
|
||||||
|
resourcesOutputPath = '',
|
||||||
|
rebaseRootRelative = false,
|
||||||
|
filename,
|
||||||
|
loader,
|
||||||
|
publicPath = '',
|
||||||
|
} = options;
|
||||||
|
const dedupeSlashes = (url: string) => url.replace(/\/\/+/g, '/');
|
||||||
|
const process = async (
|
||||||
|
inputUrl: string,
|
||||||
|
context: string,
|
||||||
|
resourceCache: Map<string, string>
|
||||||
|
) => {
|
||||||
|
// If root-relative, absolute or protocol relative url, leave as is
|
||||||
|
if (/^((?:\w+:)?\/\/|data:|chrome:|#)/.test(inputUrl)) {
|
||||||
|
return inputUrl;
|
||||||
|
}
|
||||||
|
if (!rebaseRootRelative && /^\//.test(inputUrl)) {
|
||||||
|
return inputUrl;
|
||||||
|
}
|
||||||
|
// If starts with a caret, remove and return remainder
|
||||||
|
// this supports bypassing asset processing
|
||||||
|
if (inputUrl.startsWith('^')) {
|
||||||
|
return inputUrl.slice(1);
|
||||||
|
}
|
||||||
|
const cacheKey = path.resolve(context, inputUrl);
|
||||||
|
const cachedUrl = resourceCache.get(cacheKey);
|
||||||
|
if (cachedUrl) {
|
||||||
|
return cachedUrl;
|
||||||
|
}
|
||||||
|
if (inputUrl.startsWith('~')) {
|
||||||
|
inputUrl = inputUrl.slice(1);
|
||||||
|
}
|
||||||
|
if (inputUrl.startsWith('/')) {
|
||||||
|
let outputUrl = '';
|
||||||
|
if (deployUrl.match(/:\/\//) || deployUrl.startsWith('/')) {
|
||||||
|
// If deployUrl is absolute or root relative, ignore baseHref & use deployUrl as is.
|
||||||
|
outputUrl = `${deployUrl.replace(/\/$/, '')}${inputUrl}`;
|
||||||
|
} else if (baseHref.match(/:\/\//)) {
|
||||||
|
// If baseHref contains a scheme, include it as is.
|
||||||
|
outputUrl =
|
||||||
|
baseHref.replace(/\/$/, '') +
|
||||||
|
dedupeSlashes(`/${deployUrl}/${inputUrl}`);
|
||||||
|
} else {
|
||||||
|
// Join together base-href, deploy-url and the original URL.
|
||||||
|
outputUrl = dedupeSlashes(
|
||||||
|
`/${baseHref}/${deployUrl}/${publicPath}/${inputUrl}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resourceCache.set(cacheKey, outputUrl);
|
||||||
|
return outputUrl;
|
||||||
|
}
|
||||||
|
const { pathname, hash, search } = url.parse(inputUrl.replace(/\\/g, '/'));
|
||||||
|
const resolver = (file: string, base: string) =>
|
||||||
|
new Promise<boolean | string>((resolve, reject) => {
|
||||||
|
loader.resolve(base, decodeURI(file), (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const result = await resolve(pathname as string, context, resolver);
|
||||||
|
return new Promise<boolean | string>((resolve, reject) => {
|
||||||
|
loader.fs.readFile(result as string, (err: Error, content: Buffer) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputPath = interpolateName(
|
||||||
|
{ resourcePath: result } as LoaderContext<unknown>,
|
||||||
|
filename,
|
||||||
|
{ content }
|
||||||
|
);
|
||||||
|
if (resourcesOutputPath) {
|
||||||
|
outputPath = path.posix.join(resourcesOutputPath, outputPath);
|
||||||
|
}
|
||||||
|
loader.addDependency(result as string);
|
||||||
|
loader.emitFile(outputPath, content, undefined);
|
||||||
|
let outputUrl = outputPath.replace(/\\/g, '/');
|
||||||
|
if (hash || search) {
|
||||||
|
outputUrl = url.format({ pathname: outputUrl, hash, search });
|
||||||
|
}
|
||||||
|
const loaderOptions: any = loader.loaders[loader.loaderIndex].options;
|
||||||
|
if (deployUrl && loaderOptions.ident !== 'extracted') {
|
||||||
|
outputUrl = url.resolve(deployUrl, outputUrl);
|
||||||
|
}
|
||||||
|
resourceCache.set(cacheKey, outputUrl);
|
||||||
|
resolve(outputUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'postcss-cli-resources',
|
||||||
|
Once(root) {
|
||||||
|
const urlDeclarations: Array<Declaration> = [];
|
||||||
|
/**
|
||||||
|
* TODO: Explore if this can be rewritten using the new `Declaration()`
|
||||||
|
* listener added in postcss v8
|
||||||
|
*/
|
||||||
|
root.walkDecls((decl) => {
|
||||||
|
if (decl.value && decl.value.includes('url')) {
|
||||||
|
urlDeclarations.push(decl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (urlDeclarations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resourceCache = new Map<string, string>();
|
||||||
|
return Promise.all(
|
||||||
|
urlDeclarations.map(async (decl) => {
|
||||||
|
const value = decl.value;
|
||||||
|
const urlRegex = /url(?:\(\s*(['"]?))(.*?)(?:\1\s*\))/g;
|
||||||
|
const segments: string[] = [];
|
||||||
|
let match;
|
||||||
|
let lastIndex = 0;
|
||||||
|
let modified = false;
|
||||||
|
// We want to load it relative to the file that imports
|
||||||
|
const inputFile = decl.source && decl.source.input.file;
|
||||||
|
const context =
|
||||||
|
(inputFile && path.dirname(inputFile)) || loader.context;
|
||||||
|
while ((match = urlRegex.exec(value))) {
|
||||||
|
const originalUrl = match[2];
|
||||||
|
let processedUrl;
|
||||||
|
try {
|
||||||
|
processedUrl = await process(originalUrl, context, resourceCache);
|
||||||
|
} catch (err) {
|
||||||
|
loader.emitError(decl.error(err.message, { word: originalUrl }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (lastIndex < match.index) {
|
||||||
|
segments.push(value.slice(lastIndex, match.index));
|
||||||
|
}
|
||||||
|
if (!processedUrl || originalUrl === processedUrl) {
|
||||||
|
segments.push(match[0]);
|
||||||
|
} else {
|
||||||
|
segments.push(wrapUrl(processedUrl));
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
lastIndex = match.index + match[0].length;
|
||||||
|
}
|
||||||
|
if (lastIndex < value.length) {
|
||||||
|
segments.push(value.slice(lastIndex));
|
||||||
|
}
|
||||||
|
if (modified) {
|
||||||
|
decl.value = segments.join('');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
import { interpolateName } from 'loader-utils';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {
|
||||||
|
sources,
|
||||||
|
EntryPlugin,
|
||||||
|
type Compiler,
|
||||||
|
type Compilation,
|
||||||
|
} from '@rspack/core';
|
||||||
|
|
||||||
|
export interface ScriptsRspackPluginOptions {
|
||||||
|
name: string;
|
||||||
|
sourceMap: boolean;
|
||||||
|
scripts: string[];
|
||||||
|
filename: string;
|
||||||
|
basePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScriptOutput {
|
||||||
|
filename: string;
|
||||||
|
source: sources.Source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDependencies(compilation: any, scripts: string[]): void {
|
||||||
|
for (const script of scripts) {
|
||||||
|
compilation.fileDependencies.add(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hook(
|
||||||
|
compiler: any,
|
||||||
|
action: (compilation: any, callback: (err?: Error) => void) => void
|
||||||
|
) {
|
||||||
|
compiler.hooks.thisCompilation.tap(
|
||||||
|
'scripts-rspack-plugin',
|
||||||
|
(compilation: any) => {
|
||||||
|
compilation.hooks.additionalAssets.tapAsync(
|
||||||
|
'scripts-rspack-plugin',
|
||||||
|
(callback: (err?: Error) => void) => action(compilation, callback)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ScriptsRspackPlugin {
|
||||||
|
private _lastBuildTime?: number;
|
||||||
|
private _cachedOutput?: ScriptOutput;
|
||||||
|
|
||||||
|
constructor(private options: Partial<ScriptsRspackPluginOptions> = {}) {}
|
||||||
|
|
||||||
|
private _insertOutput(
|
||||||
|
compiler: Compiler,
|
||||||
|
compilation: Compilation,
|
||||||
|
{ filename, source }: ScriptOutput,
|
||||||
|
cached = false
|
||||||
|
) {
|
||||||
|
new EntryPlugin(compiler.context, this.options.name).apply(compiler);
|
||||||
|
|
||||||
|
compilation.assets[filename] = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(compiler: Compiler): void {
|
||||||
|
if (!this.options.scripts || this.options.scripts.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scripts = this.options.scripts
|
||||||
|
.filter((script) => !!script)
|
||||||
|
.map((script) => path.resolve(this.options.basePath || '', script));
|
||||||
|
|
||||||
|
hook(compiler, (compilation: Compilation, callback) => {
|
||||||
|
const sourceGetters = scripts.map((fullPath) => {
|
||||||
|
return new Promise<sources.Source>((resolve, reject) => {
|
||||||
|
compilation.inputFileSystem.readFile(
|
||||||
|
fullPath,
|
||||||
|
(err: Error, data: Buffer) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = data.toString();
|
||||||
|
|
||||||
|
let source;
|
||||||
|
if (this.options.sourceMap) {
|
||||||
|
// TODO: Look for source map file (for '.min' scripts, etc.)
|
||||||
|
|
||||||
|
let adjustedPath = fullPath;
|
||||||
|
if (this.options.basePath) {
|
||||||
|
adjustedPath = path.relative(this.options.basePath, fullPath);
|
||||||
|
}
|
||||||
|
source = new sources.OriginalSource(content, adjustedPath);
|
||||||
|
} else {
|
||||||
|
source = new sources.RawSource(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(source);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(sourceGetters)
|
||||||
|
.then((_sources) => {
|
||||||
|
const concatSource = new sources.ConcatSource();
|
||||||
|
_sources.forEach((source) => {
|
||||||
|
concatSource.add(source);
|
||||||
|
concatSource.add('\n;');
|
||||||
|
});
|
||||||
|
|
||||||
|
const combinedSource = new sources.CachedSource(concatSource);
|
||||||
|
const filename = interpolateName(
|
||||||
|
{ resourcePath: 'scripts.js' },
|
||||||
|
this.options.filename as string,
|
||||||
|
{ content: combinedSource.source() }
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = { filename, source: combinedSource };
|
||||||
|
this._insertOutput(compiler, compilation, output);
|
||||||
|
this._cachedOutput = output;
|
||||||
|
addDependencies(compilation, scripts);
|
||||||
|
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
.catch((err: Error) => callback(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,13 @@
|
|||||||
import type { ExecutorContext } from '@nx/devkit';
|
import {
|
||||||
|
ExecutorContext,
|
||||||
|
readCachedProjectGraph,
|
||||||
|
readProjectsConfigurationFromProjectGraph,
|
||||||
|
workspaceRoot,
|
||||||
|
} from '@nx/devkit';
|
||||||
import type { Configuration } from '@rspack/core';
|
import type { Configuration } from '@rspack/core';
|
||||||
|
import { readNxJson } from 'nx/src/config/configuration';
|
||||||
|
|
||||||
import { SharedConfigContext } from './model';
|
import { NormalizedRspackExecutorSchema } from '../executors/rspack/schema';
|
||||||
|
|
||||||
export const nxRspackComposablePlugin = 'nxRspackComposablePlugin';
|
export const nxRspackComposablePlugin = 'nxRspackComposablePlugin';
|
||||||
|
|
||||||
@ -12,7 +18,7 @@ export function isNxRspackComposablePlugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NxRspackExecutionContext {
|
export interface NxRspackExecutionContext {
|
||||||
options: unknown;
|
options: NormalizedRspackExecutorSchema;
|
||||||
context: ExecutorContext;
|
context: ExecutorContext;
|
||||||
configuration?: string;
|
configuration?: string;
|
||||||
}
|
}
|
||||||
@ -27,23 +33,82 @@ export interface AsyncNxComposableRspackPlugin {
|
|||||||
| Promise<Configuration>;
|
| Promise<Configuration>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function composePlugins(...plugins: any[]) {
|
export function composePlugins(
|
||||||
return Object.defineProperty(
|
...plugins: (
|
||||||
|
| NxComposableRspackPlugin
|
||||||
|
| AsyncNxComposableRspackPlugin
|
||||||
|
| Promise<NxComposableRspackPlugin | AsyncNxComposableRspackPlugin>
|
||||||
|
)[]
|
||||||
|
) {
|
||||||
|
return Object.assign(
|
||||||
async function combined(
|
async function combined(
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
ctx: SharedConfigContext
|
ctx: NxRspackExecutionContext
|
||||||
): Promise<Configuration> {
|
): Promise<Configuration> {
|
||||||
|
// Rspack may be calling us as a standard config function.
|
||||||
|
// Build up Nx context from environment variables.
|
||||||
|
// This is to enable `@nx/webpack/plugin` to work with existing projects.
|
||||||
|
if (ctx['env']) {
|
||||||
|
ensureNxRspackExecutionContext(ctx);
|
||||||
|
// Build this from scratch since what webpack passes us is the env, not config,
|
||||||
|
// and `withNX()` creates a new config object anyway.
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
const fn = await plugin;
|
const fn = await plugin;
|
||||||
config = await fn(config, ctx);
|
config = await fn(config, ctx);
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
nxRspackComposablePlugin,
|
|
||||||
{
|
{
|
||||||
value: true,
|
[nxRspackComposablePlugin]: true,
|
||||||
enumerable: false,
|
|
||||||
writable: false,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function composePluginsSync(...plugins: NxComposableRspackPlugin[]) {
|
||||||
|
return Object.assign(
|
||||||
|
function combined(
|
||||||
|
config: Configuration,
|
||||||
|
ctx: NxRspackExecutionContext
|
||||||
|
): Configuration {
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
config = plugin(config, ctx);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[nxRspackComposablePlugin]: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureNxRspackExecutionContext(ctx: NxRspackExecutionContext): void {
|
||||||
|
const projectName = process.env.NX_TASK_TARGET_PROJECT;
|
||||||
|
const targetName = process.env.NX_TASK_TARGET_TARGET;
|
||||||
|
const configurationName = process.env.NX_TASK_TARGET_CONFIGURATION;
|
||||||
|
const projectGraph = readCachedProjectGraph();
|
||||||
|
const projectNode = projectGraph.nodes[projectName];
|
||||||
|
ctx.options ??= {
|
||||||
|
root: workspaceRoot,
|
||||||
|
projectRoot: projectNode.data.root,
|
||||||
|
sourceRoot: projectNode.data.sourceRoot ?? projectNode.data.root,
|
||||||
|
// These aren't actually needed since NxRspackPlugin and withNx both support them being undefined.
|
||||||
|
assets: undefined,
|
||||||
|
outputFileName: undefined,
|
||||||
|
rspackConfig: undefined,
|
||||||
|
};
|
||||||
|
ctx.context ??= {
|
||||||
|
projectName,
|
||||||
|
targetName,
|
||||||
|
configurationName,
|
||||||
|
projectsConfigurations:
|
||||||
|
readProjectsConfigurationFromProjectGraph(projectGraph),
|
||||||
|
nxJsonConfiguration: readNxJson(workspaceRoot),
|
||||||
|
cwd: process.cwd(),
|
||||||
|
root: workspaceRoot,
|
||||||
|
isVerbose: process.env['NX_VERBOSE_LOGGING'] === 'true',
|
||||||
|
projectGraph,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -172,6 +172,7 @@ export function addOrChangeBuildTarget(
|
|||||||
// If standalone project then use the project's name in dist.
|
// If standalone project then use the project's name in dist.
|
||||||
project.root === '.' ? project.name : project.root
|
project.root === '.' ? project.name : project.root
|
||||||
),
|
),
|
||||||
|
index: joinPathFragments(project.root, 'src/index.html'),
|
||||||
main: determineMain(tree, options),
|
main: determineMain(tree, options),
|
||||||
tsConfig: determineTsConfig(tree, options),
|
tsConfig: determineTsConfig(tree, options),
|
||||||
rspackConfig: joinPathFragments(project.root, 'rspack.config.js'),
|
rspackConfig: joinPathFragments(project.root, 'rspack.config.js'),
|
||||||
|
|||||||
@ -1,7 +1,19 @@
|
|||||||
import { ExecutorContext } from '@nx/devkit';
|
export interface ExtraEntryPointClass {
|
||||||
import { RspackExecutorSchema } from '../executors/rspack/schema';
|
bundleName?: string;
|
||||||
|
inject?: boolean;
|
||||||
export interface SharedConfigContext {
|
input: string;
|
||||||
options: RspackExecutorSchema;
|
lazy?: boolean;
|
||||||
context: ExecutorContext;
|
}
|
||||||
|
|
||||||
|
export type ExtraEntryPoint = ExtraEntryPointClass | string;
|
||||||
|
|
||||||
|
export type NormalizedEntryPoint = Required<ExtraEntryPointClass>;
|
||||||
|
|
||||||
|
export interface EmittedFile {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
file: string;
|
||||||
|
extension: string;
|
||||||
|
initial: boolean;
|
||||||
|
asset?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { DefinePlugin } from '@rspack/core';
|
import { DefinePlugin } from '@rspack/core';
|
||||||
import { SharedConfigContext } from '../../model';
|
|
||||||
import {
|
import {
|
||||||
ModuleFederationConfig,
|
ModuleFederationConfig,
|
||||||
NxModuleFederationConfigOverride,
|
NxModuleFederationConfigOverride,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { getModuleFederationConfig } from './utils';
|
import { getModuleFederationConfig } from './utils';
|
||||||
|
import { NxRspackExecutionContext } from '../../config';
|
||||||
|
|
||||||
export async function withModuleFederationForSSR(
|
export async function withModuleFederationForSSR(
|
||||||
options: ModuleFederationConfig,
|
options: ModuleFederationConfig,
|
||||||
@ -19,7 +19,7 @@ export async function withModuleFederationForSSR(
|
|||||||
isServer: true,
|
isServer: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (config, { context }: SharedConfigContext) => {
|
return (config, { context }: NxRspackExecutionContext) => {
|
||||||
config.target = 'async-node';
|
config.target = 'async-node';
|
||||||
config.output.uniqueName = options.name;
|
config.output.uniqueName = options.name;
|
||||||
config.optimization = {
|
config.optimization = {
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
|
import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
|
||||||
import type { Configuration } from '@rspack/core';
|
import type { Configuration } from '@rspack/core';
|
||||||
import { DefinePlugin } from '@rspack/core';
|
import { DefinePlugin } from '@rspack/core';
|
||||||
import { SharedConfigContext } from '../../model';
|
|
||||||
import {
|
import {
|
||||||
ModuleFederationConfig,
|
ModuleFederationConfig,
|
||||||
NxModuleFederationConfigOverride,
|
NxModuleFederationConfigOverride,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { getModuleFederationConfig } from './utils';
|
import { getModuleFederationConfig } from './utils';
|
||||||
|
import { NxRspackExecutionContext } from '../../config';
|
||||||
|
|
||||||
const isVarOrWindow = (libType?: string) =>
|
const isVarOrWindow = (libType?: string) =>
|
||||||
libType === 'var' || libType === 'window';
|
libType === 'var' || libType === 'window';
|
||||||
@ -31,7 +31,7 @@ export async function withModuleFederation(
|
|||||||
|
|
||||||
return function makeConfig(
|
return function makeConfig(
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
{ context }: SharedConfigContext
|
{ context }: NxRspackExecutionContext
|
||||||
): Configuration {
|
): Configuration {
|
||||||
config.output.uniqueName = options.name;
|
config.output.uniqueName = options.name;
|
||||||
config.output.publicPath = 'auto';
|
config.output.publicPath = 'auto';
|
||||||
|
|||||||
@ -25,6 +25,12 @@ export async function readRspackOptions(
|
|||||||
root: workspaceRoot,
|
root: workspaceRoot,
|
||||||
projectRoot: '',
|
projectRoot: '',
|
||||||
sourceRoot: '',
|
sourceRoot: '',
|
||||||
|
outputFileName: '',
|
||||||
|
assets: [],
|
||||||
|
main: '',
|
||||||
|
tsConfig: '',
|
||||||
|
outputPath: '',
|
||||||
|
rspackConfig: '',
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
root: workspaceRoot,
|
root: workspaceRoot,
|
||||||
|
|||||||
@ -11,13 +11,13 @@ import * as path from 'path';
|
|||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { GeneratePackageJsonPlugin } from '../plugins/generate-package-json-plugin';
|
import { GeneratePackageJsonPlugin } from '../plugins/generate-package-json-plugin';
|
||||||
import { getCopyPatterns } from './get-copy-patterns';
|
import { getCopyPatterns } from './get-copy-patterns';
|
||||||
import { SharedConfigContext } from './model';
|
|
||||||
import { normalizeAssets } from './normalize-assets';
|
import { normalizeAssets } from './normalize-assets';
|
||||||
|
import { NxRspackExecutionContext } from './config';
|
||||||
|
|
||||||
export function withNx(_opts = {}) {
|
export function withNx(_opts = {}) {
|
||||||
return function makeConfig(
|
return function makeConfig(
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
{ options, context }: SharedConfigContext
|
{ options, context }: NxRspackExecutionContext
|
||||||
): Configuration {
|
): Configuration {
|
||||||
const isProd =
|
const isProd =
|
||||||
process.env.NODE_ENV === 'production' || options.mode === 'production';
|
process.env.NODE_ENV === 'production' || options.mode === 'production';
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Configuration } from '@rspack/core';
|
import { Configuration } from '@rspack/core';
|
||||||
import { SharedConfigContext } from './model';
|
|
||||||
import { withWeb } from './with-web';
|
import { withWeb } from './with-web';
|
||||||
|
import { NxRspackExecutionContext } from './config';
|
||||||
|
|
||||||
export function withReact(opts = {}) {
|
export function withReact(opts = {}) {
|
||||||
return function makeConfig(
|
return function makeConfig(
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
{ options, context }: SharedConfigContext
|
{ options, context }: NxRspackExecutionContext
|
||||||
): Configuration {
|
): Configuration {
|
||||||
const isDev =
|
const isDev =
|
||||||
process.env.NODE_ENV === 'development' || options.mode === 'development';
|
process.env.NODE_ENV === 'development' || options.mode === 'development';
|
||||||
|
|||||||
@ -1,132 +1,51 @@
|
|||||||
import { Configuration, RuleSetRule, rspack } from '@rspack/core';
|
import { Configuration } from '@rspack/core';
|
||||||
import * as path from 'path';
|
import { ExtraEntryPointClass } from './model';
|
||||||
import { SharedConfigContext } from './model';
|
import { applyWebConfig } from '../plugins/utils/apply-web-config';
|
||||||
|
import { NxRspackExecutionContext } from './config';
|
||||||
|
|
||||||
export interface WithWebOptions {
|
export interface WithWebOptions {
|
||||||
|
baseHref?: string;
|
||||||
|
deployUrl?: string;
|
||||||
|
extractCss?: boolean;
|
||||||
|
generateIndexHtml?: boolean;
|
||||||
|
index?: string;
|
||||||
|
postcssConfig?: string;
|
||||||
|
scripts?: Array<ExtraEntryPointClass | string>;
|
||||||
|
styles?: Array<ExtraEntryPointClass | string>;
|
||||||
|
subresourceIntegrity?: boolean;
|
||||||
stylePreprocessorOptions?: {
|
stylePreprocessorOptions?: {
|
||||||
includePaths?: string[];
|
includePaths?: string[];
|
||||||
};
|
};
|
||||||
cssModules?: boolean;
|
cssModules?: boolean;
|
||||||
|
ssr?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withWeb(opts: WithWebOptions = {}) {
|
const processed = new Set();
|
||||||
|
|
||||||
|
export function withWeb(pluginOptions: WithWebOptions = {}) {
|
||||||
return function makeConfig(
|
return function makeConfig(
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
{ options, context }: SharedConfigContext
|
{ options, context }: NxRspackExecutionContext
|
||||||
): Configuration {
|
): Configuration {
|
||||||
const isProd =
|
if (processed.has(config)) {
|
||||||
process.env.NODE_ENV === 'production' || options.mode === 'production';
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
const projectRoot = path.join(
|
applyWebConfig(
|
||||||
context.root,
|
{
|
||||||
context.projectGraph.nodes[context.projectName].data.root
|
...options,
|
||||||
|
...pluginOptions,
|
||||||
|
root: context.root,
|
||||||
|
projectName: context.projectName,
|
||||||
|
targetName: context.targetName,
|
||||||
|
configurationName: context.configurationName,
|
||||||
|
projectGraph: context.projectGraph,
|
||||||
|
},
|
||||||
|
config
|
||||||
);
|
);
|
||||||
|
|
||||||
const includePaths: string[] = [];
|
processed.add(config);
|
||||||
if (opts?.stylePreprocessorOptions?.includePaths?.length > 0) {
|
return config;
|
||||||
opts.stylePreprocessorOptions.includePaths.forEach(
|
|
||||||
(includePath: string) =>
|
|
||||||
includePaths.push(path.resolve(context.root, includePath))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let lessPathOptions: { paths?: string[] } = {};
|
|
||||||
|
|
||||||
if (includePaths.length > 0) {
|
|
||||||
lessPathOptions = {
|
|
||||||
paths: includePaths,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
target: config.target ?? 'web',
|
|
||||||
experiments: {
|
|
||||||
css: true,
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
...config.module,
|
|
||||||
rules: [
|
|
||||||
...(config.module.rules || []),
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
type: opts?.cssModules ? 'css/module' : undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
type: 'css',
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('postcss-loader'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$|\.sass$/,
|
|
||||||
type: opts?.cssModules ? 'css/module' : undefined,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('sass-loader'),
|
|
||||||
options: {
|
|
||||||
sourceMap: !!options.sourceMap,
|
|
||||||
sassOptions: {
|
|
||||||
fiber: false,
|
|
||||||
// bootstrap-sass requires a minimum precision of 8
|
|
||||||
precision: 8,
|
|
||||||
includePaths,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /.less$/,
|
|
||||||
type: opts?.cssModules ? 'css/module' : undefined,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('less-loader'),
|
|
||||||
options: {
|
|
||||||
sourceMap: !!options.sourceMap,
|
|
||||||
lessOptions: {
|
|
||||||
javascriptEnabled: true,
|
|
||||||
...lessPathOptions,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.styl$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('stylus-loader'),
|
|
||||||
options: {
|
|
||||||
sourceMap: !!options.sourceMap,
|
|
||||||
stylusOptions: {
|
|
||||||
include: includePaths,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
].filter((a): a is RuleSetRule => !!a),
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
...config.plugins,
|
|
||||||
new rspack.HtmlRspackPlugin({
|
|
||||||
template: options.indexHtml
|
|
||||||
? path.join(context.root, options.indexHtml)
|
|
||||||
: path.join(projectRoot, 'src/index.html'),
|
|
||||||
...(options.baseHref ? { base: { href: options.baseHref } } : {}),
|
|
||||||
}),
|
|
||||||
new rspack.EnvironmentPlugin({
|
|
||||||
NODE_ENV: isProd ? 'production' : 'development',
|
|
||||||
}),
|
|
||||||
new rspack.DefinePlugin(
|
|
||||||
getClientEnvironment(isProd ? 'production' : undefined).stringified
|
|
||||||
),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export function applyWebConfig(
|
|||||||
useNormalizedEntry?: boolean;
|
useNormalizedEntry?: boolean;
|
||||||
} = {}
|
} = {}
|
||||||
): void {
|
): void {
|
||||||
if (!process.env['NX_TASK_TARGET_PROJECT']) return;
|
if (global.NX_GRAPH_CREATION) return;
|
||||||
|
|
||||||
// Defaults that was applied from executor schema previously.
|
// Defaults that was applied from executor schema previously.
|
||||||
options.runtimeChunk ??= true; // need this for HMR and other things to work
|
options.runtimeChunk ??= true; // need this for HMR and other things to work
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user