Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: split server chunk #7040

Open
wants to merge 81 commits into
base: release/next
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
264f512
feat: last step
HomyeeKing Dec 19, 2024
0f7ee36
fix: css
HomyeeKing Dec 23, 2024
b7111f9
feat: v1
HomyeeKing Jan 2, 2025
60bdcbb
feat: skip css
HomyeeKing Jan 2, 2025
252eb4b
chore: save
HomyeeKing Jan 2, 2025
fe58611
fix: external
HomyeeKing Jan 2, 2025
490e1da
fix: ci
HomyeeKing Jan 2, 2025
9d585da
chore: rm unused diff
HomyeeKing Jan 2, 2025
3f0d671
chore: lock
HomyeeKing Jan 2, 2025
a6fa7e1
chore: changeset
HomyeeKing Jan 2, 2025
8b2efbe
fix: er
HomyeeKing Jan 2, 2025
40876d5
fix: ci
HomyeeKing Jan 2, 2025
eb2e5db
feat: prepack esbuild-loader
HomyeeKing Jan 3, 2025
4ef7f8d
feat: tsconfig plugin
HomyeeKing Jan 4, 2025
6a5b317
chore: lock
HomyeeKing Jan 4, 2025
a56748b
chore: save
HomyeeKing Jan 4, 2025
4edab02
chore: save
HomyeeKing Jan 4, 2025
cc6617e
fix: err
HomyeeKing Jan 4, 2025
8ca5444
chore: revert format
HomyeeKing Jan 4, 2025
36314c5
chore: revert format
HomyeeKing Jan 4, 2025
c5df87b
fix: require is not defined
HomyeeKing Jan 6, 2025
9f32548
fix: banner maybe undefiend
HomyeeKing Jan 6, 2025
8eb8167
chore: save
HomyeeKing Jan 6, 2025
f49a02d
chore: save
HomyeeKing Jan 6, 2025
f8609f4
fix: external node builtin
HomyeeKing Jan 6, 2025
8a677be
chore: save
HomyeeKing Jan 7, 2025
22fafbe
fix: target
HomyeeKing Jan 8, 2025
1965e16
chore: save
HomyeeKing Jan 9, 2025
73e506c
fix: chunk and exports
HomyeeKing Jan 13, 2025
2ac13ec
feat: remove sourcemap comment
HomyeeKing Jan 13, 2025
fbef850
fix: sourcemap path
HomyeeKing Jan 13, 2025
f93a678
fix: assign __quickMode
HomyeeKing Jan 13, 2025
bd0faf8
feat: provider plugin
HomyeeKing Jan 14, 2025
7800077
fix: target
HomyeeKing Jan 14, 2025
ce34573
fix: no export
HomyeeKing Jan 14, 2025
579b338
fix: manifest assets
HomyeeKing Jan 15, 2025
4258ec2
feat: add minify
HomyeeKing Jan 15, 2025
e367461
feat: control minify
HomyeeKing Jan 15, 2025
9567a92
fix: hack replace
HomyeeKing Jan 15, 2025
f690570
Revert "feat: skip css"
HomyeeKing Jan 16, 2025
2a20982
feat: disable sourcemap
HomyeeKing Jan 16, 2025
b14428a
fix: jsx importSource
HomyeeKing Jan 16, 2025
a0cd991
chore: version
HomyeeKing Jan 16, 2025
6ffb452
chore: name
HomyeeKing Jan 16, 2025
af6d933
chore: sae
HomyeeKing Jan 16, 2025
50c3234
chore: save
HomyeeKing Jan 16, 2025
121348a
chore: sa
HomyeeKing Jan 16, 2025
5760e3f
chore: split
HomyeeKing Jan 16, 2025
d7827e0
chore: save
HomyeeKing Jan 16, 2025
ed0c302
feat: format alias
HomyeeKing Jan 17, 2025
2ac6d99
fix: alias
HomyeeKing Jan 17, 2025
cb689c0
feat: allow userConfig
HomyeeKing Jan 20, 2025
51182cb
chore: version
HomyeeKing Jan 20, 2025
7e62ad3
fix: err
HomyeeKing Jan 20, 2025
7c6b8a7
chore: save
HomyeeKing Jan 20, 2025
02cd737
feat: support server config
HomyeeKing Jan 21, 2025
bb08f88
chore: save
HomyeeKing Jan 22, 2025
dc44e7a
feat: call in vendor
HomyeeKing Jan 26, 2025
cb55a64
fix: runtime and writable
HomyeeKing Jan 26, 2025
bc0266a
chore: sync code
HomyeeKing Feb 6, 2025
d297a07
feat: support split by routes
HomyeeKing Feb 7, 2025
e50b1e5
feat: update Code
HomyeeKing Feb 11, 2025
eb8ee79
feat: ice/bundles
HomyeeKing Feb 11, 2025
68b1b82
fix: type
HomyeeKing Feb 11, 2025
f999e74
fix: err
HomyeeKing Feb 11, 2025
f373eeb
chore: rm unused file
HomyeeKing Feb 11, 2025
b2a6aa2
chore: rm unused
HomyeeKing Feb 11, 2025
a994968
feat: disable chunkFormat
HomyeeKing Feb 11, 2025
8d85cc3
chore: change back
HomyeeKing Feb 11, 2025
6354d1d
feat: change set
HomyeeKing Feb 11, 2025
917268e
feat: bundle all need plugin and loaders
HomyeeKing Feb 11, 2025
4b5429e
chore: changeset
HomyeeKing Feb 11, 2025
8e6ceee
feat: suppor transformInclude a array
HomyeeKing Feb 13, 2025
0017d9c
feat: handle svg and custom esbuild-loader
HomyeeKing Feb 13, 2025
8f4b5c0
feat: support handle assets
HomyeeKing Feb 13, 2025
9b0576a
feat: use esbuildMinify
HomyeeKing Feb 17, 2025
4208450
feat: minify css
HomyeeKing Feb 17, 2025
b8b4c92
chore: changeset
HomyeeKing Feb 19, 2025
e33705b
chore: changeset
HomyeeKing Feb 19, 2025
39eb3f5
chore: lock
HomyeeKing Feb 27, 2025
bc0c3a4
chore: merge code
HomyeeKing Feb 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/cyan-emus-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@ice/bundles': patch
'@ice/app': patch
---

@ice/app: align the output result with the former esbuild
@ice/bundles: export more webpack internal modules
7 changes: 7 additions & 0 deletions .changeset/forty-lamps-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@ice/bundles': patch
'@ice/app': patch
---

@ice/app: remove unused deps and import them from @ice/bundles
@ice/bundles: compile tsconfig-paths-webpack-plugin
5 changes: 5 additions & 0 deletions .changeset/khaki-maps-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/app': patch
---

rebase releast/next
7 changes: 7 additions & 0 deletions .changeset/neat-queens-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@ice/app': patch
---

feat: minify css file;
feat: change minifier from terser to esbuildMinifier.
feat: support config minify option
8 changes: 8 additions & 0 deletions .changeset/rude-games-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@ice/app': patch
---

- feat: change transformInclude to array
- fix: only treat .js as jsx
- feat: support customize webpack.module.rule
- feat: support handle assets
5 changes: 5 additions & 0 deletions .changeset/spotty-otters-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ice/app': minor
---

support split server bundle
100 changes: 51 additions & 49 deletions packages/bundles/package.json
Original file line number Diff line number Diff line change
@@ -15,103 +15,105 @@
"main": "./esm/index.js",
"type": "module",
"dependencies": {
"@swc/core": "1.3.80",
"@ice/swc-plugin-remove-export": "0.2.0",
"@ice/css-modules-hash": "0.0.10",
"@ice/pack-binding": "0.0.13",
"@ice/swc-plugin-keep-export": "0.2.0",
"@ice/swc-plugin-node-transform": "0.2.0",
"@ice/swc-plugin-remove-export": "0.2.0",
"@swc/core": "1.3.80",
"ansi-html-community": "^0.0.8",
"html-entities": "^2.3.2",
"core-js": "3.32.0",
"browserslist": "^4.21.3",
"caniuse-lite": "^1.0.30001561",
"chokidar": "3.5.3",
"compare-versions": "6.0.0-rc.1",
"core-js": "3.32.0",
"core-js-pure": "^3.8.1",
"enhanced-resolve": "5.12.0",
"error-stack-parser": "^2.0.6",
"esbuild": "^0.17.16",
"events": "3.3.0",
"fast-querystring": "1.1.2",
"html-entities": "^2.3.2",
"jest-worker": "27.5.1",
"json-parse-even-better-errors": "^3.0.0",
"less": "4.1.2",
"mime-types": "2.1.35",
"neo-async": "2.6.2",
"postcss": "8.4.31",
"sass": "1.50.0",
"react-refresh": "0.14.0",
"core-js-pure": "^3.8.1",
"error-stack-parser": "^2.0.6",
"@ice/css-modules-hash": "0.0.10",
"browserslist": "^4.21.3",
"compare-versions": "6.0.0-rc.1",
"enhanced-resolve": "5.12.0",
"fast-querystring": "1.1.2",
"json-parse-even-better-errors": "^3.0.0",
"neo-async": "2.6.2",
"sass": "1.50.0",
"terminal-link": "^2.1.1",
"tsconfig-paths-webpack-plugin": "^4.2.0",
"watchpack": "^2.4.0",
"webpack-sources": "3.2.3",
"zod": "^3.22.3",
"zod-validation-error": "1.2.0",
"terminal-link": "^2.1.1",
"@ice/pack-binding": "0.0.13",
"mime-types": "2.1.35"
"zod-validation-error": "1.2.0"
},
"devDependencies": {
"@rspack/plugin-react-refresh": "0.5.7",
"@rspack/dev-server": "0.5.7",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
"@rspack/core": "0.5.7",
"@rspack/dev-server": "0.5.7",
"@rspack/plugin-react-refresh": "0.5.7",
"@types/less": "^3.0.3",
"@types/lodash": "^4.14.181",
"@types/webpack-bundle-analyzer": "^4.4.1",
"bonjour-service": "^1.0.13",
"cacache": "17.0.4",
"colorette": "^2.0.10",
"common-path-prefix": "3.0.0",
"compression": "^1.7.4",
"connect-history-api-fallback": "2.0.0",
"copy-webpack-plugin": "10.2.4",
"css-loader": "6.7.1",
"css-minimizer-webpack-plugin": "3.4.1",
"cssnano": "^5.1.7",
"es-module-lexer": "1.6.0",
"default-gateway": "^6.0.3",
"es-module-lexer": "0.10.5",
"esbuild-loader": "^4.2.2",
"esbuild-register": "3.4.1",
"eslint": "^8.14.0",
"eslint-webpack-plugin": "3.1.1",
"express": "^4.19.2",
"find-up": "5.0.0",
"fork-ts-checker-webpack-plugin": "7.2.6",
"fs-extra": "^10.0.0",
"globby": "13.1.2",
"graceful-fs": "4.2.10",
"http-proxy-middleware": "^2.0.3",
"ipaddr.js": "^2.0.1",
"less-loader": "10.2.0",
"loader-utils": "^2.0.0",
"lodash": "4.17.21",
"magic-string": "0.27.0",
"mini-css-extract-plugin": "2.6.1",
"open": "^8.0.9",
"ora": "5.4.1",
"p-retry": "^4.5.0",
"portfinder": "^1.0.28",
"postcss-loader": "6.2.1",
"postcss-modules": "4.3.1",
"postcss-nested": "5.0.6",
"postcss-plugin-rpx2vw": "1.0.0",
"postcss-preset-env": "7.4.3",
"rimraf": "^3.0.2",
"sass-loader": "12.6.0",
"schema-utils": "^4.0.0",
"selfsigned": "^2.0.1",
"serve-index": "^1.9.1",
"sockjs": "^0.3.21",
"source-map": "0.8.0-beta.0",
"spdy": "^4.0.2",
"tapable": "2.2.1",
"terser": "5.14.2",
"terser-webpack-plugin": "5.3.5",
"typescript": "^4.6.4",
"trusted-cert": "1.1.3",
"typescript": "^4.6.4",
"unplugin": "1.6.0",
"webpack": "5.88.2",
"webpack-bundle-analyzer": "4.5.0",
"webpack-dev-server": "4.15.0",
"unplugin": "1.6.0",
"bonjour-service": "^1.0.13",
"colorette": "^2.0.10",
"compression": "^1.7.4",
"connect-history-api-fallback": "2.0.0",
"default-gateway": "^6.0.3",
"express": "^4.19.2",
"graceful-fs": "4.2.10",
"http-proxy-middleware": "^2.0.3",
"ipaddr.js": "^2.0.1",
"open": "^8.0.9",
"p-retry": "^4.5.0",
"portfinder": "^1.0.28",
"rimraf": "^3.0.2",
"schema-utils": "^4.0.0",
"selfsigned": "^2.0.1",
"serve-index": "^1.9.1",
"sockjs": "^0.3.21",
"spdy": "^4.0.2",
"webpack-dev-middleware": "^5.3.4",
"ws": "^8.4.2",
"globby": "13.1.2",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
"loader-utils": "^2.0.0",
"source-map": "0.8.0-beta.0",
"find-up": "5.0.0",
"common-path-prefix": "3.0.0"
"webpack-dev-server": "4.15.0",
"ws": "^8.4.2"
},
"publishConfig": {
"access": "public",
8 changes: 7 additions & 1 deletion packages/bundles/scripts/tasks.ts
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@ export function filterExternals(externals: Record<string, string>, keys: string[
const tasks = [
// simple task
...['cssnano', 'tapable', 'schema-utils', 'lodash',
'less-loader', 'postcss-loader', 'sass-loader', 'css-loader',
'less-loader', 'postcss-loader', 'sass-loader', 'css-loader', 'esbuild-loader',
'postcss-preset-env', 'postcss-nested', 'postcss-modules', 'postcss-plugin-rpx2vw',
'webpack-bundle-analyzer', 'es-module-lexer', 'terser', 'trusted-cert', 'magic-string',
'eslint-webpack-plugin', 'copy-webpack-plugin', 'cacache', 'ora', 'unplugin',
@@ -102,6 +102,12 @@ const tasks = [
file: 'node_modules/unplugin/dist/rspack/loaders/load.js',
bundleName: 'rspack/loaders/load.js',
},
{
pkgName: 'tsconfig-paths-webpack-plugin',
declaration: false,
emptyDir: false,
externals: taskExternals,
},
{
// pack main package
pkgName: 'fork-ts-checker-webpack-plugin',
5 changes: 5 additions & 0 deletions packages/bundles/webpack/bundle.js
Original file line number Diff line number Diff line change
@@ -10,6 +10,11 @@ module.exports = {
SingleEntryPlugin: require('webpack/lib/SingleEntryPlugin'),
FetchCompileAsyncWasmPlugin: require('webpack/lib/web/FetchCompileAsyncWasmPlugin'),
FetchCompileWasmPlugin: require('webpack/lib/web/FetchCompileWasmPlugin'),
JavascriptModulesPlugin: require('webpack/lib/javascript/JavascriptModulesPlugin'),
StartupChunkDependenciesPlugin: require('webpack/lib/runtime/StartupChunkDependenciesPlugin'),
StartupHelpers: require('webpack/lib/javascript/StartupHelpers'),
compileBooleanMatcher: require('webpack/lib/util/compileBooleanMatcher'),
identifier: require('webpack/lib/util/identifier'),
StringXor: require('webpack/lib/util/StringXor'),
NormalModule: require('webpack/lib/NormalModule'),
EntryDependency: require('webpack/lib/dependencies/EntryDependency'),
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./bundle').JavascriptModulesPlugin;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./bundle').StartupChunkDependenciesPlugin;
1 change: 1 addition & 0 deletions packages/bundles/webpack/packages/StartupHelpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./bundle').StartupHelpers;
1 change: 1 addition & 0 deletions packages/bundles/webpack/packages/compileBooleanMatcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./bundle').compileBooleanMatcher;
1 change: 1 addition & 0 deletions packages/bundles/webpack/packages/identifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./bundle').identifier;
2 changes: 1 addition & 1 deletion packages/ice/package.json
Original file line number Diff line number Diff line change
@@ -108,4 +108,4 @@
"publishConfig": {
"access": "public"
}
}
}
24 changes: 17 additions & 7 deletions packages/ice/src/requireHook.ts
Original file line number Diff line number Diff line change
@@ -28,10 +28,24 @@ export function getHookFiles() {
'webpack/lib/ExternalsPlugin',
'webpack/lib/web/FetchCompileAsyncWasmPlugin',
'webpack/lib/web/FetchCompileWasmPlugin',
'webpack/lib/runtime/StartupChunkDependenciesPlugin',
'webpack/lib/javascript/JavascriptModulesPlugin',
'webpack/lib/javascript/StartupHelpers',
'webpack/lib/util/identifier',
'webpack/lib/util/compileBooleanMatcher',
];
const webpackDir = path.join(require.resolve('@ice/bundles/compiled/webpack'), '../');
const pluginMap = webpackPlugins.map((pluginPath) => {
return [pluginPath, pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker)\/)?/, webpackDir)];
return [
pluginPath,
pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util)\/)?/, webpackDir),
];
});
const pluginMapWithJs = webpackPlugins.map((pluginPath) => {
return [
`${pluginPath}.js`,
pluginPath.replace(/^webpack\/lib\/((web|node|optimize|webworker|runtime|javascript|util)\/)?/, webpackDir),
];
});

return [
@@ -42,6 +56,7 @@ export function getHookFiles() {
['webpack/hot/only-dev-server', `${webpackDir}hot/only-dev-server`],
['webpack/hot/emitter', `${webpackDir}hot/emitter`],
...pluginMap,
...pluginMapWithJs,
];
}

@@ -52,12 +67,7 @@ function hijackWebpack() {
// eslint-disable-next-line global-require
const mod = require('module');
const resolveFilename = mod._resolveFilename;
mod._resolveFilename = function (
request: string,
parent: any,
isMain: boolean,
options: any,
) {
mod._resolveFilename = function (request: string, parent: any, isMain: boolean, options: any) {
const hookResolved = hookPropertyMap.get(request);
if (hookResolved) request = hookResolved;
return resolveFilename.call(mod, request, parent, isMain, options);
43 changes: 40 additions & 3 deletions packages/ice/src/service/serverCompiler.ts
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ import getCSSModuleIdent from '../utils/getCSSModuleIdent.js';
import { scanImports } from './analyze.js';
import type { PreBundleDepsMetaData } from './preBundleDeps.js';
import preBundleDeps from './preBundleDeps.js';
import { WebpackServerCompiler } from './webpackServerCompiler/compiler.js';
import VirualAssetPlugin from './webpackServerCompiler/virtualAssetPlugin.js';

const logger = createLogger('server-compiler');

@@ -100,7 +102,6 @@ export function createServerCompiler(options: Options) {
const externals = task.config?.externals || {};
const sourceMap = task.config?.sourceMap;
const dev = command === 'start';

// Filter empty alias.
const { ignores, alias } = filterAlias(task.config?.alias || {});

@@ -144,6 +145,20 @@ export function createServerCompiler(options: Options) {
redirectImports,
getRoutesFile,
}, 'esbuild', { isServer });
const transformWebpackPlugins = getCompilerPlugins(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没必要同时获取 transformWebpackPlugins 和 esbuildPlugins

rootDir,
{
...task.config,
fastRefresh: false,
enableEnv,
polyfill: false,
swcOptions,
redirectImports,
getRoutesFile,
},
'webpack',
{ isServer },
);
const define = getRuntimeDefination(task.config?.define || {}, runtimeDefineVars, transformEnv);
if (preBundle) {
const plugins = [
@@ -171,7 +186,6 @@ export function createServerCompiler(options: Options) {
}

const format = customBuildOptions?.format || 'esm';

let buildOptions: esbuild.BuildOptions = {
bundle: true,
format,
@@ -245,7 +259,30 @@ export function createServerCompiler(options: Options) {
context = await esbuild.context(buildOptions);
esbuildResult = await context.rebuild();
} else {
esbuildResult = await esbuild.build(buildOptions);
if (Array.isArray(buildOptions.entryPoints)) {
// this build phase is aimed to generate .ice/
esbuildResult = await esbuild.build(buildOptions);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为啥多 entry 必定是 esbuild

} else {
switch (server.bundler) {
case 'webpack':
const webpackServerCompiler = new WebpackServerCompiler({
...buildOptions,
externals,
plugins: [
compilationInfo && new VirualAssetPlugin({ compilationInfo, rootDir }),
...transformWebpackPlugins,
].filter(Boolean),
rootDir,
userServerConfig: server,
});
esbuildResult = (await webpackServerCompiler.build())?.compilation;
break;
case 'esbuild':
default:
esbuildResult = await esbuild.build(buildOptions);
break;
}
}
}

logger.debug('[esbuild]', `time cost: ${new Date().getTime() - startTime}ms`);
314 changes: 314 additions & 0 deletions packages/ice/src/service/webpackServerCompiler/compiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
import { createRequire } from 'module';
import path from 'path';
import { fileURLToPath } from 'url';
import { esbuild, less, sass } from '@ice/bundles';
import MiniCssExtractPlugin from '@ice/bundles/compiled/mini-css-extract-plugin/dist/index.js';
import TerserPlugin from '@ice/bundles/compiled/terser-webpack-plugin/index.js';
import { getCSSModuleLocalIdent, getPostcssOpts } from '@ice/shared-config';
import type { Config } from '@ice/shared-config/types';
import TsconfigPathsPlugin from '@ice/bundles/compiled/tsconfig-paths-webpack-plugin/index.js';
import CssMinimizerPlugin from '@ice/bundles/compiled/css-minimizer-webpack-plugin/index.js';
import webpack, { type LoaderContext } from 'webpack';
import type { UserConfig } from '../../types/userConfig.js';
import { logger } from '../../utils/logger.js';

const require = createRequire(import.meta.url);
const _dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
interface Options {
publicPath: string;
postcssOptions: Config['postcss'];
rootDir: string;
enableRpx2Vw: boolean;
cssModules: Config['cssModules'];
}

type CSSRuleConfig = [string, string?, Record<string, any>?];

export const ASSET_TYPES = [
// images
'png',
'jpe?g',
'gif',
'svg',
'ico',
'webp',
'avif',

// media
'mp4',
'webm',
'ogg',
'mp3',
'wav',
'flac',
'aac',

// fonts
'woff2?',
'eot',
'ttf',
'otf',

// other
'wasm',
'webmanifest',
'pdf',
'txt',
];

const ASSETS_RE = new RegExp(`\\.(${ASSET_TYPES.join('|')})(\\?.*)?$`);

export class WebpackServerCompiler {
private config: webpack.Configuration;
private options: { userServerConfig: UserConfig['server']; [key: string]: any };

constructor(options: any) {
this.options = options;
this.config = this.createWebpackConfig(options);
}
private configCSSRule(config: CSSRuleConfig, options: Options) {
const { publicPath, rootDir, enableRpx2Vw, postcssOptions: userPostcssOptions, cssModules } = options;
const [style, loader, loaderOptions] = config;
const cssLoaderOpts = {
sourceMap: false,
};
const cssModuleLoaderOpts = {
...cssLoaderOpts,
modules: {
auto: (resourcePath: string) => resourcePath.endsWith(`.module.${style}`),
getLocalIdent: (context: LoaderContext<any>, localIdentName: string, localName: string) => {
return getCSSModuleLocalIdent(context.resourcePath, localName, cssModules?.localIdentName);
},
},
};
const postcssOpts = getPostcssOpts({ rootDir, userPostcssOptions, enableRpx2Vw });
return {
test: new RegExp(`\\.${style}$`),
use: [
{
loader: MiniCssExtractPlugin.loader,
// compatible with commonjs syntax: const styles = require('./index.module.less')
options: {
esModule: false,
publicPath,
},
},
{
loader: require.resolve('@ice/bundles/compiled/css-loader'),
options: cssModuleLoaderOpts,
},
{
loader: require.resolve('@ice/bundles/compiled/postcss-loader'),
options: {
...cssLoaderOpts,
...postcssOpts,
},
},
loader && {
loader,
options: { ...cssLoaderOpts, ...loaderOptions },
},
].filter(Boolean),
};
}

private createWebpackConfig(options: {
userServerConfig: UserConfig['server'];
[key: string]: any;
}): webpack.Configuration {
const { userServerConfig } = options;
const { webpackConfig = {} } = userServerConfig;
const cssRules = [
['css'],
[
'less',
require.resolve('@ice/bundles/compiled/less-loader'),
{
lessOptions: { javascriptEnabled: true },
implementation: less,
},
],
[
'scss',
require.resolve('@ice/bundles/compiled/sass-loader'),
{
implementation: sass,
},
],
].map((config: any) =>
this.configCSSRule(config, {
publicPath: '/',
postcssOptions: {},
rootDir: process.cwd(),
enableRpx2Vw: true,
cssModules: {},
}),
);
const cssOutputFolder = 'css';
const hashKey = '';
const cssFilename = undefined;
const cssChunkFilename = undefined;

for (const key of Object.keys(options.alias)) {
if (options.alias[key].startsWith('./')) {
options.alias[key] = path.resolve(options.rootDir, options.alias[key]);
}
}
return {
mode: 'production',
entry: options.entryPoints as string[],
target: 'node12.20',
externalsPresets: {
node: false,
},
output: {
filename: `[name].${options.format === 'esm' ? 'mjs' : 'cjs'}`,
path: options.outdir,
// align the output with former esbuild
chunkFormat: false,
clean: true,
library: {
type: 'commonjs2',
},
...(webpackConfig.output as any),
},
devtool: 'source-map',
externals: options.externals,
optimization: {
minimize: !!options.minify,
minimizer: [
new TerserPlugin({
extractComments: false,
terserOptions: typeof options.minify === 'object' ? options.minify : undefined,
minify: TerserPlugin.esbuildMinify,
}),
new CssMinimizerPlugin({
parallel: false,
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true },
},
],
},
}),
],
...(webpackConfig.optimization as any),
},
resolve: {
alias: options.alias,
fallback: { crypto: false },
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '...'],
plugins: [
new TsconfigPathsPlugin({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  这个是必要插件吗,理论上编译不受 tsconfig 影响

/* options: see below */
}),
],
},
module: {
rules: [
// Use esbuild to compile JavaScript & TypeScript
{
// // Match `.js`, `.jsx`, `.ts` or `.tsx` files
test: /\.m?[jt]sx?$/,
exclude(path) {
if (path.includes('node_modules')) {
if (webpackConfig.transformInclude) {
return !webpackConfig.transformInclude.some((i) => {
if (i instanceof RegExp) {
return i.test(path);
} else if (typeof i === 'string') {
return path.includes(i);
} else {
return false;
}
});
} else {
return true;
}
}
return false;
},
use: [
(info: any) => {
const ext = path.extname(info.resource);
return {
loader: '@ice/bundles/compiled/esbuild-loader',
// available options: https://github.com/evanw/esbuild/blob/88821b7e7d46737f633120f91c65f662eace0bcf/lib/shared/types.ts#L158-L172
options: {
target: options.target,
// make sure tree shaking is worked
format: 'esm',
loader: ext === '.js' ? 'jsx' : 'default',
jsx: options.jsx,
jsxImportSource: '@ice/runtime/react',
sourcemap: options.sourcemap,
define: options.define,
// banner can only be string in transform mode
banner: options.banner?.js,
implementation: esbuild,
},
};
},
path.resolve(_dirname, 'removeMagicString.js'),
],
},
{
test: ASSETS_RE,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webpack 大部分规则是不是跟 csr 对齐就好了,没必要单独这里维护,对于 node 端而言主要是产物 target 和样式处理规则的差异

type: 'asset/inline',
},
...cssRules,
...(webpackConfig.module?.rules || []),
],
},
plugins: [
...options.plugins,
new webpack.DefinePlugin(options.define),
new webpack.SourceMapDevToolPlugin({
// remove append sourcemap comment
append: false,
filename: '[file].map',
moduleFilenameTemplate: '[absolute-resource-path]',
}),
new MiniCssExtractPlugin({
filename: cssFilename || `${cssOutputFolder}/${hashKey ? `[name]-[${hashKey}].css` : '[name].css'}`,
chunkFilename: cssChunkFilename || `${cssOutputFolder}/${hashKey ? `[name]-[${hashKey}].css` : '[name].css'}`,
// If the warning is triggered, it seen to be unactionable for the user,
ignoreOrder: true,
}),
...(webpackConfig.plugins || []),
],
stats: {
errorDetails: true,
},
};
}

private async handleEsbuildInject() {
const provideRecord = {};
const allInjects = await Promise.all(this.options.inject.map((inj) => import(inj)));
allInjects.forEach((injs, index) => {
Object.keys(injs).forEach((key) => {
provideRecord[key] = [this.options.inject[index], key];
});
});
return new webpack.ProvidePlugin(provideRecord);
}

async build(): Promise<any> {
const providePlugin = await this.handleEsbuildInject();
this.config.plugins.push(providePlugin);
return new Promise((resolve, reject) => {
webpack(this.config, (err, stats) => {
if (err || stats?.hasErrors?.()) {
logger.error(err || stats.toString());
reject(err || stats.toString());
process.exit(1);
} else {
resolve(stats);
}
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function (source: string) {
const result = source.replace(
/webpackChunkName:\s*["'][^"']+["']/g,
'webpackMode: "eager"',
);

// Return the modified source
return result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { type AssetsManifest } from '@ice/runtime/types';
import NormalModule from 'webpack/lib/NormalModule.js';
import { type Compiler } from '@ice/bundles/compiled/webpack';

interface CompilationInfo {
assetsManifest?: AssetsManifest;
}

const PLUGIN_NAME = 'VirtualManifestPlugin';
class VirtualManifestPlugin {
private rootDir: string;
private compilationInfo: CompilationInfo | (() => CompilationInfo);

constructor(options) {
this.rootDir = options.rootDir;
this.compilationInfo = options.compilationInfo;
}

apply(compiler: Compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => {
NormalModule.getCompilationHooks(compilation)
.readResource.for('virtual')
.tap(PLUGIN_NAME, () => {
const manifest = this.generateManifestContent();
return JSON.stringify(manifest?.assetsManifest || '');
});
normalModuleFactory.hooks.beforeResolve.tap(PLUGIN_NAME, (resolveData) => {
if (resolveData.request === 'virtual:assets-manifest.json') {
resolveData.assertions = {
type: 'json',
};
}
});
});
}

generateManifestContent() {
return typeof this.compilationInfo === 'function' ? this.compilationInfo() : this.compilationInfo;
}
}

export default VirtualManifestPlugin;
17 changes: 17 additions & 0 deletions packages/ice/src/types/userConfig.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import type { DefineRouteFunction, RouteItem } from '@ice/route-manifest';
import type { PluginList } from 'build-scripts';
import type { UnpluginOptions } from '@ice/bundles/compiled/unplugin/index.js';
import type { ProcessOptions } from '@ice/bundles';
import type { Configuration as WebpackConfiguration } from '@ice/bundles/compiled/webpack';
import type { Config, ModifyWebpackConfig, MinimizerOptions } from '@ice/shared-config/types';
import type { OverwritePluginAPI } from './plugin';

@@ -242,6 +243,22 @@ export interface UserConfig {
* externals config for server bundle
*/
externals?: string[];
/**
* bundler for server bundle, support webpack and esbuild
* @default esbuild
*/
bundler?: 'webpack' | 'esbuild';
/**
* webpack config, only works when bundler is webpack
*/
webpackConfig?: Pick<WebpackConfiguration, 'plugins' | 'optimization' | 'output' | 'module'> & {
/**
* we exclude the node_modules/* by default
*
* use this if you need to transform some packages inside of node_modues
*/
transformInclude?: Array<RegExp | string>;
};
};
/**
* Optimization options for build.
742 changes: 506 additions & 236 deletions pnpm-lock.yaml

Large diffs are not rendered by default.