feat(rspack): move logic for withNx to applyBaseConfig and bring in line with webpack (#28825)
<!-- 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 --> `withNx` 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 `withNx` in line with `@nx/webpack` ## Notes
This commit is contained in:
parent
cd121bd5ee
commit
048f7c61af
@ -86,13 +86,15 @@ describe('React Applications', () => {
|
||||
`
|
||||
);
|
||||
|
||||
runCLI(`build ${appName}`);
|
||||
runCLI(`build ${appName}`, { verbose: true });
|
||||
|
||||
checkFilesExist(`dist/${appName}/index.html`);
|
||||
|
||||
if (runE2ETests()) {
|
||||
// TODO(Colum): investigate why webkit is failing
|
||||
const e2eResults = runCLI(`e2e ${appName}-e2e -- --project=chromium`);
|
||||
const e2eResults = runCLI(`e2e ${appName}-e2e -- --project=chromium`, {
|
||||
verbose: true,
|
||||
});
|
||||
expect(e2eResults).toContain('Successfully ran target e2e for project');
|
||||
expect(await killPorts()).toBeTruthy();
|
||||
}
|
||||
|
||||
@ -139,11 +139,11 @@ describe('rspack e2e', () => {
|
||||
result = runCLI(`build ${app3}`);
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
// Make sure expected files are present.
|
||||
expect(listFiles(`dist/${app3}`)).toHaveLength(2);
|
||||
expect(listFiles(`dist/${app3}`)).toHaveLength(3);
|
||||
|
||||
result = runCLI(`build ${app3} --generatePackageJson=true`);
|
||||
expect(result).toContain('Successfully ran target build');
|
||||
// Make sure expected files are present.
|
||||
expect(listFiles(`dist/${app3}`)).toHaveLength(4);
|
||||
expect(listFiles(`dist/${app3}`)).toHaveLength(5);
|
||||
}, 200_000);
|
||||
});
|
||||
|
||||
@ -32,23 +32,25 @@
|
||||
"@rspack/dev-server": "^1.0.4",
|
||||
"@rspack/plugin-react-refresh": "^1.0.0",
|
||||
"autoprefixer": "^10.4.9",
|
||||
"browserslist": "^4.21.4",
|
||||
"chalk": "~4.1.0",
|
||||
"css-loader": "^6.4.0",
|
||||
"enquirer": "~2.3.6",
|
||||
"express": "^4.19.2",
|
||||
"fork-ts-checker-webpack-plugin": "7.2.13",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
"less-loader": "11.1.0",
|
||||
"license-webpack-plugin": "^4.0.2",
|
||||
"loader-utils": "^2.0.3",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.2.0",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"style-loader": "^3.3.0",
|
||||
"postcss-import": "~14.1.0",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"postcss": "^8.4.38",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"tslib": "^2.3.0",
|
||||
"webpack-subresource-integrity": "^5.1.0"
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@module-federation/enhanced": "~0.6.0",
|
||||
|
||||
@ -11,6 +11,7 @@ import { createCompiler, isMultiCompiler } from '../../utils/create-compiler';
|
||||
import { isMode } from '../../utils/mode-utils';
|
||||
import { getDevServerOptions } from './lib/get-dev-server-config';
|
||||
import { DevServerExecutorSchema } from './schema';
|
||||
import { normalizeOptions } from '../rspack/lib/normalize-options';
|
||||
|
||||
type DevServer = Configuration['devServer'];
|
||||
export default async function* runExecutor(
|
||||
@ -30,14 +31,28 @@ export default async function* runExecutor(
|
||||
|
||||
const buildOptions = readTargetOptions(buildTarget, context);
|
||||
|
||||
process.env.NX_BUILD_LIBS_FROM_SOURCE = `${buildOptions.buildLibsFromSource}`;
|
||||
process.env.NX_BUILD_TARGET = options.buildTarget;
|
||||
|
||||
const metadata = context.projectsConfigurations.projects[context.projectName];
|
||||
const sourceRoot = metadata.sourceRoot;
|
||||
const normalizedBuildOptions = normalizeOptions(
|
||||
buildOptions,
|
||||
context.root,
|
||||
metadata.root,
|
||||
sourceRoot
|
||||
);
|
||||
let devServerConfig: DevServer = getDevServerOptions(
|
||||
context.root,
|
||||
options,
|
||||
buildOptions
|
||||
normalizedBuildOptions
|
||||
);
|
||||
|
||||
const compiler = await createCompiler(
|
||||
{ ...buildOptions, devServer: devServerConfig, mode: options.mode },
|
||||
{
|
||||
...normalizedBuildOptions,
|
||||
devServer: devServerConfig,
|
||||
mode: options.mode,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ export function getDevServerOptions(
|
||||
|
||||
const config: RspackDevServerConfiguration = {
|
||||
host: serveOptions.host,
|
||||
port: serveOptions.port,
|
||||
port: serveOptions.port ?? 4200,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
historyApiFallback: {
|
||||
index:
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import { resolve } from 'path';
|
||||
|
||||
import {
|
||||
normalizeAssets,
|
||||
normalizeFileReplacements,
|
||||
} from '../../../plugins/utils/plugins/normalize-options';
|
||||
import type {
|
||||
RspackExecutorSchema,
|
||||
NormalizedRspackExecutorSchema,
|
||||
} from '../schema';
|
||||
|
||||
export function normalizeOptions(
|
||||
options: RspackExecutorSchema,
|
||||
root: string,
|
||||
projectRoot: string,
|
||||
sourceRoot: string
|
||||
): NormalizedRspackExecutorSchema {
|
||||
const normalizedOptions = {
|
||||
...options,
|
||||
root,
|
||||
projectRoot,
|
||||
sourceRoot,
|
||||
target: options.target ?? 'web',
|
||||
outputFileName: options.outputFileName ?? 'main.js',
|
||||
rspackConfig: normalizePluginPath(options.rspackConfig, root),
|
||||
fileReplacements: normalizeFileReplacements(root, options.fileReplacements),
|
||||
optimization:
|
||||
typeof options.optimization !== 'object'
|
||||
? {
|
||||
scripts: options.optimization,
|
||||
styles: options.optimization,
|
||||
}
|
||||
: options.optimization,
|
||||
};
|
||||
if (options.assets) {
|
||||
normalizedOptions.assets = normalizeAssets(
|
||||
options.assets,
|
||||
root,
|
||||
sourceRoot,
|
||||
projectRoot,
|
||||
false // executor assets are relative to workspace root for consistency
|
||||
);
|
||||
}
|
||||
return normalizedOptions as NormalizedRspackExecutorSchema;
|
||||
}
|
||||
|
||||
export function normalizePluginPath(pluginPath: void | string, root: string) {
|
||||
if (!pluginPath) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
return require.resolve(pluginPath);
|
||||
} catch {
|
||||
return resolve(root, pluginPath);
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import * as path from 'path';
|
||||
import { createCompiler, isMultiCompiler } from '../../utils/create-compiler';
|
||||
import { isMode } from '../../utils/mode-utils';
|
||||
import { RspackExecutorSchema } from './schema';
|
||||
import { normalizeOptions } from './lib/normalize-options';
|
||||
|
||||
export default async function* runExecutor(
|
||||
options: RspackExecutorSchema,
|
||||
@ -28,8 +29,16 @@ export default async function* runExecutor(
|
||||
force: true,
|
||||
recursive: true,
|
||||
});
|
||||
const metadata = context.projectsConfigurations.projects[context.projectName];
|
||||
const sourceRoot = metadata.sourceRoot;
|
||||
const normalizedOptions = normalizeOptions(
|
||||
options,
|
||||
context.root,
|
||||
metadata.root,
|
||||
sourceRoot
|
||||
);
|
||||
|
||||
const compiler = await createCompiler(options, context);
|
||||
const compiler = await createCompiler(normalizedOptions, context);
|
||||
|
||||
const iterable = createAsyncIterable<{
|
||||
success: boolean;
|
||||
@ -58,7 +67,11 @@ export default async function* runExecutor(
|
||||
}
|
||||
next({
|
||||
success: !stats.hasErrors(),
|
||||
outfile: path.resolve(context.root, options.outputPath, 'main.js'),
|
||||
outfile: path.resolve(
|
||||
context.root,
|
||||
normalizedOptions.outputPath,
|
||||
'main.js'
|
||||
),
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -90,7 +103,11 @@ export default async function* runExecutor(
|
||||
}
|
||||
next({
|
||||
success: !stats.hasErrors(),
|
||||
outfile: path.resolve(context.root, options.outputPath, 'main.js'),
|
||||
outfile: path.resolve(
|
||||
context.root,
|
||||
normalizedOptions.outputPath,
|
||||
'main.js'
|
||||
),
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
423
packages/rspack/src/plugins/utils/apply-base-config.ts
Normal file
423
packages/rspack/src/plugins/utils/apply-base-config.ts
Normal file
@ -0,0 +1,423 @@
|
||||
import * as path from 'path';
|
||||
import { type ExecutorContext } from '@nx/devkit';
|
||||
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
||||
import {
|
||||
Configuration,
|
||||
ProgressPlugin,
|
||||
RspackPluginInstance,
|
||||
SwcJsMinimizerRspackPlugin,
|
||||
CopyRspackPlugin,
|
||||
RspackOptionsNormalized,
|
||||
ExternalItem,
|
||||
} from '@rspack/core';
|
||||
import { getRootTsConfigPath } from '@nx/js';
|
||||
|
||||
import { StatsJsonPlugin } from './plugins/stats-json-plugin';
|
||||
import { GeneratePackageJsonPlugin } from './plugins/generate-package-json-plugin';
|
||||
import { getOutputHashFormat } from './hash-format';
|
||||
import { NxTsconfigPathsRspackPlugin } from './plugins/nx-tsconfig-paths-rspack-plugin';
|
||||
import { getTerserEcmaVersion } from './get-terser-ecma-version';
|
||||
import nodeExternals = require('webpack-node-externals');
|
||||
import { NormalizedNxAppRspackPluginOptions } from './models';
|
||||
|
||||
const IGNORED_RSPACK_WARNINGS = [
|
||||
/The comment file/i,
|
||||
/could not find any license/i,
|
||||
];
|
||||
|
||||
const extensions = ['...', '.ts', '.tsx', '.mjs', '.js', '.jsx'];
|
||||
const mainFields = ['module', 'main'];
|
||||
|
||||
export function applyBaseConfig(
|
||||
options: NormalizedNxAppRspackPluginOptions,
|
||||
config: Partial<RspackOptionsNormalized | 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 {
|
||||
// Defaults that was applied from executor schema previously.
|
||||
options.deleteOutputPath ??= true;
|
||||
options.externalDependencies ??= 'all';
|
||||
options.fileReplacements ??= [];
|
||||
options.memoryLimit ??= 2048;
|
||||
options.transformers ??= [];
|
||||
options.progress ??= true;
|
||||
|
||||
applyNxIndependentConfig(options, config);
|
||||
|
||||
// Some of the options only work during actual tasks, not when reading the rspack config during CreateNodes.
|
||||
if (global.NX_GRAPH_CREATION) return;
|
||||
|
||||
applyNxDependentConfig(options, config, { useNormalizedEntry });
|
||||
}
|
||||
|
||||
function applyNxIndependentConfig(
|
||||
options: NormalizedNxAppRspackPluginOptions,
|
||||
config: Partial<RspackOptionsNormalized | Configuration>
|
||||
): void {
|
||||
const isProd =
|
||||
process.env.NODE_ENV === 'production' || options.mode === 'production';
|
||||
const hashFormat = getOutputHashFormat(options.outputHashing as string);
|
||||
config.context = path.join(options.root, options.projectRoot);
|
||||
config.target ??= options.target as 'node' | 'web';
|
||||
config.node = false;
|
||||
config.mode =
|
||||
// When the target is Node avoid any optimizations, such as replacing `process.env.NODE_ENV` with build time value.
|
||||
config.target === 'node'
|
||||
? 'none'
|
||||
: // Otherwise, make sure it matches `process.env.NODE_ENV`.
|
||||
// When mode is development or production, rspack 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 rspack.
|
||||
options.mode ??
|
||||
(process.env.NODE_ENV === 'development' ||
|
||||
process.env.NODE_ENV === 'production'
|
||||
? (process.env.NODE_ENV as 'development' | 'production')
|
||||
: 'none');
|
||||
// When target is Node, the Webpack mode will be set to 'none' which disables in memory caching and causes a full rebuild on every change.
|
||||
// So to mitigate this we enable in memory caching when target is Node and in watch mode.
|
||||
config.cache = options.target === 'node' && options.watch ? true : undefined;
|
||||
|
||||
config.devtool =
|
||||
options.sourceMap === 'hidden'
|
||||
? 'hidden-source-map'
|
||||
: options.sourceMap
|
||||
? 'source-map'
|
||||
: false;
|
||||
|
||||
config.output = {
|
||||
...(config.output ?? {}),
|
||||
libraryTarget:
|
||||
(config as Configuration).output?.libraryTarget ??
|
||||
(options.target === 'node' ? 'commonjs' : undefined),
|
||||
path:
|
||||
config.output?.path ??
|
||||
(options.outputPath
|
||||
? // If path is relative, it is relative from project root (aka cwd).
|
||||
// Otherwise, it is relative to workspace root (legacy behavior).
|
||||
options.outputPath.startsWith('.')
|
||||
? path.join(options.root, options.projectRoot, options.outputPath)
|
||||
: path.join(options.root, options.outputPath)
|
||||
: undefined),
|
||||
filename:
|
||||
config.output?.filename ??
|
||||
(options.outputHashing ? `[name]${hashFormat.script}.js` : '[name].js'),
|
||||
chunkFilename:
|
||||
config.output?.chunkFilename ??
|
||||
(options.outputHashing ? `[name]${hashFormat.chunk}.js` : '[name].js'),
|
||||
hashFunction: config.output?.hashFunction ?? 'xxhash64',
|
||||
// Disabled for performance
|
||||
pathinfo: config.output?.pathinfo ?? false,
|
||||
};
|
||||
|
||||
config.watch = options.watch;
|
||||
|
||||
config.watchOptions = {
|
||||
poll: options.poll,
|
||||
};
|
||||
|
||||
config.profile = options.statsJson;
|
||||
|
||||
config.performance = {
|
||||
...config.performance,
|
||||
hints: false,
|
||||
};
|
||||
|
||||
config.ignoreWarnings = [
|
||||
(x) =>
|
||||
IGNORED_RSPACK_WARNINGS.some((r) =>
|
||||
typeof x === 'string' ? r.test(x) : r.test(x.message)
|
||||
),
|
||||
];
|
||||
|
||||
config.optimization = !isProd
|
||||
? undefined
|
||||
: {
|
||||
...(config.optimization ?? {}),
|
||||
sideEffects: true,
|
||||
minimize:
|
||||
typeof options.optimization === 'object'
|
||||
? !!options.optimization.scripts
|
||||
: !!options.optimization,
|
||||
minimizer: [
|
||||
new SwcJsMinimizerRspackPlugin({
|
||||
extractComments: false,
|
||||
minimizerOptions: {
|
||||
// this needs to be false to allow toplevel variables to be used in the global scope
|
||||
// important especially for module-federation which operates as such
|
||||
module: false,
|
||||
mangle: {
|
||||
keep_classnames: true,
|
||||
},
|
||||
format: {
|
||||
ecma: getTerserEcmaVersion(
|
||||
path.join(options.root, options.projectRoot)
|
||||
),
|
||||
ascii_only: true,
|
||||
comments: false,
|
||||
webkit: true,
|
||||
safari10: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
runtimeChunk: false,
|
||||
concatenateModules: true,
|
||||
};
|
||||
|
||||
config.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,
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize properties that get set when rspack is used during task execution.
|
||||
* These properties may be used by consumers who expect them to not be undefined.
|
||||
*
|
||||
* When @nx/rspack/plugin resolves the config, it is not during a task, and therefore
|
||||
* these values are not set, which can lead to errors being thrown when reading
|
||||
* the rspack options from the resolved file.
|
||||
*/
|
||||
config.entry ??= {};
|
||||
config.resolve ??= {};
|
||||
config.module ??= {};
|
||||
config.plugins ??= [];
|
||||
config.externals ??= [];
|
||||
}
|
||||
|
||||
function applyNxDependentConfig(
|
||||
options: NormalizedNxAppRspackPluginOptions,
|
||||
config: Partial<RspackOptionsNormalized | Configuration>,
|
||||
{ useNormalizedEntry }: { useNormalizedEntry?: boolean } = {}
|
||||
): void {
|
||||
const tsConfig = options.tsConfig ?? getRootTsConfigPath();
|
||||
const plugins: RspackPluginInstance[] = [];
|
||||
|
||||
const executorContext: Partial<ExecutorContext> = {
|
||||
projectName: options.projectName,
|
||||
targetName: options.targetName,
|
||||
projectGraph: options.projectGraph,
|
||||
configurationName: options.configurationName,
|
||||
root: options.root,
|
||||
};
|
||||
|
||||
plugins.push(new NxTsconfigPathsRspackPlugin({ ...options, tsConfig }));
|
||||
|
||||
if (!options?.skipTypeChecking) {
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
plugins.push(
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
configFile: path.isAbsolute(tsConfig)
|
||||
? tsConfig
|
||||
: path.join(options.root, tsConfig),
|
||||
memoryLimit: options.memoryLimit || 2018,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
const entries: Array<{ name: string; import: string[] }> = [];
|
||||
|
||||
if (options.main) {
|
||||
const mainEntry = options.outputFileName
|
||||
? path.parse(options.outputFileName).name
|
||||
: 'main';
|
||||
entries.push({
|
||||
name: mainEntry,
|
||||
import: [path.resolve(options.root, options.main)],
|
||||
});
|
||||
}
|
||||
|
||||
if (options.additionalEntryPoints) {
|
||||
for (const { entryName, entryPath } of options.additionalEntryPoints) {
|
||||
entries.push({
|
||||
name: entryName,
|
||||
import: [path.resolve(options.root, entryPath)],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.polyfills) {
|
||||
entries.push({
|
||||
name: 'polyfills',
|
||||
import: [path.resolve(options.root, options.polyfills)],
|
||||
});
|
||||
}
|
||||
|
||||
config.entry ??= {};
|
||||
entries.forEach((entry) => {
|
||||
if (useNormalizedEntry) {
|
||||
config.entry[entry.name] = { import: entry.import };
|
||||
} else {
|
||||
config.entry[entry.name] = entry.import;
|
||||
}
|
||||
});
|
||||
|
||||
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 RspackPluginInstance
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(options.assets) && options.assets.length > 0) {
|
||||
plugins.push(
|
||||
new CopyRspackPlugin({
|
||||
patterns: options.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,
|
||||
},
|
||||
};
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (options.generatePackageJson && executorContext) {
|
||||
plugins.push(new GeneratePackageJsonPlugin({ ...options, tsConfig }));
|
||||
}
|
||||
|
||||
if (options.statsJson) {
|
||||
plugins.push(new StatsJsonPlugin());
|
||||
}
|
||||
|
||||
const 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();
|
||||
});
|
||||
}
|
||||
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
extensions: [...(config?.resolve?.extensions ?? []), ...extensions],
|
||||
alias: {
|
||||
...(config.resolve?.alias ?? {}),
|
||||
...(options.fileReplacements?.reduce(
|
||||
(aliases, replacement) => ({
|
||||
...aliases,
|
||||
[replacement.replace]: replacement.with,
|
||||
}),
|
||||
{}
|
||||
) ?? {}),
|
||||
},
|
||||
mainFields: config.resolve?.mainFields ?? mainFields,
|
||||
};
|
||||
|
||||
config.externals = externals;
|
||||
|
||||
// Enabled for performance
|
||||
config.cache = true;
|
||||
config.module = {
|
||||
...config.module,
|
||||
rules: [
|
||||
...(config?.module?.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',
|
||||
},
|
||||
// Rspack's docs only suggest swc for TS compilation
|
||||
//https://rspack.dev/guide/tech/typescript
|
||||
{
|
||||
test: /\.([jt])sx?$/,
|
||||
loader: 'builtin:swc-loader',
|
||||
exclude: /node_modules/,
|
||||
|
||||
type: 'javascript/auto',
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
decorators: true,
|
||||
tsx: true,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
pragma: 'React.createElement',
|
||||
pragmaFrag: 'React.Fragment',
|
||||
throwIfNamespace: true,
|
||||
// Config.mode is already set based on options.mode and `process.env.NODE_ENV`
|
||||
development: config.mode === 'development',
|
||||
useBuiltins: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
].filter((r) => !!r),
|
||||
};
|
||||
|
||||
config.plugins ??= [];
|
||||
config.plugins.push(...plugins);
|
||||
}
|
||||
@ -10,7 +10,6 @@ import {
|
||||
} 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 {
|
||||
@ -69,18 +68,17 @@ export function applyWebConfig(
|
||||
plugins.push(
|
||||
new HtmlRspackPlugin({
|
||||
template: options.index,
|
||||
sri: options.subresourceIntegrity ? 'sha256' : undefined,
|
||||
sri: 'sha256',
|
||||
...(options.baseHref ? { base: { href: options.baseHref } } : {}),
|
||||
...(config.output?.scriptType === 'module'
|
||||
? { scriptLoading: 'module' }
|
||||
: {}),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (options.subresourceIntegrity) {
|
||||
plugins.push(new SubresourceIntegrityPlugin() as any);
|
||||
}
|
||||
|
||||
const minimizer: RspackPluginInstance[] = [];
|
||||
if (stylesOptimization) {
|
||||
if (isProd && stylesOptimization) {
|
||||
minimizer.push(
|
||||
new LightningCssMinimizerRspackPlugin({
|
||||
test: /\.(?:css|scss|sass|less|styl)$/,
|
||||
@ -338,13 +336,11 @@ export function applyWebConfig(
|
||||
|
||||
config.output = {
|
||||
...(config.output ?? {}),
|
||||
assetModuleFilename: '[name].[contenthash:20][ext]',
|
||||
crossOriginLoading: options.subresourceIntegrity
|
||||
? ('anonymous' as const)
|
||||
: (false as const),
|
||||
assetModuleFilename: '[name].[contenthash:16][ext]',
|
||||
crossOriginLoading: 'anonymous',
|
||||
};
|
||||
|
||||
// In case users customize their webpack config with unsupported entry.
|
||||
// In case users customize their rspack 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')
|
||||
@ -360,41 +356,43 @@ export function applyWebConfig(
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
config.optimization = !isProd
|
||||
? undefined
|
||||
: {
|
||||
...(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[\\/]/,
|
||||
},
|
||||
},
|
||||
},
|
||||
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'];
|
||||
|
||||
@ -437,7 +435,7 @@ export function applyWebConfig(
|
||||
|
||||
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.
|
||||
// injected into the application via DefinePlugin in rspack configuration.
|
||||
const nxPublicKeyRegex = /^NX_PUBLIC_/i;
|
||||
|
||||
const raw = Object.keys(process.env)
|
||||
@ -447,7 +445,7 @@ function getClientEnvironment(mode?: string) {
|
||||
return env;
|
||||
}, {});
|
||||
|
||||
// Stringify all values so we can feed into webpack DefinePlugin
|
||||
// Stringify all values so we can feed into rspack DefinePlugin
|
||||
const stringified = {
|
||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
|
||||
36
packages/rspack/src/plugins/utils/get-terser-ecma-version.ts
Normal file
36
packages/rspack/src/plugins/utils/get-terser-ecma-version.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import browserslist = require('browserslist');
|
||||
|
||||
const VALID_BROWSERSLIST_FILES = ['.browserslistrc', 'browserslist'];
|
||||
|
||||
const ES5_BROWSERS = [
|
||||
'ie 10',
|
||||
'ie 11',
|
||||
'safari 11',
|
||||
'safari 11.1',
|
||||
'safari 12',
|
||||
'safari 12.1',
|
||||
'safari 13',
|
||||
'ios_saf 13.0',
|
||||
'ios_saf 13.3',
|
||||
];
|
||||
|
||||
export function getTerserEcmaVersion(projectRoot: string): 2020 | 5 {
|
||||
let pathToBrowserslistFile = '';
|
||||
for (const browserslistFile of VALID_BROWSERSLIST_FILES) {
|
||||
const fullPathToFile = path.join(projectRoot, browserslistFile);
|
||||
if (fs.existsSync(fullPathToFile)) {
|
||||
pathToBrowserslistFile = fullPathToFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathToBrowserslistFile) {
|
||||
return 2020;
|
||||
}
|
||||
|
||||
const env = browserslist.loadConfig({ path: pathToBrowserslistFile });
|
||||
const browsers = browserslist(env);
|
||||
return browsers.some((b) => ES5_BROWSERS.includes(b)) ? 5 : 2020;
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
import { logger } from '@nx/devkit';
|
||||
|
||||
export interface HashFormat {
|
||||
chunk: string;
|
||||
extract: string;
|
||||
@ -5,7 +7,18 @@ export interface HashFormat {
|
||||
script: string;
|
||||
}
|
||||
|
||||
export function getOutputHashFormat(option: string, length = 20): HashFormat {
|
||||
const MAX_HASH_LENGTH = 16;
|
||||
|
||||
export function getOutputHashFormat(
|
||||
option: string,
|
||||
length = MAX_HASH_LENGTH
|
||||
): HashFormat {
|
||||
if (length > MAX_HASH_LENGTH) {
|
||||
logger.warn(
|
||||
`Hash format length cannot be longer than ${MAX_HASH_LENGTH}. Using default of ${MAX_HASH_LENGTH}.`
|
||||
);
|
||||
length = MAX_HASH_LENGTH;
|
||||
}
|
||||
const hashFormats: { [option: string]: HashFormat } = {
|
||||
none: { chunk: '', extract: '', file: '', script: '' },
|
||||
media: { chunk: '', extract: '', file: `.[hash:${length}]`, script: '' },
|
||||
|
||||
@ -105,7 +105,7 @@ function postcssOptionsCreator(
|
||||
}
|
||||
) {
|
||||
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:
|
||||
// PostCSS options depend on the rspack 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) => ({
|
||||
|
||||
@ -130,11 +130,11 @@ export interface NxAppRspackPluginOptions {
|
||||
*/
|
||||
outputHashing?: any;
|
||||
/**
|
||||
* Override `output.path` in webpack configuration. This setting is not recommended and exists for backwards compatibility.
|
||||
* Override `output.path` in rspack 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.
|
||||
* Override `watchOptions.poll` in rspack configuration. This setting is not recommended and exists for backwards compatibility.
|
||||
*/
|
||||
poll?: number;
|
||||
/**
|
||||
@ -150,7 +150,7 @@ export interface NxAppRspackPluginOptions {
|
||||
*/
|
||||
progress?: boolean;
|
||||
/**
|
||||
* Add an additional chunk for the Webpack runtime. Defaults to `true` when `target === 'web'`.
|
||||
* Add an additional chunk for the rspack runtime. Defaults to `true` when `target === 'web'`.
|
||||
*/
|
||||
runtimeChunk?: boolean;
|
||||
/**
|
||||
@ -194,11 +194,7 @@ export interface NxAppRspackPluginOptions {
|
||||
*/
|
||||
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.
|
||||
* Override the `target` option in rspack configuration. This setting is not recommended and exists for backwards compatibility.
|
||||
*/
|
||||
target?: string | string[];
|
||||
/**
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
import {
|
||||
type Compiler,
|
||||
sources,
|
||||
type RspackPluginInstance,
|
||||
} from '@rspack/core';
|
||||
import {
|
||||
createLockFile,
|
||||
createPackageJson,
|
||||
getHelperDependenciesFromProjectGraph,
|
||||
getLockFileName,
|
||||
HelperDependency,
|
||||
readTsConfig,
|
||||
} from '@nx/js';
|
||||
import {
|
||||
detectPackageManager,
|
||||
type ProjectGraph,
|
||||
serializeJson,
|
||||
} from '@nx/devkit';
|
||||
|
||||
const pluginName = 'GeneratePackageJsonPlugin';
|
||||
|
||||
export class GeneratePackageJsonPlugin implements RspackPluginInstance {
|
||||
constructor(
|
||||
private readonly options: {
|
||||
skipPackageManager?: boolean;
|
||||
tsConfig: string;
|
||||
outputFileName: string;
|
||||
root: string;
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
projectGraph: ProjectGraph;
|
||||
}
|
||||
) {}
|
||||
|
||||
apply(compiler: Compiler): void {
|
||||
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
||||
compilation.hooks.processAssets.tap(
|
||||
{
|
||||
name: pluginName,
|
||||
stage: compiler.rspack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
|
||||
},
|
||||
() => {
|
||||
const helperDependencies = getHelperDependenciesFromProjectGraph(
|
||||
this.options.root,
|
||||
this.options.projectName,
|
||||
this.options.projectGraph
|
||||
);
|
||||
|
||||
const importHelpers = !!readTsConfig(this.options.tsConfig).options
|
||||
.importHelpers;
|
||||
const shouldAddHelperDependency =
|
||||
importHelpers &&
|
||||
helperDependencies.every(
|
||||
(dep) => dep.target !== HelperDependency.tsc
|
||||
);
|
||||
|
||||
if (shouldAddHelperDependency) {
|
||||
helperDependencies.push({
|
||||
type: 'static',
|
||||
source: this.options.projectName,
|
||||
target: HelperDependency.tsc,
|
||||
});
|
||||
}
|
||||
|
||||
const packageJson = createPackageJson(
|
||||
this.options.projectName,
|
||||
this.options.projectGraph,
|
||||
{
|
||||
target: this.options.targetName,
|
||||
root: this.options.root,
|
||||
isProduction: true,
|
||||
helperDependencies: helperDependencies.map((dep) => dep.target),
|
||||
skipPackageManager: this.options.skipPackageManager,
|
||||
}
|
||||
);
|
||||
packageJson.main = packageJson.main ?? this.options.outputFileName;
|
||||
|
||||
compilation.emitAsset(
|
||||
'package.json',
|
||||
new sources.RawSource(serializeJson(packageJson))
|
||||
);
|
||||
const packageManager = detectPackageManager(this.options.root);
|
||||
compilation.emitAsset(
|
||||
getLockFileName(packageManager),
|
||||
new sources.RawSource(
|
||||
createLockFile(
|
||||
packageJson,
|
||||
this.options.projectGraph,
|
||||
packageManager
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
235
packages/rspack/src/plugins/utils/plugins/normalize-options.ts
Normal file
235
packages/rspack/src/plugins/utils/plugins/normalize-options.ts
Normal file
@ -0,0 +1,235 @@
|
||||
import { basename, dirname, join, parse, relative, resolve } from 'path';
|
||||
import { statSync } from 'fs';
|
||||
import {
|
||||
normalizePath,
|
||||
parseTargetString,
|
||||
readCachedProjectGraph,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import {
|
||||
AssetGlobPattern,
|
||||
FileReplacement,
|
||||
NxAppRspackPluginOptions,
|
||||
NormalizedNxAppRspackPluginOptions,
|
||||
} from '../models';
|
||||
|
||||
export function normalizeOptions(
|
||||
options: NxAppRspackPluginOptions
|
||||
): NormalizedNxAppRspackPluginOptions {
|
||||
const combinedPluginAndMaybeExecutorOptions: Partial<NormalizedNxAppRspackPluginOptions> =
|
||||
{};
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
// Since this is invoked by the executor, the graph has already been created and cached.
|
||||
const projectGraph = readCachedProjectGraph();
|
||||
|
||||
const taskDetailsFromBuildTarget = process.env.NX_BUILD_TARGET
|
||||
? parseTargetString(process.env.NX_BUILD_TARGET, projectGraph)
|
||||
: undefined;
|
||||
const projectName = taskDetailsFromBuildTarget
|
||||
? taskDetailsFromBuildTarget.project
|
||||
: process.env.NX_TASK_TARGET_PROJECT;
|
||||
const targetName = taskDetailsFromBuildTarget
|
||||
? taskDetailsFromBuildTarget.target
|
||||
: process.env.NX_TASK_TARGET_TARGET;
|
||||
const configurationName = taskDetailsFromBuildTarget
|
||||
? taskDetailsFromBuildTarget.configuration
|
||||
: process.env.NX_TASK_TARGET_CONFIGURATION;
|
||||
|
||||
const projectNode = projectGraph.nodes[projectName];
|
||||
const targetConfig = projectNode.data.targets[targetName];
|
||||
|
||||
normalizeRelativePaths(projectNode.data.root, options);
|
||||
|
||||
// Merge options from `@nx/rspack:rspack` into plugin options.
|
||||
// Options from `@nx/rspack:rspack` take precedence.
|
||||
const originalTargetOptions = targetConfig.options;
|
||||
if (configurationName) {
|
||||
Object.assign(
|
||||
originalTargetOptions,
|
||||
targetConfig.configurations?.[configurationName]
|
||||
);
|
||||
}
|
||||
// This could be called from dev-server which means we need to read `buildTarget` to get actual build options.
|
||||
// Otherwise, the options are passed from the `@nx/rspack:rspack` executor.
|
||||
if (originalTargetOptions.buildTarget) {
|
||||
const buildTargetOptions = targetConfig.options;
|
||||
if (configurationName) {
|
||||
Object.assign(
|
||||
buildTargetOptions,
|
||||
targetConfig.configurations?.[configurationName]
|
||||
);
|
||||
}
|
||||
Object.assign(
|
||||
combinedPluginAndMaybeExecutorOptions,
|
||||
options,
|
||||
// executor options take precedence (especially for overriding with CLI args)
|
||||
buildTargetOptions
|
||||
);
|
||||
} else {
|
||||
Object.assign(
|
||||
combinedPluginAndMaybeExecutorOptions,
|
||||
options,
|
||||
// executor options take precedence (especially for overriding with CLI args)
|
||||
originalTargetOptions
|
||||
);
|
||||
}
|
||||
|
||||
const sourceRoot = projectNode.data.sourceRoot ?? projectNode.data.root;
|
||||
|
||||
if (!combinedPluginAndMaybeExecutorOptions.main) {
|
||||
throw new Error(
|
||||
`Missing "main" option for the entry file. Set this option in your Nx rspack plugin.`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...combinedPluginAndMaybeExecutorOptions,
|
||||
assets: combinedPluginAndMaybeExecutorOptions.assets
|
||||
? normalizeAssets(
|
||||
combinedPluginAndMaybeExecutorOptions.assets,
|
||||
workspaceRoot,
|
||||
sourceRoot,
|
||||
projectNode.data.root
|
||||
)
|
||||
: [],
|
||||
baseHref: combinedPluginAndMaybeExecutorOptions.baseHref ?? '/',
|
||||
buildLibsFromSource:
|
||||
combinedPluginAndMaybeExecutorOptions.buildLibsFromSource ?? true,
|
||||
commonChunk: combinedPluginAndMaybeExecutorOptions.commonChunk ?? true,
|
||||
configurationName,
|
||||
deleteOutputPath:
|
||||
combinedPluginAndMaybeExecutorOptions.deleteOutputPath ?? true,
|
||||
extractCss: combinedPluginAndMaybeExecutorOptions.extractCss ?? true,
|
||||
fileReplacements: normalizeFileReplacements(
|
||||
workspaceRoot,
|
||||
combinedPluginAndMaybeExecutorOptions.fileReplacements
|
||||
),
|
||||
generateIndexHtml:
|
||||
combinedPluginAndMaybeExecutorOptions.generateIndexHtml ?? true,
|
||||
main: combinedPluginAndMaybeExecutorOptions.main,
|
||||
namedChunks: combinedPluginAndMaybeExecutorOptions.namedChunks ?? !isProd,
|
||||
optimization: combinedPluginAndMaybeExecutorOptions.optimization ?? isProd,
|
||||
outputFileName:
|
||||
combinedPluginAndMaybeExecutorOptions.outputFileName ?? 'main.js',
|
||||
outputHashing:
|
||||
combinedPluginAndMaybeExecutorOptions.outputHashing ??
|
||||
(isProd ? 'all' : 'none'),
|
||||
outputPath: combinedPluginAndMaybeExecutorOptions.outputPath,
|
||||
projectGraph,
|
||||
projectName,
|
||||
projectRoot: projectNode.data.root,
|
||||
root: workspaceRoot,
|
||||
runtimeChunk: combinedPluginAndMaybeExecutorOptions.runtimeChunk ?? true,
|
||||
scripts: combinedPluginAndMaybeExecutorOptions.scripts ?? [],
|
||||
sourceMap: combinedPluginAndMaybeExecutorOptions.sourceMap ?? !isProd,
|
||||
sourceRoot,
|
||||
styles: combinedPluginAndMaybeExecutorOptions.styles ?? [],
|
||||
target: combinedPluginAndMaybeExecutorOptions.target,
|
||||
targetName,
|
||||
vendorChunk: combinedPluginAndMaybeExecutorOptions.vendorChunk ?? !isProd,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeAssets(
|
||||
assets: any[],
|
||||
root: string,
|
||||
sourceRoot: string,
|
||||
projectRoot: string,
|
||||
resolveRelativePathsToProjectRoot = true
|
||||
): AssetGlobPattern[] {
|
||||
return assets.map((asset) => {
|
||||
if (typeof asset === 'string') {
|
||||
const assetPath = normalizePath(asset);
|
||||
const resolvedAssetPath = resolve(root, assetPath);
|
||||
const resolvedSourceRoot = resolve(root, sourceRoot);
|
||||
|
||||
if (!resolvedAssetPath.startsWith(resolvedSourceRoot)) {
|
||||
throw new Error(
|
||||
`The ${resolvedAssetPath} asset path must start with the project source root: ${sourceRoot}`
|
||||
);
|
||||
}
|
||||
|
||||
const isDirectory = statSync(resolvedAssetPath).isDirectory();
|
||||
const input = isDirectory
|
||||
? resolvedAssetPath
|
||||
: dirname(resolvedAssetPath);
|
||||
const output = relative(resolvedSourceRoot, resolve(root, input));
|
||||
const glob = isDirectory ? '**/*' : basename(resolvedAssetPath);
|
||||
return {
|
||||
input,
|
||||
output,
|
||||
glob,
|
||||
};
|
||||
} else {
|
||||
if (asset.output.startsWith('..')) {
|
||||
throw new Error(
|
||||
'An asset cannot be written to a location outside of the output path.'
|
||||
);
|
||||
}
|
||||
|
||||
const assetPath = normalizePath(asset.input);
|
||||
let resolvedAssetPath = resolve(root, assetPath);
|
||||
if (resolveRelativePathsToProjectRoot && asset.input.startsWith('.')) {
|
||||
const resolvedProjectRoot = resolve(root, projectRoot);
|
||||
resolvedAssetPath = resolve(resolvedProjectRoot, assetPath);
|
||||
}
|
||||
|
||||
return {
|
||||
...asset,
|
||||
input: resolvedAssetPath,
|
||||
// Now we remove starting slash to make rspack place it from the output root.
|
||||
output: asset.output.replace(/^\//, ''),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeFileReplacements(
|
||||
root: string,
|
||||
fileReplacements: FileReplacement[]
|
||||
): FileReplacement[] {
|
||||
return fileReplacements
|
||||
? fileReplacements.map((fileReplacement) => ({
|
||||
replace: resolve(root, fileReplacement.replace),
|
||||
with: resolve(root, fileReplacement.with),
|
||||
}))
|
||||
: [];
|
||||
}
|
||||
|
||||
function normalizeRelativePaths(
|
||||
projectRoot: string,
|
||||
options: NxAppRspackPluginOptions
|
||||
): void {
|
||||
for (const [fieldName, fieldValue] of Object.entries(options)) {
|
||||
if (isRelativePath(fieldValue)) {
|
||||
options[fieldName] = join(projectRoot, fieldValue);
|
||||
} else if (fieldName === 'additionalEntryPoints') {
|
||||
for (let i = 0; i < fieldValue.length; i++) {
|
||||
const v = fieldValue[i];
|
||||
if (isRelativePath(v)) {
|
||||
fieldValue[i] = {
|
||||
entryName: parse(v).name,
|
||||
entryPath: join(projectRoot, v),
|
||||
};
|
||||
} else if (isRelativePath(v.entryPath)) {
|
||||
v.entryPath = join(projectRoot, v.entryPath);
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(fieldValue)) {
|
||||
for (let i = 0; i < fieldValue.length; i++) {
|
||||
if (isRelativePath(fieldValue[i])) {
|
||||
fieldValue[i] = join(projectRoot, fieldValue[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isRelativePath(val: unknown): boolean {
|
||||
return (
|
||||
typeof val === 'string' &&
|
||||
(val.startsWith('./') ||
|
||||
// Windows
|
||||
val.startsWith('.\\'))
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
import * as path from 'path';
|
||||
import {
|
||||
Compiler,
|
||||
type Configuration,
|
||||
type RspackOptionsNormalized,
|
||||
} from '@rspack/core';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
import {
|
||||
calculateProjectBuildableDependencies,
|
||||
createTmpTsConfig,
|
||||
} from '@nx/js/src/utils/buildable-libs-utils';
|
||||
import { NormalizedNxAppRspackPluginOptions } from '../models';
|
||||
import { RspackNxBuildCoordinationPlugin } from './rspack-nx-build-coordination-plugin';
|
||||
import { unlinkSync } from 'fs';
|
||||
|
||||
export class NxTsconfigPathsRspackPlugin {
|
||||
private tmpTsConfigPath: string;
|
||||
|
||||
constructor(private options: NormalizedNxAppRspackPluginOptions) {
|
||||
if (!this.options.tsConfig)
|
||||
throw new Error(
|
||||
`Missing "tsConfig" option. Set this option in your Nx rspack plugin.`
|
||||
);
|
||||
}
|
||||
|
||||
apply(compiler: Compiler): void {
|
||||
// TODO(Colum): Investigate the best way to handle this, currently it is not working and affecting HMR
|
||||
// // If we are not building libs from source, we need to remap paths so tsconfig may be updated.
|
||||
// this.handleBuildLibsFromSource(compiler.options, this.options);
|
||||
|
||||
const pathToTsconfig = !path.isAbsolute(this.options.tsConfig)
|
||||
? path.join(workspaceRoot, this.options.tsConfig)
|
||||
: this.options.tsConfig;
|
||||
|
||||
const extensions = new Set([
|
||||
...['.ts', '.tsx', '.mjs', '.js', '.jsx'],
|
||||
...(compiler.options?.resolve?.extensions ?? []),
|
||||
]);
|
||||
|
||||
compiler.options.resolve = {
|
||||
...compiler.options.resolve,
|
||||
extensions: [...extensions],
|
||||
tsConfig: { configFile: pathToTsconfig },
|
||||
};
|
||||
}
|
||||
|
||||
cleanupTmpTsConfigFile() {
|
||||
if (this.tmpTsConfigPath) {
|
||||
try {
|
||||
if (this.tmpTsConfigPath) {
|
||||
unlinkSync(this.tmpTsConfigPath);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
handleBuildLibsFromSource(
|
||||
config: Partial<RspackOptionsNormalized | Configuration>,
|
||||
options
|
||||
): void {
|
||||
if (!options.buildLibsFromSource && options.targetName) {
|
||||
const remappedTarget =
|
||||
options.targetName === 'serve' ? 'build' : options.targetName;
|
||||
|
||||
const { target, dependencies } = calculateProjectBuildableDependencies(
|
||||
undefined,
|
||||
options.projectGraph,
|
||||
options.root,
|
||||
options.projectName,
|
||||
remappedTarget,
|
||||
options.configurationName
|
||||
);
|
||||
|
||||
options.tsConfig = createTmpTsConfig(
|
||||
options.tsConfig,
|
||||
options.root,
|
||||
target.data.root,
|
||||
dependencies
|
||||
);
|
||||
this.tmpTsConfigPath = options.tsConfig;
|
||||
|
||||
if (options.targetName === 'serve') {
|
||||
const buildableDependencies = dependencies
|
||||
.filter((dependency) => dependency.node.type === 'lib')
|
||||
.map((dependency) => dependency.node.name)
|
||||
.join(',');
|
||||
|
||||
const buildCommand = `nx run-many --target=build --projects=${buildableDependencies}`;
|
||||
|
||||
if (buildableDependencies && buildableDependencies.length > 0) {
|
||||
config.plugins.push(
|
||||
new RspackNxBuildCoordinationPlugin(buildCommand)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
import { exec } from 'child_process';
|
||||
import type { Compiler } from '@rspack/core';
|
||||
import { daemonClient, isDaemonEnabled } from 'nx/src/daemon/client/client';
|
||||
import { BatchFunctionRunner } from 'nx/src/command-line/watch/watch';
|
||||
import { output } from 'nx/src/utils/output';
|
||||
|
||||
export class RspackNxBuildCoordinationPlugin {
|
||||
private currentlyRunning: 'none' | 'nx-build' | 'rspack-build' = 'none';
|
||||
private buildCmdProcess: ReturnType<typeof exec> | null = null;
|
||||
|
||||
constructor(private readonly buildCmd: string, skipInitialBuild?: boolean) {
|
||||
if (!skipInitialBuild) {
|
||||
this.buildChangedProjects();
|
||||
}
|
||||
if (isDaemonEnabled()) {
|
||||
this.startWatchingBuildableLibs();
|
||||
} else {
|
||||
output.warn({
|
||||
title:
|
||||
'Nx Daemon is not enabled. Buildable libs will not be rebuilt on file changes.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
apply(compiler: Compiler) {
|
||||
compiler.hooks.beforeCompile.tapPromise(
|
||||
'IncrementalDevServerPlugin',
|
||||
async () => {
|
||||
while (this.currentlyRunning === 'nx-build') {
|
||||
await sleep(50);
|
||||
}
|
||||
this.currentlyRunning = 'rspack-build';
|
||||
}
|
||||
);
|
||||
compiler.hooks.done.tapPromise('IncrementalDevServerPlugin', async () => {
|
||||
this.currentlyRunning = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
async startWatchingBuildableLibs() {
|
||||
const unregisterFileWatcher = await this.createFileWatcher();
|
||||
|
||||
process.on('exit', () => {
|
||||
unregisterFileWatcher();
|
||||
});
|
||||
}
|
||||
|
||||
async buildChangedProjects() {
|
||||
while (this.currentlyRunning === 'rspack-build') {
|
||||
await sleep(50);
|
||||
}
|
||||
this.currentlyRunning = 'nx-build';
|
||||
try {
|
||||
return await new Promise<void>((res) => {
|
||||
this.buildCmdProcess = exec(this.buildCmd, {
|
||||
windowsHide: false,
|
||||
});
|
||||
|
||||
this.buildCmdProcess.stdout.pipe(process.stdout);
|
||||
this.buildCmdProcess.stderr.pipe(process.stderr);
|
||||
this.buildCmdProcess.on('exit', () => {
|
||||
res();
|
||||
});
|
||||
this.buildCmdProcess.on('error', () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
this.currentlyRunning = 'none';
|
||||
this.buildCmdProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
private createFileWatcher() {
|
||||
const runner = new BatchFunctionRunner(() => this.buildChangedProjects());
|
||||
return daemonClient.registerFileWatcher(
|
||||
{
|
||||
watchProjects: 'all',
|
||||
},
|
||||
(err, { changedProjects, changedFiles }) => {
|
||||
if (err === 'closed') {
|
||||
output.error({
|
||||
title: 'Watch connection closed',
|
||||
bodyLines: [
|
||||
'The daemon has closed the connection to this watch process.',
|
||||
'Please restart your watch command.',
|
||||
],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (this.buildCmdProcess) {
|
||||
this.buildCmdProcess.kill(2);
|
||||
this.buildCmdProcess = null;
|
||||
}
|
||||
// Queue a build
|
||||
runner.enqueue(changedProjects, changedFiles);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(time: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { Compiler, sources } from '@rspack/core';
|
||||
|
||||
export class StatsJsonPlugin {
|
||||
apply(compiler: Compiler) {
|
||||
compiler.hooks.emit.tap('StatsJsonPlugin', (compilation) => {
|
||||
const data = JSON.stringify(compilation.getStats().toJson('verbose'));
|
||||
compilation.assets[`stats.json`] = new sources.RawSource(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -47,10 +47,10 @@ export function composePlugins(
|
||||
): 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.
|
||||
// This is to enable `@nx/rspack/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,
|
||||
// Build this from scratch since what rspack passes us is the env, not config,
|
||||
// and `withNX()` creates a new config object anyway.
|
||||
config = {};
|
||||
}
|
||||
@ -97,6 +97,7 @@ function ensureNxRspackExecutionContext(ctx: NxRspackExecutionContext): void {
|
||||
// These aren't actually needed since NxRspackPlugin and withNx both support them being undefined.
|
||||
assets: undefined,
|
||||
outputFileName: undefined,
|
||||
outputPath: undefined,
|
||||
rspackConfig: undefined,
|
||||
};
|
||||
ctx.context ??= {
|
||||
|
||||
@ -15,7 +15,7 @@ export async function createCompiler(
|
||||
},
|
||||
context: ExecutorContext
|
||||
): Promise<Compiler | MultiCompiler> {
|
||||
const pathToConfig = path.join(context.root, options.rspackConfig);
|
||||
const pathToConfig = options.rspackConfig;
|
||||
let userDefinedConfig: any = {};
|
||||
if (options.tsConfig) {
|
||||
userDefinedConfig = resolveUserDefinedRspackConfig(
|
||||
|
||||
@ -2,7 +2,7 @@ export function getCopyPatterns(assets: any[]) {
|
||||
return assets.map((asset) => {
|
||||
return {
|
||||
context: asset.input,
|
||||
// Now we remove starting slash to make Webpack place it from the output root.
|
||||
// Now we remove starting slash to make rspack place it from the output root.
|
||||
to: asset.output,
|
||||
from: asset.glob,
|
||||
globOptions: {
|
||||
|
||||
@ -82,7 +82,7 @@ export function shareWorkspaceLibraries(
|
||||
pathMappings.reduce(
|
||||
(aliases, library) => ({
|
||||
...aliases,
|
||||
// If the library path ends in a wildcard, remove it as webpack can't handle this in resolve.alias
|
||||
// If the library path ends in a wildcard, remove it as rspack can't handle this in resolve.alias
|
||||
// e.g. path/to/my/lib/* -> path/to/my/lib
|
||||
[library.name]: library.path.replace(/\/\*$/, ''),
|
||||
}),
|
||||
@ -155,7 +155,7 @@ export function shareWorkspaceLibraries(
|
||||
* library.path is usually in the form of "/Users/username/path/to/Workspace/path/to/library"
|
||||
*
|
||||
* When a wildcard is used in the TS path mappings, we want to get everything after the import to
|
||||
* re-route the request correctly inline with the webpack resolve.alias
|
||||
* re-route the request correctly inline with the rspack resolve.alias
|
||||
*/
|
||||
join(
|
||||
library.name,
|
||||
|
||||
@ -40,7 +40,7 @@ export function getFunctionDeterminateRemoteUrl(isServer = false) {
|
||||
if (!serveTarget) {
|
||||
throw new Error(
|
||||
`Cannot automatically determine URL of remote (${remote}). Looked for property "host" in the project's "${serveTarget}" target.\n
|
||||
You can also use the tuple syntax in your webpack config to configure your remotes. e.g. \`remotes: [['remote1', 'http://localhost:4201']]\``
|
||||
You can also use the tuple syntax in your rspack config to configure your remotes. e.g. \`remotes: [['remote1', 'http://localhost:4201']]\``
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ export function normalizeAssets(
|
||||
return {
|
||||
...asset,
|
||||
input: resolvedAssetPath,
|
||||
// Now we remove starting slash to make Webpack place it from the output root.
|
||||
// Now we remove starting slash to make rspack place it from the output root.
|
||||
output: asset.output.replace(/^\//, ''),
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,233 +1,50 @@
|
||||
import {
|
||||
Configuration,
|
||||
ExternalItem,
|
||||
ResolveAlias,
|
||||
RspackPluginInstance,
|
||||
rspack,
|
||||
} from '@rspack/core';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { LicenseWebpackPlugin } from 'license-webpack-plugin';
|
||||
import * as path from 'path';
|
||||
import { join } from 'path';
|
||||
import { GeneratePackageJsonPlugin } from '../plugins/generate-package-json-plugin';
|
||||
import { getCopyPatterns } from './get-copy-patterns';
|
||||
import { type Configuration } from '@rspack/core';
|
||||
import { normalizeAssets } from './normalize-assets';
|
||||
import { NxRspackExecutionContext } from './config';
|
||||
import { NxAppRspackPluginOptions } from '../plugins/utils/models';
|
||||
import { applyBaseConfig } from '../plugins/utils/apply-base-config';
|
||||
import { NxRspackExecutionContext, NxComposableRspackPlugin } from './config';
|
||||
|
||||
export function withNx(_opts = {}) {
|
||||
const processed = new Set();
|
||||
|
||||
export type WithNxOptions = Partial<NxAppRspackPluginOptions>;
|
||||
|
||||
/**
|
||||
* @param {WithNxOptions} pluginOptions
|
||||
* @returns {NxComposableRspackPlugin}
|
||||
*/
|
||||
export function withNx(
|
||||
pluginOptions: WithNxOptions = {}
|
||||
): NxComposableRspackPlugin {
|
||||
return function makeConfig(
|
||||
config: Configuration,
|
||||
{ options, context }: NxRspackExecutionContext
|
||||
): Configuration {
|
||||
const isProd =
|
||||
process.env.NODE_ENV === 'production' || options.mode === 'production';
|
||||
if (processed.has(config)) return config;
|
||||
|
||||
const project = context.projectGraph.nodes[context.projectName];
|
||||
const sourceRoot = path.join(context.root, project.data.sourceRoot);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const tsconfigPaths = require('tsconfig-paths');
|
||||
const { paths } = tsconfigPaths.loadConfig(options.tsConfig);
|
||||
const alias: ResolveAlias = Object.keys(paths).reduce((acc, k) => {
|
||||
acc[k] = path.join(context.root, paths[k][0]);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const plugins = config.plugins ?? [];
|
||||
if (options.extractLicenses) {
|
||||
/**
|
||||
* Needed to prevent an issue with Rspack and Workspaces where the
|
||||
* workspace's root package.json file is added to the dependency tree
|
||||
*/
|
||||
let rootPackageJsonName;
|
||||
const pathToRootPackageJson = join(context.root, 'package.json');
|
||||
if (existsSync(pathToRootPackageJson)) {
|
||||
try {
|
||||
const rootPackageJson = JSON.parse(
|
||||
readFileSync(pathToRootPackageJson, 'utf-8')
|
||||
);
|
||||
rootPackageJsonName = rootPackageJson.name;
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
plugins.push(
|
||||
new LicenseWebpackPlugin({
|
||||
stats: {
|
||||
warnings: false,
|
||||
errors: false,
|
||||
},
|
||||
outputFilename: `3rdpartylicenses.txt`,
|
||||
/**
|
||||
* Needed to prevent an issue with Rspack and Workspaces where the
|
||||
* workspace's root package.json file is added to the dependency tree
|
||||
*/
|
||||
excludedPackageTest: (packageName) => {
|
||||
if (!rootPackageJsonName) {
|
||||
return false;
|
||||
}
|
||||
return packageName === rootPackageJsonName;
|
||||
},
|
||||
}) as unknown as RspackPluginInstance
|
||||
);
|
||||
}
|
||||
|
||||
if (options.generatePackageJson) {
|
||||
const mainOutputFile =
|
||||
options.main.split('/').pop().split('.')[0] + '.js';
|
||||
|
||||
plugins.push(
|
||||
new GeneratePackageJsonPlugin(
|
||||
{
|
||||
tsConfig: options.tsConfig,
|
||||
outputFileName: options.outputFileName ?? mainOutputFile,
|
||||
},
|
||||
context
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
plugins.push(
|
||||
new rspack.CopyRspackPlugin({
|
||||
patterns: getCopyPatterns(
|
||||
normalizeAssets(options.assets, context.root, sourceRoot)
|
||||
),
|
||||
})
|
||||
applyBaseConfig(
|
||||
{
|
||||
...options,
|
||||
...pluginOptions,
|
||||
target: options.target ?? 'web',
|
||||
assets: options.assets
|
||||
? options.assets
|
||||
: pluginOptions.assets
|
||||
? normalizeAssets(
|
||||
pluginOptions.assets,
|
||||
options.root,
|
||||
options.sourceRoot
|
||||
)
|
||||
: [],
|
||||
root: context.root,
|
||||
projectName: context.projectName,
|
||||
targetName: context.targetName,
|
||||
configurationName: context.configurationName,
|
||||
projectGraph: context.projectGraph,
|
||||
},
|
||||
config
|
||||
);
|
||||
plugins.push(new rspack.ProgressPlugin());
|
||||
|
||||
options.fileReplacements.forEach((item) => {
|
||||
alias[item.replace] = item.with;
|
||||
});
|
||||
|
||||
const externals: ExternalItem = {};
|
||||
let externalsType: Configuration['externalsType'];
|
||||
if (options.target === 'node') {
|
||||
const projectDeps =
|
||||
context.projectGraph.dependencies[context.projectName];
|
||||
for (const dep of Object.values(projectDeps)) {
|
||||
const externalNode = context.projectGraph.externalNodes[dep.target];
|
||||
if (externalNode) {
|
||||
externals[externalNode.data.packageName] =
|
||||
externalNode.data.packageName;
|
||||
}
|
||||
}
|
||||
externalsType = 'commonjs';
|
||||
}
|
||||
|
||||
const updated: Configuration = {
|
||||
...config,
|
||||
target: options.target,
|
||||
mode: options.mode,
|
||||
entry: {},
|
||||
context: join(
|
||||
context.root,
|
||||
context.projectGraph.nodes[context.projectName].data.root
|
||||
),
|
||||
devtool:
|
||||
options.sourceMap === 'hidden'
|
||||
? ('hidden-source-map' as const)
|
||||
: options.sourceMap
|
||||
? ('source-map' as const)
|
||||
: (false as const),
|
||||
output: {
|
||||
path: path.join(context.root, options.outputPath),
|
||||
publicPath: '/',
|
||||
filename:
|
||||
isProd && options.target !== 'node'
|
||||
? '[name].[contenthash:8].js'
|
||||
: '[name].js',
|
||||
chunkFilename:
|
||||
isProd && options.target !== 'node'
|
||||
? '[name].[contenthash:8].js'
|
||||
: '[name].js',
|
||||
cssFilename:
|
||||
isProd && options.target !== 'node'
|
||||
? '[name].[contenthash:8].css'
|
||||
: '[name].css',
|
||||
cssChunkFilename:
|
||||
isProd && options.target !== 'node'
|
||||
? '[name].[contenthash:8].css'
|
||||
: '[name].css',
|
||||
assetModuleFilename:
|
||||
isProd && options.target !== 'node'
|
||||
? '[name].[contenthash:8][ext]'
|
||||
: '[name][ext]',
|
||||
},
|
||||
devServer: {
|
||||
...(config.devServer ?? {}),
|
||||
port: config.devServer?.port ?? 4200,
|
||||
hot: config.devServer?.hot ?? true,
|
||||
devMiddleware: {
|
||||
...(config.devServer?.devMiddleware ?? {}),
|
||||
stats: true,
|
||||
},
|
||||
} as any,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'builtin:swc-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'ecmascript',
|
||||
},
|
||||
externalHelpers: true,
|
||||
},
|
||||
},
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'builtin:swc-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
decorators: true,
|
||||
},
|
||||
transform: {
|
||||
legacyDecorator: true,
|
||||
decoratorMetadata: true,
|
||||
},
|
||||
externalHelpers: true,
|
||||
},
|
||||
},
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: plugins,
|
||||
resolve: {
|
||||
// There are some issues resolving workspace libs in a monorepo.
|
||||
// It looks to be an issue with rspack itself, but will check back after Nx 16 release
|
||||
// once I can reproduce a small example repo with rspack only.
|
||||
alias,
|
||||
// We need to define the extensions that rspack can resolve
|
||||
extensions: ['...', '.ts', '.tsx', '.jsx'],
|
||||
// tsConfigPath: path.join(context.root, options.tsConfig),
|
||||
},
|
||||
infrastructureLogging: {
|
||||
debug: false,
|
||||
},
|
||||
externals,
|
||||
externalsType,
|
||||
stats: {
|
||||
colors: true,
|
||||
preset: 'normal',
|
||||
},
|
||||
};
|
||||
|
||||
const mainEntry = options.main
|
||||
? options.outputFileName
|
||||
? path.parse(options.outputFileName).name
|
||||
: 'main'
|
||||
: 'main';
|
||||
updated.entry[mainEntry] = path.resolve(context.root, options.main);
|
||||
|
||||
return updated;
|
||||
processed.add(config);
|
||||
return config;
|
||||
};
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ export interface WithWebOptions {
|
||||
postcssConfig?: string;
|
||||
scripts?: Array<ExtraEntryPointClass | string>;
|
||||
styles?: Array<ExtraEntryPointClass | string>;
|
||||
subresourceIntegrity?: boolean;
|
||||
stylePreprocessorOptions?: {
|
||||
includePaths?: string[];
|
||||
};
|
||||
@ -51,7 +50,7 @@ export function withWeb(pluginOptions: WithWebOptions = {}) {
|
||||
|
||||
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.
|
||||
// injected into the application via DefinePlugin in rspack configuration.
|
||||
const nxPublicKeyRegex = /^NX_PUBLIC_/i;
|
||||
|
||||
const raw = Object.keys(process.env)
|
||||
@ -61,7 +60,7 @@ function getClientEnvironment(mode?: string) {
|
||||
return env;
|
||||
}, {});
|
||||
|
||||
// Stringify all values so we can feed into webpack DefinePlugin
|
||||
// Stringify all values so we can feed into rspack DefinePlugin
|
||||
const stringified = {
|
||||
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||
env[key] = JSON.stringify(raw[key]);
|
||||
|
||||
@ -51,7 +51,7 @@ export function applyBaseConfig(
|
||||
applyNxIndependentConfig(options, config);
|
||||
|
||||
// Some of the options only work during actual tasks, not when reading the webpack config during CreateNodes.
|
||||
if (!process.env['NX_TASK_TARGET_PROJECT']) return;
|
||||
if (global.NX_GRAPH_CREATION) return;
|
||||
|
||||
applyNxDependentConfig(options, config, { useNormalizedEntry });
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user