@modern-js/app-tools 3.2.2 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/builder/shared/builderPlugins/adapterSSR.js +30 -4
- package/dist/cjs/builder/shared/bundlerPlugins/RouterPlugin.js +32 -3
- package/dist/cjs/builder/shared/lazyCompilation.js +94 -0
- package/dist/cjs/config/default.js +18 -2
- package/dist/cjs/plugins/analyze/index.js +3 -1
- package/dist/cjs/plugins/initialize/index.js +10 -1
- package/dist/esm/builder/shared/builderPlugins/adapterSSR.mjs +31 -5
- package/dist/esm/builder/shared/bundlerPlugins/RouterPlugin.mjs +32 -3
- package/dist/esm/builder/shared/lazyCompilation.mjs +44 -0
- package/dist/esm/config/default.mjs +14 -1
- package/dist/esm/plugins/analyze/index.mjs +3 -1
- package/dist/esm/plugins/initialize/index.mjs +11 -2
- package/dist/esm-node/builder/shared/builderPlugins/adapterSSR.mjs +31 -5
- package/dist/esm-node/builder/shared/bundlerPlugins/RouterPlugin.mjs +32 -3
- package/dist/esm-node/builder/shared/lazyCompilation.mjs +45 -0
- package/dist/esm-node/config/default.mjs +14 -1
- package/dist/esm-node/plugins/analyze/index.mjs +3 -1
- package/dist/esm-node/plugins/initialize/index.mjs +11 -2
- package/dist/types/builder/shared/bundlerPlugins/RouterPlugin.d.ts +3 -1
- package/dist/types/builder/shared/lazyCompilation.d.ts +43 -0
- package/dist/types/builder/shared/types.d.ts +10 -0
- package/dist/types/config/default.d.ts +6 -0
- package/dist/types/types/plugin.d.ts +14 -0
- package/package.json +13 -13
|
@@ -36,18 +36,27 @@ const utils_namespaceObject = require("@modern-js/utils");
|
|
|
36
36
|
const core_namespaceObject = require("@rsbuild/core");
|
|
37
37
|
const utils_js_namespaceObject = require("../../../plugins/analyze/utils.js");
|
|
38
38
|
const index_js_namespaceObject = require("../bundlerPlugins/index.js");
|
|
39
|
+
const external_lazyCompilation_js_namespaceObject = require("../lazyCompilation.js");
|
|
39
40
|
const builderPluginAdapterSSR = (options)=>({
|
|
40
41
|
name: 'builder-plugin-adapter-modern-ssr',
|
|
41
42
|
setup (api) {
|
|
42
|
-
const { normalizedConfig } = options;
|
|
43
|
-
api.modifyRsbuildConfig((config)=>
|
|
43
|
+
const { normalizedConfig, appContext, eagerRouteComponentFilesByEntry } = options;
|
|
44
|
+
api.modifyRsbuildConfig((config)=>{
|
|
45
|
+
const merged = (0, core_namespaceObject.mergeRsbuildConfig)(config, {
|
|
44
46
|
html: {
|
|
45
47
|
inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
|
|
46
48
|
},
|
|
47
49
|
server: {
|
|
48
50
|
compress: isStreamingSSR(normalizedConfig) || (0, utils_namespaceObject.isUseRsc)(normalizedConfig) ? false : void 0
|
|
49
51
|
}
|
|
50
|
-
})
|
|
52
|
+
});
|
|
53
|
+
const lazyCompilation = getSSRLazyCompilation(merged.dev?.lazyCompilation, normalizedConfig, appContext, eagerRouteComponentFilesByEntry);
|
|
54
|
+
if (void 0 !== lazyCompilation) merged.dev = {
|
|
55
|
+
...merged.dev,
|
|
56
|
+
lazyCompilation
|
|
57
|
+
};
|
|
58
|
+
return merged;
|
|
59
|
+
});
|
|
51
60
|
api.modifyBundlerChain(async (chain, { target, isProd, HtmlPlugin: HtmlBundlerPlugin, isServer, environment })=>{
|
|
52
61
|
const builderConfig = environment.config;
|
|
53
62
|
const { normalizedConfig } = options;
|
|
@@ -83,6 +92,22 @@ const isStreamingSSR = (userConfig)=>{
|
|
|
83
92
|
}
|
|
84
93
|
return false;
|
|
85
94
|
};
|
|
95
|
+
function getSSRLazyCompilation(current, normalizedConfig, appContext, eagerRouteComponentFilesByEntry) {
|
|
96
|
+
if (!current || (0, utils_namespaceObject.isUseRsc)(normalizedConfig) || !isStreamingSSR(normalizedConfig)) return;
|
|
97
|
+
const plan = (0, external_lazyCompilation_js_namespaceObject.planSSRLazyCompilation)(current, (0, external_lazyCompilation_js_namespaceObject.aggregateEagerRouteComponentFiles)(eagerRouteComponentFilesByEntry));
|
|
98
|
+
if (!plan.apply) {
|
|
99
|
+
if (plan.unresolvedByEntry) warnUnresolvedRouteComponents(appContext.appDirectory, plan.unresolvedByEntry);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
return plan.lazyCompilation;
|
|
103
|
+
}
|
|
104
|
+
const warnedLazyApps = new Set();
|
|
105
|
+
function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
|
|
106
|
+
if (warnedLazyApps.has(appDirectory)) return;
|
|
107
|
+
warnedLazyApps.add(appDirectory);
|
|
108
|
+
const detail = Array.from(unresolvedByEntry).map(([entry, comps])=>`${entry}: ${comps.join(', ')}`).join('; ');
|
|
109
|
+
utils_namespaceObject.logger.warn(`[lazyCompilation] Skipped stream SSR route-eager optimization because some route components could not be resolved to a file (${detail}). Lazy compilation may break first-screen CSS/JS for these routes.`);
|
|
110
|
+
}
|
|
86
111
|
function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
|
|
87
112
|
if (isStreamingSSR(modernConfig) || (0, utils_namespaceObject.isUseRsc)(modernConfig)) chain.plugin('html-async-chunk').use(index_js_namespaceObject.HtmlAsyncChunkPlugin, [
|
|
88
113
|
HtmlBundlerPlugin
|
|
@@ -102,7 +127,8 @@ function applyRouterPlugin(chain, pluginName, options, HtmlBundlerPlugin) {
|
|
|
102
127
|
staticJsDir: normalizedConfig.output?.distPath?.js,
|
|
103
128
|
disableFilenameHash: normalizedConfig.output?.filenameHash === false,
|
|
104
129
|
scriptLoading: normalizedConfig.html?.scriptLoading,
|
|
105
|
-
nonce: normalizedConfig.security?.nonce
|
|
130
|
+
nonce: normalizedConfig.security?.nonce,
|
|
131
|
+
useRsc: (0, utils_namespaceObject.isUseRsc)(normalizedConfig)
|
|
106
132
|
}
|
|
107
133
|
]);
|
|
108
134
|
}
|
|
@@ -96,6 +96,25 @@ class RouterPlugin {
|
|
|
96
96
|
routeAssets: {}
|
|
97
97
|
});
|
|
98
98
|
const prevManifest = JSON.parse(prevManifestStr);
|
|
99
|
+
const namedChunkGroupInstances = new Map();
|
|
100
|
+
for (const cg of compilation.chunkGroups || [])if (cg.name) namedChunkGroupInstances.set(cg.name, cg);
|
|
101
|
+
const collectDescendantCssAssets = (name)=>{
|
|
102
|
+
const root = namedChunkGroupInstances.get(name);
|
|
103
|
+
if (!root) return [];
|
|
104
|
+
const cssFiles = new Set();
|
|
105
|
+
const visited = new Set();
|
|
106
|
+
const stack = [
|
|
107
|
+
...root.childrenIterable
|
|
108
|
+
];
|
|
109
|
+
while(stack.length){
|
|
110
|
+
const child = stack.pop();
|
|
111
|
+
if (visited.has(child)) continue;
|
|
112
|
+
visited.add(child);
|
|
113
|
+
for (const chunk of child.chunks)for (const file of chunk.files)if (/\.css$/.test(file)) cssFiles.add(publicPath ? normalizePath(publicPath) + file : file);
|
|
114
|
+
for (const c of child.childrenIterable)stack.push(c);
|
|
115
|
+
}
|
|
116
|
+
return Array.from(cssFiles);
|
|
117
|
+
};
|
|
99
118
|
const asyncEntryNames = [];
|
|
100
119
|
for (const [name, chunkGroup] of Object.entries(namedChunkGroups)){
|
|
101
120
|
if (name.startsWith('async-')) asyncEntryNames.push(name);
|
|
@@ -103,7 +122,12 @@ class RouterPlugin {
|
|
|
103
122
|
const filename = asset.name;
|
|
104
123
|
return publicPath ? normalizePath(publicPath) + filename : filename;
|
|
105
124
|
});
|
|
106
|
-
const
|
|
125
|
+
const directCssAssets = assets.filter((asset)=>/\.css$/.test(asset));
|
|
126
|
+
const descendantCssAssets = collectDescendantCssAssets(name).filter((asset)=>!directCssAssets.includes(asset));
|
|
127
|
+
const referenceCssAssets = [
|
|
128
|
+
...directCssAssets,
|
|
129
|
+
...descendantCssAssets
|
|
130
|
+
];
|
|
107
131
|
routeAssets[name] = {
|
|
108
132
|
chunkIds: chunkGroup.chunks,
|
|
109
133
|
assets,
|
|
@@ -145,10 +169,14 @@ class RouterPlugin {
|
|
|
145
169
|
const manifest = {
|
|
146
170
|
routeAssets: relatedAssets
|
|
147
171
|
};
|
|
172
|
+
const { useRsc } = this;
|
|
148
173
|
const injectedContent = `
|
|
149
174
|
;(function(){
|
|
150
175
|
window.${constants_namespaceObject.ROUTE_MANIFEST} = ${JSON.stringify(manifest, (k, v)=>{
|
|
151
|
-
if (('assets' === k || 'referenceCssAssets' === k) && Array.isArray(v))
|
|
176
|
+
if (('assets' === k || 'referenceCssAssets' === k) && Array.isArray(v)) {
|
|
177
|
+
if (!useRsc) return;
|
|
178
|
+
return v.map((item)=>item.replace(publicPath, ''));
|
|
179
|
+
}
|
|
152
180
|
return v;
|
|
153
181
|
})};
|
|
154
182
|
})();
|
|
@@ -176,7 +204,7 @@ class RouterPlugin {
|
|
|
176
204
|
});
|
|
177
205
|
});
|
|
178
206
|
}
|
|
179
|
-
constructor({ staticJsDir = 'static/js', HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash = false, scriptLoading = 'defer', nonce }){
|
|
207
|
+
constructor({ staticJsDir = 'static/js', HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash = false, scriptLoading = 'defer', nonce, useRsc = false }){
|
|
180
208
|
this.name = 'RouterPlugin';
|
|
181
209
|
this.HtmlBundlerPlugin = HtmlBundlerPlugin;
|
|
182
210
|
this.enableInlineRouteManifests = enableInlineRouteManifests;
|
|
@@ -184,6 +212,7 @@ class RouterPlugin {
|
|
|
184
212
|
this.disableFilenameHash = disableFilenameHash;
|
|
185
213
|
this.scriptLoading = scriptLoading;
|
|
186
214
|
this.nonce = nonce;
|
|
215
|
+
this.useRsc = useRsc;
|
|
187
216
|
}
|
|
188
217
|
}
|
|
189
218
|
exports.RouterPlugin = __webpack_exports__.RouterPlugin;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
+
})();
|
|
18
|
+
(()=>{
|
|
19
|
+
__webpack_require__.r = (exports1)=>{
|
|
20
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
+
value: 'Module'
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
+
value: true
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
})();
|
|
28
|
+
var __webpack_exports__ = {};
|
|
29
|
+
__webpack_require__.r(__webpack_exports__);
|
|
30
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
31
|
+
aggregateEagerRouteComponentFiles: ()=>aggregateEagerRouteComponentFiles,
|
|
32
|
+
buildSSRLazyCompilationTest: ()=>buildSSRLazyCompilationTest,
|
|
33
|
+
collectRouteComponentFiles: ()=>utils_namespaceObject.collectRouteComponentFiles,
|
|
34
|
+
normalizeModulePath: ()=>utils_namespaceObject.normalizeModulePath,
|
|
35
|
+
planSSRLazyCompilation: ()=>planSSRLazyCompilation
|
|
36
|
+
});
|
|
37
|
+
const utils_namespaceObject = require("@modern-js/utils");
|
|
38
|
+
function aggregateEagerRouteComponentFiles(byEntry) {
|
|
39
|
+
const files = new Set();
|
|
40
|
+
const unresolvedByEntry = new Map();
|
|
41
|
+
if (byEntry) for (const [entryName, collection] of byEntry){
|
|
42
|
+
for (const file of collection.resolvedFiles)files.add(file);
|
|
43
|
+
if (collection.unresolvedSpecifiers.length > 0) unresolvedByEntry.set(entryName, collection.unresolvedSpecifiers);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
files,
|
|
47
|
+
unresolvedByEntry
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
|
|
51
|
+
const userTestFn = 'function' == typeof userTest ? userTest : userTest instanceof RegExp ? (m)=>userTest.test(m.resource || '') : ()=>true;
|
|
52
|
+
return (m)=>{
|
|
53
|
+
const resource = m.resource;
|
|
54
|
+
if (!resource) return userTestFn(m);
|
|
55
|
+
if (eagerRouteFiles.has((0, utils_namespaceObject.normalizeModulePath)(resource))) return false;
|
|
56
|
+
return userTestFn(m);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function planSSRLazyCompilation(current, info) {
|
|
60
|
+
if (!current) return {
|
|
61
|
+
apply: false
|
|
62
|
+
};
|
|
63
|
+
if (info.unresolvedByEntry.size > 0) return {
|
|
64
|
+
apply: false,
|
|
65
|
+
unresolvedByEntry: info.unresolvedByEntry
|
|
66
|
+
};
|
|
67
|
+
if (0 === info.files.size) return {
|
|
68
|
+
apply: false
|
|
69
|
+
};
|
|
70
|
+
const base = 'object' == typeof current ? current : {};
|
|
71
|
+
const userTest = current.test;
|
|
72
|
+
return {
|
|
73
|
+
apply: true,
|
|
74
|
+
lazyCompilation: {
|
|
75
|
+
...base,
|
|
76
|
+
test: buildSSRLazyCompilationTest(info.files, userTest)
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
exports.aggregateEagerRouteComponentFiles = __webpack_exports__.aggregateEagerRouteComponentFiles;
|
|
81
|
+
exports.buildSSRLazyCompilationTest = __webpack_exports__.buildSSRLazyCompilationTest;
|
|
82
|
+
exports.collectRouteComponentFiles = __webpack_exports__.collectRouteComponentFiles;
|
|
83
|
+
exports.normalizeModulePath = __webpack_exports__.normalizeModulePath;
|
|
84
|
+
exports.planSSRLazyCompilation = __webpack_exports__.planSSRLazyCompilation;
|
|
85
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
86
|
+
"aggregateEagerRouteComponentFiles",
|
|
87
|
+
"buildSSRLazyCompilationTest",
|
|
88
|
+
"collectRouteComponentFiles",
|
|
89
|
+
"normalizeModulePath",
|
|
90
|
+
"planSSRLazyCompilation"
|
|
91
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
92
|
+
Object.defineProperty(exports, '__esModule', {
|
|
93
|
+
value: true
|
|
94
|
+
});
|
|
@@ -28,7 +28,8 @@ var __webpack_require__ = {};
|
|
|
28
28
|
var __webpack_exports__ = {};
|
|
29
29
|
__webpack_require__.r(__webpack_exports__);
|
|
30
30
|
__webpack_require__.d(__webpack_exports__, {
|
|
31
|
-
createDefaultConfig: ()=>createDefaultConfig
|
|
31
|
+
createDefaultConfig: ()=>createDefaultConfig,
|
|
32
|
+
isLazyCompilationSafeByDefault: ()=>isLazyCompilationSafeByDefault
|
|
32
33
|
});
|
|
33
34
|
const utils_namespaceObject = require("@modern-js/utils");
|
|
34
35
|
const env_js_namespaceObject = require("../utils/env.js");
|
|
@@ -120,9 +121,24 @@ function createDefaultConfig(appContext) {
|
|
|
120
121
|
builderPlugins: []
|
|
121
122
|
};
|
|
122
123
|
}
|
|
124
|
+
const isStreamSSRConfig = (ssr)=>{
|
|
125
|
+
if (!ssr) return false;
|
|
126
|
+
if ('boolean' == typeof ssr) return ssr;
|
|
127
|
+
return 'string' !== ssr.mode;
|
|
128
|
+
};
|
|
129
|
+
function isLazyCompilationSafeByDefault(userConfig) {
|
|
130
|
+
const { server, output } = userConfig;
|
|
131
|
+
if (output?.ssg || output?.ssgByEntries && Object.keys(output.ssgByEntries).length > 0) return false;
|
|
132
|
+
if (server?.rsc) return false;
|
|
133
|
+
if (server?.ssr && !isStreamSSRConfig(server.ssr)) return false;
|
|
134
|
+
if (server?.ssrByEntries && 'object' == typeof server.ssrByEntries && Object.values(server.ssrByEntries).some((ssr)=>Boolean(ssr) && !isStreamSSRConfig(ssr))) return false;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
123
137
|
exports.createDefaultConfig = __webpack_exports__.createDefaultConfig;
|
|
138
|
+
exports.isLazyCompilationSafeByDefault = __webpack_exports__.isLazyCompilationSafeByDefault;
|
|
124
139
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
125
|
-
"createDefaultConfig"
|
|
140
|
+
"createDefaultConfig",
|
|
141
|
+
"isLazyCompilationSafeByDefault"
|
|
126
142
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
127
143
|
Object.defineProperty(exports, '__esModule', {
|
|
128
144
|
value: true
|
|
@@ -154,10 +154,12 @@ const analyze = ()=>({
|
|
|
154
154
|
entrypoints
|
|
155
155
|
});
|
|
156
156
|
const normalizedConfig = api.getNormalizedConfig();
|
|
157
|
+
const { eagerRouteComponentFilesByEntry } = api.getAppContext();
|
|
157
158
|
const createBuilderForModern = await (0, index_js_namespaceObject.createBuilderGenerator)();
|
|
158
159
|
const builder = await createBuilderForModern({
|
|
159
160
|
normalizedConfig: normalizedConfig,
|
|
160
|
-
appContext: appContext
|
|
161
|
+
appContext: appContext,
|
|
162
|
+
eagerRouteComponentFilesByEntry
|
|
161
163
|
});
|
|
162
164
|
builder.onBeforeBuild(async ({ bundlerConfigs, isFirstCompile, environments, isWatch })=>{
|
|
163
165
|
if (!isFirstCompile) return;
|
|
@@ -43,7 +43,16 @@ const initialize = ()=>({
|
|
|
43
43
|
setup (api) {
|
|
44
44
|
api.config(()=>{
|
|
45
45
|
const appContext = api.getAppContext();
|
|
46
|
-
|
|
46
|
+
const userConfig = api.getConfig();
|
|
47
|
+
const defaultConfig = (0, index_js_namespaceObject.createDefaultConfig)(appContext);
|
|
48
|
+
if (userConfig.dev?.lazyCompilation === void 0 && (0, index_js_namespaceObject.isLazyCompilationSafeByDefault)(userConfig)) defaultConfig.dev = {
|
|
49
|
+
...defaultConfig.dev,
|
|
50
|
+
lazyCompilation: {
|
|
51
|
+
imports: true,
|
|
52
|
+
entries: false
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return defaultConfig;
|
|
47
56
|
});
|
|
48
57
|
api.modifyResolvedConfig(async (resolved)=>{
|
|
49
58
|
let appContext = api.getAppContext();
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { SERVICE_WORKER_ENVIRONMENT_NAME, isHtmlDisabled } from "@modern-js/builder";
|
|
2
|
-
import { fs, isUseRsc, isUseSSRBundle } from "@modern-js/utils";
|
|
2
|
+
import { fs, isUseRsc, isUseSSRBundle, logger } from "@modern-js/utils";
|
|
3
3
|
import { mergeRsbuildConfig } from "@rsbuild/core";
|
|
4
4
|
import { getServerCombinedModuleFile } from "../../../plugins/analyze/utils.mjs";
|
|
5
5
|
import { HtmlAsyncChunkPlugin, RouterPlugin } from "../bundlerPlugins/index.mjs";
|
|
6
|
+
import { aggregateEagerRouteComponentFiles, planSSRLazyCompilation } from "../lazyCompilation.mjs";
|
|
6
7
|
import * as __rspack_external_path from "path";
|
|
7
8
|
const builderPluginAdapterSSR = (options)=>({
|
|
8
9
|
name: 'builder-plugin-adapter-modern-ssr',
|
|
9
10
|
setup (api) {
|
|
10
|
-
const { normalizedConfig } = options;
|
|
11
|
-
api.modifyRsbuildConfig((config)=>
|
|
11
|
+
const { normalizedConfig, appContext, eagerRouteComponentFilesByEntry } = options;
|
|
12
|
+
api.modifyRsbuildConfig((config)=>{
|
|
13
|
+
const merged = mergeRsbuildConfig(config, {
|
|
12
14
|
html: {
|
|
13
15
|
inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
|
|
14
16
|
},
|
|
15
17
|
server: {
|
|
16
18
|
compress: isStreamingSSR(normalizedConfig) || isUseRsc(normalizedConfig) ? false : void 0
|
|
17
19
|
}
|
|
18
|
-
})
|
|
20
|
+
});
|
|
21
|
+
const lazyCompilation = getSSRLazyCompilation(merged.dev?.lazyCompilation, normalizedConfig, appContext, eagerRouteComponentFilesByEntry);
|
|
22
|
+
if (void 0 !== lazyCompilation) merged.dev = {
|
|
23
|
+
...merged.dev,
|
|
24
|
+
lazyCompilation
|
|
25
|
+
};
|
|
26
|
+
return merged;
|
|
27
|
+
});
|
|
19
28
|
api.modifyBundlerChain(async (chain, { target, isProd, HtmlPlugin: HtmlBundlerPlugin, isServer, environment })=>{
|
|
20
29
|
const builderConfig = environment.config;
|
|
21
30
|
const { normalizedConfig } = options;
|
|
@@ -51,6 +60,22 @@ const isStreamingSSR = (userConfig)=>{
|
|
|
51
60
|
}
|
|
52
61
|
return false;
|
|
53
62
|
};
|
|
63
|
+
function getSSRLazyCompilation(current, normalizedConfig, appContext, eagerRouteComponentFilesByEntry) {
|
|
64
|
+
if (!current || isUseRsc(normalizedConfig) || !isStreamingSSR(normalizedConfig)) return;
|
|
65
|
+
const plan = planSSRLazyCompilation(current, aggregateEagerRouteComponentFiles(eagerRouteComponentFilesByEntry));
|
|
66
|
+
if (!plan.apply) {
|
|
67
|
+
if (plan.unresolvedByEntry) warnUnresolvedRouteComponents(appContext.appDirectory, plan.unresolvedByEntry);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
return plan.lazyCompilation;
|
|
71
|
+
}
|
|
72
|
+
const warnedLazyApps = new Set();
|
|
73
|
+
function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
|
|
74
|
+
if (warnedLazyApps.has(appDirectory)) return;
|
|
75
|
+
warnedLazyApps.add(appDirectory);
|
|
76
|
+
const detail = Array.from(unresolvedByEntry).map(([entry, comps])=>`${entry}: ${comps.join(', ')}`).join('; ');
|
|
77
|
+
logger.warn(`[lazyCompilation] Skipped stream SSR route-eager optimization because some route components could not be resolved to a file (${detail}). Lazy compilation may break first-screen CSS/JS for these routes.`);
|
|
78
|
+
}
|
|
54
79
|
function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
|
|
55
80
|
if (isStreamingSSR(modernConfig) || isUseRsc(modernConfig)) chain.plugin('html-async-chunk').use(HtmlAsyncChunkPlugin, [
|
|
56
81
|
HtmlBundlerPlugin
|
|
@@ -70,7 +95,8 @@ function applyRouterPlugin(chain, pluginName, options, HtmlBundlerPlugin) {
|
|
|
70
95
|
staticJsDir: normalizedConfig.output?.distPath?.js,
|
|
71
96
|
disableFilenameHash: normalizedConfig.output?.filenameHash === false,
|
|
72
97
|
scriptLoading: normalizedConfig.html?.scriptLoading,
|
|
73
|
-
nonce: normalizedConfig.security?.nonce
|
|
98
|
+
nonce: normalizedConfig.security?.nonce,
|
|
99
|
+
useRsc: isUseRsc(normalizedConfig)
|
|
74
100
|
}
|
|
75
101
|
]);
|
|
76
102
|
}
|
|
@@ -64,6 +64,25 @@ class RouterPlugin {
|
|
|
64
64
|
routeAssets: {}
|
|
65
65
|
});
|
|
66
66
|
const prevManifest = JSON.parse(prevManifestStr);
|
|
67
|
+
const namedChunkGroupInstances = new Map();
|
|
68
|
+
for (const cg of compilation.chunkGroups || [])if (cg.name) namedChunkGroupInstances.set(cg.name, cg);
|
|
69
|
+
const collectDescendantCssAssets = (name)=>{
|
|
70
|
+
const root = namedChunkGroupInstances.get(name);
|
|
71
|
+
if (!root) return [];
|
|
72
|
+
const cssFiles = new Set();
|
|
73
|
+
const visited = new Set();
|
|
74
|
+
const stack = [
|
|
75
|
+
...root.childrenIterable
|
|
76
|
+
];
|
|
77
|
+
while(stack.length){
|
|
78
|
+
const child = stack.pop();
|
|
79
|
+
if (visited.has(child)) continue;
|
|
80
|
+
visited.add(child);
|
|
81
|
+
for (const chunk of child.chunks)for (const file of chunk.files)if (/\.css$/.test(file)) cssFiles.add(publicPath ? normalizePath(publicPath) + file : file);
|
|
82
|
+
for (const c of child.childrenIterable)stack.push(c);
|
|
83
|
+
}
|
|
84
|
+
return Array.from(cssFiles);
|
|
85
|
+
};
|
|
67
86
|
const asyncEntryNames = [];
|
|
68
87
|
for (const [name, chunkGroup] of Object.entries(namedChunkGroups)){
|
|
69
88
|
if (name.startsWith('async-')) asyncEntryNames.push(name);
|
|
@@ -71,7 +90,12 @@ class RouterPlugin {
|
|
|
71
90
|
const filename = asset.name;
|
|
72
91
|
return publicPath ? normalizePath(publicPath) + filename : filename;
|
|
73
92
|
});
|
|
74
|
-
const
|
|
93
|
+
const directCssAssets = assets.filter((asset)=>/\.css$/.test(asset));
|
|
94
|
+
const descendantCssAssets = collectDescendantCssAssets(name).filter((asset)=>!directCssAssets.includes(asset));
|
|
95
|
+
const referenceCssAssets = [
|
|
96
|
+
...directCssAssets,
|
|
97
|
+
...descendantCssAssets
|
|
98
|
+
];
|
|
75
99
|
routeAssets[name] = {
|
|
76
100
|
chunkIds: chunkGroup.chunks,
|
|
77
101
|
assets,
|
|
@@ -113,10 +137,14 @@ class RouterPlugin {
|
|
|
113
137
|
const manifest = {
|
|
114
138
|
routeAssets: relatedAssets
|
|
115
139
|
};
|
|
140
|
+
const { useRsc } = this;
|
|
116
141
|
const injectedContent = `
|
|
117
142
|
;(function(){
|
|
118
143
|
window.${ROUTE_MANIFEST} = ${JSON.stringify(manifest, (k, v)=>{
|
|
119
|
-
if (('assets' === k || 'referenceCssAssets' === k) && Array.isArray(v))
|
|
144
|
+
if (('assets' === k || 'referenceCssAssets' === k) && Array.isArray(v)) {
|
|
145
|
+
if (!useRsc) return;
|
|
146
|
+
return v.map((item)=>item.replace(publicPath, ''));
|
|
147
|
+
}
|
|
120
148
|
return v;
|
|
121
149
|
})};
|
|
122
150
|
})();
|
|
@@ -144,7 +172,7 @@ class RouterPlugin {
|
|
|
144
172
|
});
|
|
145
173
|
});
|
|
146
174
|
}
|
|
147
|
-
constructor({ staticJsDir = 'static/js', HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash = false, scriptLoading = 'defer', nonce }){
|
|
175
|
+
constructor({ staticJsDir = 'static/js', HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash = false, scriptLoading = 'defer', nonce, useRsc = false }){
|
|
148
176
|
this.name = 'RouterPlugin';
|
|
149
177
|
this.HtmlBundlerPlugin = HtmlBundlerPlugin;
|
|
150
178
|
this.enableInlineRouteManifests = enableInlineRouteManifests;
|
|
@@ -152,6 +180,7 @@ class RouterPlugin {
|
|
|
152
180
|
this.disableFilenameHash = disableFilenameHash;
|
|
153
181
|
this.scriptLoading = scriptLoading;
|
|
154
182
|
this.nonce = nonce;
|
|
183
|
+
this.useRsc = useRsc;
|
|
155
184
|
}
|
|
156
185
|
}
|
|
157
186
|
export { RouterPlugin };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { collectRouteComponentFiles, normalizeModulePath } from "@modern-js/utils";
|
|
2
|
+
function aggregateEagerRouteComponentFiles(byEntry) {
|
|
3
|
+
const files = new Set();
|
|
4
|
+
const unresolvedByEntry = new Map();
|
|
5
|
+
if (byEntry) for (const [entryName, collection] of byEntry){
|
|
6
|
+
for (const file of collection.resolvedFiles)files.add(file);
|
|
7
|
+
if (collection.unresolvedSpecifiers.length > 0) unresolvedByEntry.set(entryName, collection.unresolvedSpecifiers);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
files,
|
|
11
|
+
unresolvedByEntry
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
|
|
15
|
+
const userTestFn = 'function' == typeof userTest ? userTest : userTest instanceof RegExp ? (m)=>userTest.test(m.resource || '') : ()=>true;
|
|
16
|
+
return (m)=>{
|
|
17
|
+
const resource = m.resource;
|
|
18
|
+
if (!resource) return userTestFn(m);
|
|
19
|
+
if (eagerRouteFiles.has(normalizeModulePath(resource))) return false;
|
|
20
|
+
return userTestFn(m);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function planSSRLazyCompilation(current, info) {
|
|
24
|
+
if (!current) return {
|
|
25
|
+
apply: false
|
|
26
|
+
};
|
|
27
|
+
if (info.unresolvedByEntry.size > 0) return {
|
|
28
|
+
apply: false,
|
|
29
|
+
unresolvedByEntry: info.unresolvedByEntry
|
|
30
|
+
};
|
|
31
|
+
if (0 === info.files.size) return {
|
|
32
|
+
apply: false
|
|
33
|
+
};
|
|
34
|
+
const base = 'object' == typeof current ? current : {};
|
|
35
|
+
const userTest = current.test;
|
|
36
|
+
return {
|
|
37
|
+
apply: true,
|
|
38
|
+
lazyCompilation: {
|
|
39
|
+
...base,
|
|
40
|
+
test: buildSSRLazyCompilationTest(info.files, userTest)
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export { aggregateEagerRouteComponentFiles, buildSSRLazyCompilationTest, collectRouteComponentFiles, normalizeModulePath, planSSRLazyCompilation };
|
|
@@ -88,4 +88,17 @@ function createDefaultConfig(appContext) {
|
|
|
88
88
|
builderPlugins: []
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
const isStreamSSRConfig = (ssr)=>{
|
|
92
|
+
if (!ssr) return false;
|
|
93
|
+
if ('boolean' == typeof ssr) return ssr;
|
|
94
|
+
return 'string' !== ssr.mode;
|
|
95
|
+
};
|
|
96
|
+
function isLazyCompilationSafeByDefault(userConfig) {
|
|
97
|
+
const { server, output } = userConfig;
|
|
98
|
+
if (output?.ssg || output?.ssgByEntries && Object.keys(output.ssgByEntries).length > 0) return false;
|
|
99
|
+
if (server?.rsc) return false;
|
|
100
|
+
if (server?.ssr && !isStreamSSRConfig(server.ssr)) return false;
|
|
101
|
+
if (server?.ssrByEntries && 'object' == typeof server.ssrByEntries && Object.values(server.ssrByEntries).some((ssr)=>Boolean(ssr) && !isStreamSSRConfig(ssr))) return false;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
export { createDefaultConfig, isLazyCompilationSafeByDefault };
|
|
@@ -122,10 +122,12 @@ const analyze = ()=>({
|
|
|
122
122
|
entrypoints
|
|
123
123
|
});
|
|
124
124
|
const normalizedConfig = api.getNormalizedConfig();
|
|
125
|
+
const { eagerRouteComponentFilesByEntry } = api.getAppContext();
|
|
125
126
|
const createBuilderForModern = await createBuilderGenerator();
|
|
126
127
|
const builder = await createBuilderForModern({
|
|
127
128
|
normalizedConfig: normalizedConfig,
|
|
128
|
-
appContext: appContext
|
|
129
|
+
appContext: appContext,
|
|
130
|
+
eagerRouteComponentFilesByEntry
|
|
129
131
|
});
|
|
130
132
|
builder.onBeforeBuild(async ({ bundlerConfigs, isFirstCompile, environments, isWatch })=>{
|
|
131
133
|
if (!isFirstCompile) return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ensureAbsolutePath, getPort, isDev, isDevCommand } from "@modern-js/utils";
|
|
2
|
-
import { createDefaultConfig } from "../../config/index.mjs";
|
|
2
|
+
import { createDefaultConfig, isLazyCompilationSafeByDefault } from "../../config/index.mjs";
|
|
3
3
|
const initialize = ()=>({
|
|
4
4
|
name: '@modern-js/plugin-initialize',
|
|
5
5
|
post: [
|
|
@@ -11,7 +11,16 @@ const initialize = ()=>({
|
|
|
11
11
|
setup (api) {
|
|
12
12
|
api.config(()=>{
|
|
13
13
|
const appContext = api.getAppContext();
|
|
14
|
-
|
|
14
|
+
const userConfig = api.getConfig();
|
|
15
|
+
const defaultConfig = createDefaultConfig(appContext);
|
|
16
|
+
if (userConfig.dev?.lazyCompilation === void 0 && isLazyCompilationSafeByDefault(userConfig)) defaultConfig.dev = {
|
|
17
|
+
...defaultConfig.dev,
|
|
18
|
+
lazyCompilation: {
|
|
19
|
+
imports: true,
|
|
20
|
+
entries: false
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return defaultConfig;
|
|
15
24
|
});
|
|
16
25
|
api.modifyResolvedConfig(async (resolved)=>{
|
|
17
26
|
let appContext = api.getAppContext();
|
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import __rslib_shim_module__ from "node:module";
|
|
2
2
|
const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
|
|
3
3
|
import { SERVICE_WORKER_ENVIRONMENT_NAME, isHtmlDisabled } from "@modern-js/builder";
|
|
4
|
-
import { fs, isUseRsc, isUseSSRBundle } from "@modern-js/utils";
|
|
4
|
+
import { fs, isUseRsc, isUseSSRBundle, logger } from "@modern-js/utils";
|
|
5
5
|
import { mergeRsbuildConfig } from "@rsbuild/core";
|
|
6
6
|
import { getServerCombinedModuleFile } from "../../../plugins/analyze/utils.mjs";
|
|
7
7
|
import { HtmlAsyncChunkPlugin, RouterPlugin } from "../bundlerPlugins/index.mjs";
|
|
8
|
+
import { aggregateEagerRouteComponentFiles, planSSRLazyCompilation } from "../lazyCompilation.mjs";
|
|
8
9
|
import * as __rspack_external_path from "path";
|
|
9
10
|
const builderPluginAdapterSSR = (options)=>({
|
|
10
11
|
name: 'builder-plugin-adapter-modern-ssr',
|
|
11
12
|
setup (api) {
|
|
12
|
-
const { normalizedConfig } = options;
|
|
13
|
-
api.modifyRsbuildConfig((config)=>
|
|
13
|
+
const { normalizedConfig, appContext, eagerRouteComponentFilesByEntry } = options;
|
|
14
|
+
api.modifyRsbuildConfig((config)=>{
|
|
15
|
+
const merged = mergeRsbuildConfig(config, {
|
|
14
16
|
html: {
|
|
15
17
|
inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
|
|
16
18
|
},
|
|
17
19
|
server: {
|
|
18
20
|
compress: isStreamingSSR(normalizedConfig) || isUseRsc(normalizedConfig) ? false : void 0
|
|
19
21
|
}
|
|
20
|
-
})
|
|
22
|
+
});
|
|
23
|
+
const lazyCompilation = getSSRLazyCompilation(merged.dev?.lazyCompilation, normalizedConfig, appContext, eagerRouteComponentFilesByEntry);
|
|
24
|
+
if (void 0 !== lazyCompilation) merged.dev = {
|
|
25
|
+
...merged.dev,
|
|
26
|
+
lazyCompilation
|
|
27
|
+
};
|
|
28
|
+
return merged;
|
|
29
|
+
});
|
|
21
30
|
api.modifyBundlerChain(async (chain, { target, isProd, HtmlPlugin: HtmlBundlerPlugin, isServer, environment })=>{
|
|
22
31
|
const builderConfig = environment.config;
|
|
23
32
|
const { normalizedConfig } = options;
|
|
@@ -53,6 +62,22 @@ const isStreamingSSR = (userConfig)=>{
|
|
|
53
62
|
}
|
|
54
63
|
return false;
|
|
55
64
|
};
|
|
65
|
+
function getSSRLazyCompilation(current, normalizedConfig, appContext, eagerRouteComponentFilesByEntry) {
|
|
66
|
+
if (!current || isUseRsc(normalizedConfig) || !isStreamingSSR(normalizedConfig)) return;
|
|
67
|
+
const plan = planSSRLazyCompilation(current, aggregateEagerRouteComponentFiles(eagerRouteComponentFilesByEntry));
|
|
68
|
+
if (!plan.apply) {
|
|
69
|
+
if (plan.unresolvedByEntry) warnUnresolvedRouteComponents(appContext.appDirectory, plan.unresolvedByEntry);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
return plan.lazyCompilation;
|
|
73
|
+
}
|
|
74
|
+
const warnedLazyApps = new Set();
|
|
75
|
+
function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
|
|
76
|
+
if (warnedLazyApps.has(appDirectory)) return;
|
|
77
|
+
warnedLazyApps.add(appDirectory);
|
|
78
|
+
const detail = Array.from(unresolvedByEntry).map(([entry, comps])=>`${entry}: ${comps.join(', ')}`).join('; ');
|
|
79
|
+
logger.warn(`[lazyCompilation] Skipped stream SSR route-eager optimization because some route components could not be resolved to a file (${detail}). Lazy compilation may break first-screen CSS/JS for these routes.`);
|
|
80
|
+
}
|
|
56
81
|
function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
|
|
57
82
|
if (isStreamingSSR(modernConfig) || isUseRsc(modernConfig)) chain.plugin('html-async-chunk').use(HtmlAsyncChunkPlugin, [
|
|
58
83
|
HtmlBundlerPlugin
|
|
@@ -72,7 +97,8 @@ function applyRouterPlugin(chain, pluginName, options, HtmlBundlerPlugin) {
|
|
|
72
97
|
staticJsDir: normalizedConfig.output?.distPath?.js,
|
|
73
98
|
disableFilenameHash: normalizedConfig.output?.filenameHash === false,
|
|
74
99
|
scriptLoading: normalizedConfig.html?.scriptLoading,
|
|
75
|
-
nonce: normalizedConfig.security?.nonce
|
|
100
|
+
nonce: normalizedConfig.security?.nonce,
|
|
101
|
+
useRsc: isUseRsc(normalizedConfig)
|
|
76
102
|
}
|
|
77
103
|
]);
|
|
78
104
|
}
|
|
@@ -65,6 +65,25 @@ class RouterPlugin {
|
|
|
65
65
|
routeAssets: {}
|
|
66
66
|
});
|
|
67
67
|
const prevManifest = JSON.parse(prevManifestStr);
|
|
68
|
+
const namedChunkGroupInstances = new Map();
|
|
69
|
+
for (const cg of compilation.chunkGroups || [])if (cg.name) namedChunkGroupInstances.set(cg.name, cg);
|
|
70
|
+
const collectDescendantCssAssets = (name)=>{
|
|
71
|
+
const root = namedChunkGroupInstances.get(name);
|
|
72
|
+
if (!root) return [];
|
|
73
|
+
const cssFiles = new Set();
|
|
74
|
+
const visited = new Set();
|
|
75
|
+
const stack = [
|
|
76
|
+
...root.childrenIterable
|
|
77
|
+
];
|
|
78
|
+
while(stack.length){
|
|
79
|
+
const child = stack.pop();
|
|
80
|
+
if (visited.has(child)) continue;
|
|
81
|
+
visited.add(child);
|
|
82
|
+
for (const chunk of child.chunks)for (const file of chunk.files)if (/\.css$/.test(file)) cssFiles.add(publicPath ? normalizePath(publicPath) + file : file);
|
|
83
|
+
for (const c of child.childrenIterable)stack.push(c);
|
|
84
|
+
}
|
|
85
|
+
return Array.from(cssFiles);
|
|
86
|
+
};
|
|
68
87
|
const asyncEntryNames = [];
|
|
69
88
|
for (const [name, chunkGroup] of Object.entries(namedChunkGroups)){
|
|
70
89
|
if (name.startsWith('async-')) asyncEntryNames.push(name);
|
|
@@ -72,7 +91,12 @@ class RouterPlugin {
|
|
|
72
91
|
const filename = asset.name;
|
|
73
92
|
return publicPath ? normalizePath(publicPath) + filename : filename;
|
|
74
93
|
});
|
|
75
|
-
const
|
|
94
|
+
const directCssAssets = assets.filter((asset)=>/\.css$/.test(asset));
|
|
95
|
+
const descendantCssAssets = collectDescendantCssAssets(name).filter((asset)=>!directCssAssets.includes(asset));
|
|
96
|
+
const referenceCssAssets = [
|
|
97
|
+
...directCssAssets,
|
|
98
|
+
...descendantCssAssets
|
|
99
|
+
];
|
|
76
100
|
routeAssets[name] = {
|
|
77
101
|
chunkIds: chunkGroup.chunks,
|
|
78
102
|
assets,
|
|
@@ -114,10 +138,14 @@ class RouterPlugin {
|
|
|
114
138
|
const manifest = {
|
|
115
139
|
routeAssets: relatedAssets
|
|
116
140
|
};
|
|
141
|
+
const { useRsc } = this;
|
|
117
142
|
const injectedContent = `
|
|
118
143
|
;(function(){
|
|
119
144
|
window.${ROUTE_MANIFEST} = ${JSON.stringify(manifest, (k, v)=>{
|
|
120
|
-
if (('assets' === k || 'referenceCssAssets' === k) && Array.isArray(v))
|
|
145
|
+
if (('assets' === k || 'referenceCssAssets' === k) && Array.isArray(v)) {
|
|
146
|
+
if (!useRsc) return;
|
|
147
|
+
return v.map((item)=>item.replace(publicPath, ''));
|
|
148
|
+
}
|
|
121
149
|
return v;
|
|
122
150
|
})};
|
|
123
151
|
})();
|
|
@@ -145,7 +173,7 @@ class RouterPlugin {
|
|
|
145
173
|
});
|
|
146
174
|
});
|
|
147
175
|
}
|
|
148
|
-
constructor({ staticJsDir = 'static/js', HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash = false, scriptLoading = 'defer', nonce }){
|
|
176
|
+
constructor({ staticJsDir = 'static/js', HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash = false, scriptLoading = 'defer', nonce, useRsc = false }){
|
|
149
177
|
this.name = 'RouterPlugin';
|
|
150
178
|
this.HtmlBundlerPlugin = HtmlBundlerPlugin;
|
|
151
179
|
this.enableInlineRouteManifests = enableInlineRouteManifests;
|
|
@@ -153,6 +181,7 @@ class RouterPlugin {
|
|
|
153
181
|
this.disableFilenameHash = disableFilenameHash;
|
|
154
182
|
this.scriptLoading = scriptLoading;
|
|
155
183
|
this.nonce = nonce;
|
|
184
|
+
this.useRsc = useRsc;
|
|
156
185
|
}
|
|
157
186
|
}
|
|
158
187
|
export { RouterPlugin };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { collectRouteComponentFiles, normalizeModulePath } from "@modern-js/utils";
|
|
3
|
+
function aggregateEagerRouteComponentFiles(byEntry) {
|
|
4
|
+
const files = new Set();
|
|
5
|
+
const unresolvedByEntry = new Map();
|
|
6
|
+
if (byEntry) for (const [entryName, collection] of byEntry){
|
|
7
|
+
for (const file of collection.resolvedFiles)files.add(file);
|
|
8
|
+
if (collection.unresolvedSpecifiers.length > 0) unresolvedByEntry.set(entryName, collection.unresolvedSpecifiers);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
files,
|
|
12
|
+
unresolvedByEntry
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
|
|
16
|
+
const userTestFn = 'function' == typeof userTest ? userTest : userTest instanceof RegExp ? (m)=>userTest.test(m.resource || '') : ()=>true;
|
|
17
|
+
return (m)=>{
|
|
18
|
+
const resource = m.resource;
|
|
19
|
+
if (!resource) return userTestFn(m);
|
|
20
|
+
if (eagerRouteFiles.has(normalizeModulePath(resource))) return false;
|
|
21
|
+
return userTestFn(m);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function planSSRLazyCompilation(current, info) {
|
|
25
|
+
if (!current) return {
|
|
26
|
+
apply: false
|
|
27
|
+
};
|
|
28
|
+
if (info.unresolvedByEntry.size > 0) return {
|
|
29
|
+
apply: false,
|
|
30
|
+
unresolvedByEntry: info.unresolvedByEntry
|
|
31
|
+
};
|
|
32
|
+
if (0 === info.files.size) return {
|
|
33
|
+
apply: false
|
|
34
|
+
};
|
|
35
|
+
const base = 'object' == typeof current ? current : {};
|
|
36
|
+
const userTest = current.test;
|
|
37
|
+
return {
|
|
38
|
+
apply: true,
|
|
39
|
+
lazyCompilation: {
|
|
40
|
+
...base,
|
|
41
|
+
test: buildSSRLazyCompilationTest(info.files, userTest)
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export { aggregateEagerRouteComponentFiles, buildSSRLazyCompilationTest, collectRouteComponentFiles, normalizeModulePath, planSSRLazyCompilation };
|
|
@@ -89,4 +89,17 @@ function createDefaultConfig(appContext) {
|
|
|
89
89
|
builderPlugins: []
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
const isStreamSSRConfig = (ssr)=>{
|
|
93
|
+
if (!ssr) return false;
|
|
94
|
+
if ('boolean' == typeof ssr) return ssr;
|
|
95
|
+
return 'string' !== ssr.mode;
|
|
96
|
+
};
|
|
97
|
+
function isLazyCompilationSafeByDefault(userConfig) {
|
|
98
|
+
const { server, output } = userConfig;
|
|
99
|
+
if (output?.ssg || output?.ssgByEntries && Object.keys(output.ssgByEntries).length > 0) return false;
|
|
100
|
+
if (server?.rsc) return false;
|
|
101
|
+
if (server?.ssr && !isStreamSSRConfig(server.ssr)) return false;
|
|
102
|
+
if (server?.ssrByEntries && 'object' == typeof server.ssrByEntries && Object.values(server.ssrByEntries).some((ssr)=>Boolean(ssr) && !isStreamSSRConfig(ssr))) return false;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
export { createDefaultConfig, isLazyCompilationSafeByDefault };
|
|
@@ -123,10 +123,12 @@ const analyze = ()=>({
|
|
|
123
123
|
entrypoints
|
|
124
124
|
});
|
|
125
125
|
const normalizedConfig = api.getNormalizedConfig();
|
|
126
|
+
const { eagerRouteComponentFilesByEntry } = api.getAppContext();
|
|
126
127
|
const createBuilderForModern = await createBuilderGenerator();
|
|
127
128
|
const builder = await createBuilderForModern({
|
|
128
129
|
normalizedConfig: normalizedConfig,
|
|
129
|
-
appContext: appContext
|
|
130
|
+
appContext: appContext,
|
|
131
|
+
eagerRouteComponentFilesByEntry
|
|
130
132
|
});
|
|
131
133
|
builder.onBeforeBuild(async ({ bundlerConfigs, isFirstCompile, environments, isWatch })=>{
|
|
132
134
|
if (!isFirstCompile) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { ensureAbsolutePath, getPort, isDev, isDevCommand } from "@modern-js/utils";
|
|
3
|
-
import { createDefaultConfig } from "../../config/index.mjs";
|
|
3
|
+
import { createDefaultConfig, isLazyCompilationSafeByDefault } from "../../config/index.mjs";
|
|
4
4
|
const initialize = ()=>({
|
|
5
5
|
name: '@modern-js/plugin-initialize',
|
|
6
6
|
post: [
|
|
@@ -12,7 +12,16 @@ const initialize = ()=>({
|
|
|
12
12
|
setup (api) {
|
|
13
13
|
api.config(()=>{
|
|
14
14
|
const appContext = api.getAppContext();
|
|
15
|
-
|
|
15
|
+
const userConfig = api.getConfig();
|
|
16
|
+
const defaultConfig = createDefaultConfig(appContext);
|
|
17
|
+
if (userConfig.dev?.lazyCompilation === void 0 && isLazyCompilationSafeByDefault(userConfig)) defaultConfig.dev = {
|
|
18
|
+
...defaultConfig.dev,
|
|
19
|
+
lazyCompilation: {
|
|
20
|
+
imports: true,
|
|
21
|
+
entries: false
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return defaultConfig;
|
|
16
25
|
});
|
|
17
26
|
api.modifyResolvedConfig(async (resolved)=>{
|
|
18
27
|
let appContext = api.getAppContext();
|
|
@@ -14,6 +14,7 @@ type Options = {
|
|
|
14
14
|
disableFilenameHash?: boolean;
|
|
15
15
|
scriptLoading?: ScriptLoading;
|
|
16
16
|
nonce?: string;
|
|
17
|
+
useRsc?: boolean;
|
|
17
18
|
};
|
|
18
19
|
export declare class RouterPlugin {
|
|
19
20
|
readonly name: string;
|
|
@@ -23,7 +24,8 @@ export declare class RouterPlugin {
|
|
|
23
24
|
private disableFilenameHash?;
|
|
24
25
|
private scriptLoading?;
|
|
25
26
|
private nonce?;
|
|
26
|
-
|
|
27
|
+
private useRsc;
|
|
28
|
+
constructor({ staticJsDir, HtmlBundlerPlugin, enableInlineRouteManifests, disableFilenameHash, scriptLoading, nonce, useRsc, }: Options);
|
|
27
29
|
private isTargetNodeOrWebWorker;
|
|
28
30
|
private getEntryChunks;
|
|
29
31
|
private getEntryChunkFiles;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type EagerRouteComponentFilesByEntry } from '@modern-js/utils';
|
|
2
|
+
export { type EagerRouteComponentFilesByEntry, type RouteComponentFileCollection, collectRouteComponentFiles, normalizeModulePath, } from '@modern-js/utils';
|
|
3
|
+
type ModuleLike = {
|
|
4
|
+
resource?: string;
|
|
5
|
+
};
|
|
6
|
+
type LazyCompilationTestFn = (m: ModuleLike) => boolean;
|
|
7
|
+
/** Matches Rspack's `LazyCompilationOptions['test']`. */
|
|
8
|
+
type LazyCompilationTest = RegExp | LazyCompilationTestFn | undefined;
|
|
9
|
+
export type EagerRouteComponentInfo = {
|
|
10
|
+
files: Set<string>;
|
|
11
|
+
/** Specifiers that could not be resolved, keyed by entry name. */
|
|
12
|
+
unresolvedByEntry: Map<string, string[]>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Aggregate the per-entry route component data (collected by the router plugin
|
|
16
|
+
* during route generation and threaded in as
|
|
17
|
+
* `BuilderOptions.eagerRouteComponentFilesByEntry`) into the flat shape
|
|
18
|
+
* {@link planSSRLazyCompilation} expects: one Set of all route files plus the
|
|
19
|
+
* unresolved specifiers keyed by entry.
|
|
20
|
+
*/
|
|
21
|
+
export declare function aggregateEagerRouteComponentFiles(byEntry: EagerRouteComponentFilesByEntry | undefined): EagerRouteComponentInfo;
|
|
22
|
+
/**
|
|
23
|
+
* Build a `lazyCompilation.test` that forces route component modules to compile
|
|
24
|
+
* eagerly (so SSR first-screen chunk/CSS injection has the assets it needs at
|
|
25
|
+
* render time), while delegating all other modules to the user's `test`
|
|
26
|
+
* (defaulting to lazy when the user did not provide one).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildSSRLazyCompilationTest(eagerRouteFiles: Set<string>, userTest?: LazyCompilationTest): LazyCompilationTestFn;
|
|
29
|
+
export type SSRLazyPlan = {
|
|
30
|
+
apply: false;
|
|
31
|
+
unresolvedByEntry?: Map<string, string[]>;
|
|
32
|
+
} | {
|
|
33
|
+
apply: true;
|
|
34
|
+
lazyCompilation: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Decide whether to apply the route-eager lazy compilation for an SSR project.
|
|
38
|
+
* Checks unresolved route components FIRST: if any exist we cannot guarantee
|
|
39
|
+
* they are eager, so we skip the optimization (and surface them so the caller
|
|
40
|
+
* can warn) rather than silently leaving a route lazy. `current` is the
|
|
41
|
+
* existing `dev.lazyCompilation` value (lazy must be enabled for this to apply).
|
|
42
|
+
*/
|
|
43
|
+
export declare function planSSRLazyCompilation(current: unknown, info: EagerRouteComponentInfo): SSRLazyPlan;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import type { EagerRouteComponentFilesByEntry } from '@modern-js/utils';
|
|
1
2
|
import type { AppNormalizedConfig } from '../../types';
|
|
2
3
|
import type { AppToolsContext } from '../../types/plugin';
|
|
3
4
|
export type BuilderOptions = {
|
|
4
5
|
normalizedConfig: AppNormalizedConfig;
|
|
5
6
|
appContext: AppToolsContext;
|
|
7
|
+
/**
|
|
8
|
+
* Route component files collected from the FINAL file-system routes (after
|
|
9
|
+
* all `modifyFileSystemRoutes` consumers ran), keyed by entry name. Populated
|
|
10
|
+
* by the router plugin during route generation and threaded in here (read
|
|
11
|
+
* FRESH from the app context AFTER `generateEntryCode` runs) so the SSR
|
|
12
|
+
* builder plugin can force route component chunks eager under lazy
|
|
13
|
+
* compilation. Explicit param instead of a direct `_internalContext` read.
|
|
14
|
+
*/
|
|
15
|
+
eagerRouteComponentFilesByEntry?: EagerRouteComponentFilesByEntry;
|
|
6
16
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import type { AppUserConfig } from '../types';
|
|
2
2
|
import type { AppToolsContext } from '../types/plugin';
|
|
3
3
|
export declare function createDefaultConfig(appContext: AppToolsContext): AppUserConfig;
|
|
4
|
+
/**
|
|
5
|
+
* Default-enable lazy compilation for pure CSR and stream SSR. Stream SSR keeps
|
|
6
|
+
* first-screen route assets correct via the route-eager lazyCompilation.test
|
|
7
|
+
* injected by the SSR builder plugin. String SSR, RSC and SSG stay disabled.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isLazyCompilationSafeByDefault(userConfig: Pick<AppUserConfig, 'server' | 'output'>): boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppContext, AsyncHook, InternalContext, PluginHook, PluginHookTap, TransformFunction } from '@modern-js/plugin';
|
|
2
2
|
import type { Hooks } from '@modern-js/plugin/cli';
|
|
3
3
|
import type { Entrypoint, HtmlPartials, HtmlTemplates, NestedRouteForCli, PageRoute, RouteLegacy, ServerPlugin, ServerRoute } from '@modern-js/types';
|
|
4
|
+
import type { EagerRouteComponentFilesByEntry } from '@modern-js/utils';
|
|
4
5
|
import type { AppTools } from '.';
|
|
5
6
|
import type { getHookRunners } from '../compat/hooks';
|
|
6
7
|
import type { AppToolsNormalizedConfig, AppToolsUserConfig } from './config';
|
|
@@ -109,6 +110,19 @@ export interface AppToolsExtendContext {
|
|
|
109
110
|
* @private
|
|
110
111
|
*/
|
|
111
112
|
bffRuntimeFramework?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Route component files collected from the FINAL file-system routes (after all
|
|
115
|
+
* `modifyFileSystemRoutes` consumers ran), keyed by entry name. Populated by
|
|
116
|
+
* the router plugin during route generation and consumed (currently by stream
|
|
117
|
+
* SSR lazy compilation) to force route component chunks eager.
|
|
118
|
+
*
|
|
119
|
+
* Published via the app context (`api.updateAppContext`) by the router plugin,
|
|
120
|
+
* then read fresh when assembling the builder options and threaded into
|
|
121
|
+
* `BuilderOptions.eagerRouteComponentFilesByEntry`; the SSR builder plugin
|
|
122
|
+
* reads it from those options (not from the context directly).
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
eagerRouteComponentFilesByEntry?: EagerRouteComponentFilesByEntry;
|
|
112
126
|
}
|
|
113
127
|
export type AppToolsContext = AppContext<AppTools> & AppToolsExtendContext;
|
|
114
128
|
export type AppToolsHooks = Hooks<AppToolsUserConfig, AppToolsNormalizedConfig, {}, {}> & AppToolsExtendHooks;
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"modern",
|
|
16
16
|
"modern.js"
|
|
17
17
|
],
|
|
18
|
-
"version": "3.
|
|
18
|
+
"version": "3.4.0",
|
|
19
19
|
"types": "./dist/types/index.d.ts",
|
|
20
20
|
"main": "./dist/cjs/index.js",
|
|
21
21
|
"exports": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"@babel/traverse": "^7.29.7",
|
|
85
85
|
"@babel/types": "^7.29.7",
|
|
86
86
|
"@rsbuild/core": "2.0.10",
|
|
87
|
-
"@swc/core": "1.15.
|
|
87
|
+
"@swc/core": "1.15.41",
|
|
88
88
|
"@swc/helpers": "^0.5.17",
|
|
89
89
|
"es-module-lexer": "^1.7.0",
|
|
90
90
|
"flatted": "^3.4.2",
|
|
@@ -93,19 +93,19 @@
|
|
|
93
93
|
"ndepe": "^0.1.13",
|
|
94
94
|
"pkg-types": "^1.3.1",
|
|
95
95
|
"std-env": "^3.10.0",
|
|
96
|
-
"@modern-js/builder": "3.
|
|
97
|
-
"@modern-js/
|
|
98
|
-
"@modern-js/plugin
|
|
99
|
-
"@modern-js/
|
|
100
|
-
"@modern-js/server": "3.
|
|
101
|
-
"@modern-js/
|
|
102
|
-
"@modern-js/server
|
|
103
|
-
"@modern-js/
|
|
104
|
-
"@modern-js/
|
|
105
|
-
"@modern-js/utils": "3.
|
|
96
|
+
"@modern-js/builder": "3.4.0",
|
|
97
|
+
"@modern-js/plugin-data-loader": "3.4.0",
|
|
98
|
+
"@modern-js/plugin": "3.4.0",
|
|
99
|
+
"@modern-js/i18n-utils": "3.4.0",
|
|
100
|
+
"@modern-js/prod-server": "3.4.0",
|
|
101
|
+
"@modern-js/server-core": "3.4.0",
|
|
102
|
+
"@modern-js/server": "3.4.0",
|
|
103
|
+
"@modern-js/server-utils": "3.4.0",
|
|
104
|
+
"@modern-js/types": "3.4.0",
|
|
105
|
+
"@modern-js/utils": "3.4.0"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@rslib/core": "0.
|
|
108
|
+
"@rslib/core": "0.22.1",
|
|
109
109
|
"@types/babel__traverse": "7.28.0",
|
|
110
110
|
"@types/node": "^20",
|
|
111
111
|
"ts-node": "^10.9.2",
|