fix: use built-in split chunks strategy by default (#6699)

* fix: use built-in split chunks strategy by default

* fix: pre bundle react refresh package

* fix: lint error
This commit is contained in:
ClarkXia 2023-12-20 11:12:06 +08:00 committed by GitHub
parent 7d193fe96b
commit dd149e51d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 369 additions and 16 deletions

View File

@ -0,0 +1,299 @@
"use strict";
/**
* The following code is modified based on the dist build of @rspack/core
*/
/**
* The following code is modified based on
* https://github.com/webpack/webpack/blob/4b4ca3b/lib/config/normalization.js
*
* MIT Licensed
* Author Tobias Koppers @sokra
* Copyright (c) JS Foundation and other contributors
* https://github.com/webpack/webpack/blob/main/LICENSE
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNormalizedRspackOptions = void 0;
const util_1 = require("../util");
const getNormalizedRspackOptions = (config) => {
return {
features: config.features,
ignoreWarnings: config.ignoreWarnings !== undefined
? config.ignoreWarnings.map(ignore => {
if (typeof ignore === "function") {
return ignore;
}
else {
return (warning) => {
return ignore.test(warning.message);
};
}
})
: undefined,
name: config.name,
dependencies: config.dependencies,
context: config.context,
mode: config.mode,
entry: config.entry === undefined
? { main: {} }
: getNormalizedEntryStatic(config.entry),
output: nestedConfig(config.output, output => {
const { library } = output;
const libraryAsName = library;
const libraryBase = typeof library === "object" &&
library &&
!Array.isArray(library) &&
"type" in library
? library
: libraryAsName || output.libraryTarget
? {
name: libraryAsName
}
: undefined;
// DEPRECATE: remove this in after version
{
const ext = "[ext]";
const filenames = [
"filename",
"chunkFilename",
"cssFilename",
"cssChunkFilename"
];
const checkFilename = (prop) => {
const oldFilename = output[prop];
if (typeof oldFilename === "string" && oldFilename.endsWith(ext)) {
const newFilename = oldFilename.slice(0, -ext.length) +
(prop.includes("css") ? ".css" : ".js");
(0, util_1.deprecatedWarn)(`Deprecated: output.${prop} ends with [ext] is now deprecated, please use ${newFilename} instead.`);
output[prop] = newFilename;
}
};
filenames.forEach(checkFilename);
}
return {
path: output.path,
publicPath: output.publicPath,
filename: output.filename,
clean: output.clean,
chunkFormat: output.chunkFormat,
chunkLoading: output.chunkLoading,
chunkFilename: output.chunkFilename,
crossOriginLoading: output.crossOriginLoading,
cssFilename: output.cssFilename,
cssChunkFilename: output.cssChunkFilename,
hotUpdateMainFilename: output.hotUpdateMainFilename,
hotUpdateChunkFilename: output.hotUpdateChunkFilename,
hotUpdateGlobal: output.hotUpdateGlobal,
assetModuleFilename: output.assetModuleFilename,
wasmLoading: output.wasmLoading,
enabledChunkLoadingTypes: output.enabledChunkLoadingTypes
? [...output.enabledChunkLoadingTypes]
: ["..."],
enabledWasmLoadingTypes: output.enabledWasmLoadingTypes
? [...output.enabledWasmLoadingTypes]
: ["..."],
webassemblyModuleFilename: output.webassemblyModuleFilename,
uniqueName: output.uniqueName,
chunkLoadingGlobal: output.chunkLoadingGlobal,
enabledLibraryTypes: output.enabledLibraryTypes
? [...output.enabledLibraryTypes]
: ["..."],
globalObject: output.globalObject,
importFunctionName: output.importFunctionName,
iife: output.iife,
module: output.module,
sourceMapFilename: output.sourceMapFilename,
library: libraryBase && {
type: output.libraryTarget !== undefined
? output.libraryTarget
: libraryBase.type,
auxiliaryComment: output.auxiliaryComment !== undefined
? output.auxiliaryComment
: libraryBase.auxiliaryComment,
amdContainer: output.amdContainer !== undefined
? output.amdContainer
: libraryBase.amdContainer,
export: output.libraryExport !== undefined
? output.libraryExport
: libraryBase.export,
name: libraryBase.name,
umdNamedDefine: output.umdNamedDefine !== undefined
? output.umdNamedDefine
: libraryBase.umdNamedDefine
},
trustedTypes: optionalNestedConfig(output.trustedTypes, trustedTypes => {
if (trustedTypes === true)
return {};
if (typeof trustedTypes === "string")
return { policyName: trustedTypes };
return { ...trustedTypes };
}),
hashDigest: output.hashDigest,
hashDigestLength: output.hashDigestLength,
hashFunction: output.hashFunction,
hashSalt: output.hashSalt,
asyncChunks: output.asyncChunks,
workerChunkLoading: output.workerChunkLoading,
workerWasmLoading: output.workerWasmLoading,
workerPublicPath: output.workerPublicPath
};
}),
resolve: nestedConfig(config.resolve, resolve => ({
...resolve
})),
resolveLoader: nestedConfig(config.resolveLoader, resolve => ({
...resolve
})),
module: nestedConfig(config.module, module => ({
parser: keyedNestedConfig(module.parser, cloneObject, {}),
generator: keyedNestedConfig(module.generator, cloneObject, {}),
defaultRules: optionalNestedArray(module.defaultRules, r => [...r]),
rules: nestedArray(module.rules, r => [...r])
})),
target: config.target,
externals: config.externals,
externalsType: config.externalsType,
externalsPresets: cloneObject(config.externalsPresets),
infrastructureLogging: cloneObject(config.infrastructureLogging),
devtool: config.devtool,
node: nestedConfig(config.node, node => node && {
...node
}),
snapshot: nestedConfig(config.snapshot, snapshot => ({
resolve: optionalNestedConfig(snapshot.resolve, resolve => ({
timestamp: resolve.timestamp,
hash: resolve.hash
})),
module: optionalNestedConfig(snapshot.module, module => ({
timestamp: module.timestamp,
hash: module.hash
}))
})),
cache: optionalNestedConfig(config.cache, cache => cache),
stats: nestedConfig(config.stats, stats => {
if (stats === false) {
return {
preset: "none"
};
}
if (stats === true) {
return {
preset: "normal"
};
}
if (typeof stats === "string") {
return {
preset: stats
};
}
return {
...stats
};
}),
optimization: nestedConfig(config.optimization, optimization => {
return {
...optimization,
runtimeChunk: getNormalizedOptimizationRuntimeChunk(optimization.runtimeChunk),
splitChunks: nestedConfig(optimization.splitChunks, splitChunks => splitChunks && {
...splitChunks,
cacheGroups: cloneObject(splitChunks.cacheGroups)
})
};
}),
plugins: nestedArray(config.plugins, p => [...p]),
experiments: nestedConfig(config.experiments, experiments => ({
...experiments,
incrementalRebuild: optionalNestedConfig(experiments.incrementalRebuild, options => (options === true ? {} : options))
})),
watch: config.watch,
watchOptions: cloneObject(config.watchOptions),
devServer: config.devServer,
profile: config.profile,
builtins: nestedConfig(config.builtins, builtins => ({
...builtins
}))
};
};
exports.getNormalizedRspackOptions = getNormalizedRspackOptions;
const getNormalizedEntryStatic = (entry) => {
if (typeof entry === "string") {
return {
main: {
import: [entry]
}
};
}
if (Array.isArray(entry)) {
return {
main: {
import: entry
}
};
}
const result = {};
for (const key of Object.keys(entry)) {
const value = entry[key];
if (typeof value === "string") {
result[key] = {
import: [value]
};
}
else if (Array.isArray(value)) {
result[key] = {
import: value
};
}
else {
result[key] = {
import: Array.isArray(value.import) ? value.import : [value.import],
runtime: value.runtime,
publicPath: value.publicPath,
baseUri: value.baseUri,
chunkLoading: value.chunkLoading,
asyncChunks: value.asyncChunks,
filename: value.filename,
library: value.library
};
}
}
return result;
};
const getNormalizedOptimizationRuntimeChunk = (runtimeChunk) => {
if (runtimeChunk === undefined)
return undefined;
if (runtimeChunk === false)
return false;
if (runtimeChunk === "single") {
return {
name: () => "runtime"
};
}
if (runtimeChunk === true || runtimeChunk === "multiple") {
return {
name: (entrypoint) => `runtime~${entrypoint.name}`
};
}
const { name } = runtimeChunk;
const opts = {
name: typeof name === "function" ? name : () => name
};
return opts;
};
const nestedConfig = (value, fn) => value === undefined ? fn({}) : fn(value);
const optionalNestedConfig = (value, fn) => (value === undefined ? undefined : fn(value));
const nestedArray = (value, fn) => Array.isArray(value) ? fn(value) : fn([]);
const optionalNestedArray = (value, fn) => (Array.isArray(value) ? fn(value) : undefined);
const cloneObject = (value) => ({ ...value });
const keyedNestedConfig = (value, fn, customKeys) => {
const result = value === undefined
? {}
: Object.keys(value).reduce((obj, key) => ((obj[key] = (customKeys && key in customKeys ? customKeys[key] : fn)(value[key])),
obj), {});
if (customKeys) {
for (const key of Object.keys(customKeys)) {
if (!(key in result)) {
result[key] = customKeys[key]({});
}
}
}
return result;
};

View File

@ -45,10 +45,10 @@
"zod": "^3.21.4",
"zod-validation-error": "1.2.0",
"terminal-link": "^2.1.1",
"@ice/pack-binding": "0.0.3",
"@rspack/plugin-react-refresh": "0.4.0"
"@ice/pack-binding": "0.0.3"
},
"devDependencies": {
"@rspack/plugin-react-refresh": "0.4.0",
"@rspack/dev-server": "0.4.0",
"@rspack/core": "0.4.0",
"@types/less": "^3.0.3",

View File

@ -230,7 +230,13 @@ const tasks = [
// Copy the entire directory.
// filter out js files and replace with compiled files.
const filePaths = globbySync(['**/*'], { cwd: pkgPath, ignore: ['node_modules'] });
const filesAddOverwrite = ['dist/config/adapter.js', 'dist/config/defaults.js', 'dist/config/zod.js', 'dist/util/bindingVersionCheck.js'];
const filesAddOverwrite = [
'dist/config/adapter.js',
'dist/config/defaults.js',
'dist/config/zod.js',
'dist/config/normalization.js',
'dist/util/bindingVersionCheck.js',
];
filePaths.forEach((filePath) => {
const sourcePath = path.join(pkgPath, filePath);
const targetFilePath = path.join(targetPath, filePath);
@ -280,6 +286,30 @@ const tasks = [
});
},
},
{
pkgName: '@rspack/plugin-react-refresh',
skipCompile: true,
patch: () => {
const pkgPath = path.join(__dirname, '../node_modules/@rspack/plugin-react-refresh');
const filePaths = globbySync(['**/*'], { cwd: pkgPath, ignore: ['node_modules'] });
filePaths.forEach((filePath) => {
fs.ensureDirSync(path.join(__dirname, `../compiled/@rspack/plugin-react-refresh/${path.dirname(filePath)}`));
const sourcePath = path.join(pkgPath, filePath);
const targetPath = path.join(__dirname, `../compiled/@rspack/plugin-react-refresh/${filePath}`);
if (path.extname(filePath) === '.js') {
const fileContent = fs.readFileSync(sourcePath, 'utf8');
fs.writeFileSync(targetPath,
replaceDeps(fileContent, webpackDevServerDeps.concat([
...commonDeps,
'@rspack/core',
])).replace(/@pmmmwh\/react-refresh-webpack-plugin\/lib\/runtime\/RefreshUtils/g, '@ice/bundles/compiled/@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils'),
);
} else {
fs.copyFileSync(sourcePath, targetPath);
}
});
},
},
];
export default tasks;

View File

@ -1,3 +1,3 @@
import RefreshPlugin from '@rspack/plugin-react-refresh';
import RefreshPlugin from '../compiled/@rspack/plugin-react-refresh/dist/index.js';
export default RefreshPlugin;

View File

@ -6,7 +6,7 @@ import type { Configuration, rspack as Rspack } from '@rspack/core';
import lodash from '@ice/bundles/compiled/lodash/index.js';
import { coreJsPath } from '@ice/bundles';
import RefreshPlugin from '@ice/bundles/esm/plugin-refresh.js';
import getSplitChunks from './splitChunks.js';
import getSplitChunks, { getFrameworkBundles } from './splitChunks.js';
import getAssetsRule from './assetsRule.js';
import getCssRules from './cssRules.js';
@ -25,6 +25,13 @@ type GetConfig = (
options: GetRspackConfigOptions,
) => Promise<Configuration>;
interface BuiltinFeatures {
splitChunksStrategy?: {
name: string;
topLevelFrameworks: string[];
};
}
const require = createRequire(import.meta.url);
const { merge } = lodash;
@ -108,6 +115,19 @@ const getConfig: GetConfig = async (options) => {
},
module: true,
}, minimizerOptions);
const builtinFeatures: BuiltinFeatures = {};
let splitChunksStrategy = null;
// Use builtin splitChunks strategy by default.
if (splitChunks === true || splitChunks === 'chunks') {
builtinFeatures.splitChunksStrategy = {
name: 'chunks',
topLevelFrameworks: getFrameworkBundles(rootDir),
};
} else {
splitChunksStrategy = typeof splitChunks == 'object'
? splitChunks
: getSplitChunks(rootDir, splitChunks);
}
const config: Configuration = {
entry: {
main: [path.join(rootDir, runtimeTmpDir, 'entry.client.tsx')],
@ -162,9 +182,7 @@ const getConfig: GetConfig = async (options) => {
},
optimization: {
minimize: !!minify,
splitChunks: typeof splitChunks == 'object'
? splitChunks
: getSplitChunks(rootDir, splitChunks),
...(splitChunksStrategy ? { splitChunks: splitChunksStrategy } : {}),
},
// @ts-expect-error plugin instance defined by default in not compatible with rspack.
plugins: [
@ -212,7 +230,7 @@ const getConfig: GetConfig = async (options) => {
...devServer,
setupMiddlewares: middlewares,
},
features: {},
features: builtinFeatures,
};
// Compatible with API configureWebpack.
const ctx = {

View File

@ -13,7 +13,7 @@ function transformPathForRegex(str: string) {
? str.replace(/\\$/, '').replace(/\\/g, '\\') : str;
}
const getChunksStrategy = (rootDir: string) => {
export const getFrameworkBundles = (rootDir: string) => {
const frameworkPaths: string[] = [];
const visitedFramework = new Set<string>();
function addPackagePath(packageName: string, dir: string) {
@ -40,7 +40,11 @@ const getChunksStrategy = (rootDir: string) => {
FRAMEWORK_BUNDLES.forEach((packageName) => {
addPackagePath(packageName, rootDir);
});
return frameworkPaths;
};
export const getChunksStrategy = (rootDir: string) => {
const frameworkPaths = getFrameworkBundles(rootDir);
// Create test rule for framework.
const frameworkTest = new RegExp(frameworkPaths.join('|'));
return {
@ -81,14 +85,14 @@ export const getVendorStrategy = (options: Configuration['splitChunks']) => {
};
};
const getSplitChunks = (rootDir: string, strategy: string | boolean) => {
const getSplitChunks = (_: string, strategy: string | boolean) => {
if (strategy === false) {
return { minChunks: Infinity, cacheGroups: { default: false } };
} else if (typeof strategy === 'string' && ['page-vendors', 'vendors'].includes(strategy)) {
const splitChunksOptions = strategy === 'page-vendors' ? { chunks: 'all' } : {};
return getVendorStrategy(splitChunksOptions);
}
return getChunksStrategy(rootDir);
return {};
};
export default getSplitChunks;

View File

@ -1279,9 +1279,6 @@ importers:
'@ice/swc-plugin-remove-export':
specifier: 0.2.0
version: 0.2.0
'@rspack/plugin-react-refresh':
specifier: 0.4.0
version: 0.4.0(react-refresh@0.14.0)(webpack-dev-server@4.15.0)(webpack@5.88.2)
'@swc/core':
specifier: 1.3.80
version: 1.3.80
@ -1370,6 +1367,9 @@ importers:
'@rspack/dev-server':
specifier: 0.4.0
version: 0.4.0(@rspack/core@0.4.0)(@swc/core@1.3.80)(esbuild@0.17.16)(react-refresh@0.14.0)
'@rspack/plugin-react-refresh':
specifier: 0.4.0
version: 0.4.0(react-refresh@0.14.0)(webpack-dev-server@4.15.0)(webpack@5.88.2)
'@types/less':
specifier: ^3.0.3
version: 3.0.3
@ -7662,6 +7662,7 @@ packages:
source-map: 0.7.4
webpack: 5.88.2(@swc/core@1.3.80)(esbuild@0.17.16)
webpack-dev-server: 4.15.0(webpack@5.88.2)
dev: true
/@polka/url@1.0.0-next.21:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
@ -8170,7 +8171,7 @@ packages:
- webpack-dev-server
- webpack-hot-middleware
- webpack-plugin-serve
dev: false
dev: true
/@sideway/address@4.1.4:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
@ -11633,6 +11634,7 @@ packages:
/common-path-prefix@3.0.0:
resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
dev: true
/commondir@1.0.1:
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}