@modern-js/app-tools 3.10.1 → 3.10.3

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.
Files changed (24) hide show
  1. package/dist/cjs/builder/shared/builderPlugins/adapterSSR.js +13 -6
  2. package/dist/cjs/builder/shared/bundlerPlugins/HtmlAsyncChunkPlugin.js +18 -1
  3. package/dist/cjs/plugins/analyze/getServerRoutes.js +4 -2
  4. package/dist/cjs/plugins/analyze/utils.js +15 -11
  5. package/dist/cjs/plugins/deploy/platforms/netlify.js +2 -2
  6. package/dist/cjs/plugins/deploy/platforms/node.js +2 -2
  7. package/dist/cjs/plugins/deploy/platforms/vercel.js +2 -2
  8. package/dist/esm/builder/shared/builderPlugins/adapterSSR.mjs +10 -6
  9. package/dist/esm/builder/shared/bundlerPlugins/HtmlAsyncChunkPlugin.mjs +18 -1
  10. package/dist/esm/plugins/analyze/getServerRoutes.mjs +4 -2
  11. package/dist/esm/plugins/analyze/utils.mjs +15 -11
  12. package/dist/esm/plugins/deploy/platforms/netlify.mjs +2 -2
  13. package/dist/esm/plugins/deploy/platforms/node.mjs +2 -2
  14. package/dist/esm/plugins/deploy/platforms/vercel.mjs +2 -2
  15. package/dist/esm-node/builder/shared/builderPlugins/adapterSSR.mjs +10 -6
  16. package/dist/esm-node/builder/shared/bundlerPlugins/HtmlAsyncChunkPlugin.mjs +18 -1
  17. package/dist/esm-node/plugins/analyze/getServerRoutes.mjs +4 -2
  18. package/dist/esm-node/plugins/analyze/utils.mjs +15 -11
  19. package/dist/esm-node/plugins/deploy/platforms/netlify.mjs +2 -2
  20. package/dist/esm-node/plugins/deploy/platforms/node.mjs +2 -2
  21. package/dist/esm-node/plugins/deploy/platforms/vercel.mjs +2 -2
  22. package/dist/types/builder/shared/builderPlugins/adapterSSR.d.ts +3 -0
  23. package/dist/types/builder/shared/bundlerPlugins/HtmlAsyncChunkPlugin.d.ts +4 -1
  24. package/package.json +13 -13
@@ -28,13 +28,15 @@ var __webpack_require__ = {};
28
28
  var __webpack_exports__ = {};
29
29
  __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
- builderPluginAdapterSSR: ()=>builderPluginAdapterSSR
31
+ builderPluginAdapterSSR: ()=>builderPluginAdapterSSR,
32
+ isStreamingSSR: ()=>isStreamingSSR
32
33
  });
33
34
  const external_path_namespaceObject = require("path");
34
35
  const builder_namespaceObject = require("@modern-js/builder");
35
36
  const utils_namespaceObject = require("@modern-js/utils");
36
37
  const core_namespaceObject = require("@rsbuild/core");
37
38
  const utils_js_namespaceObject = require("../../../plugins/analyze/utils.js");
39
+ const routes_js_namespaceObject = require("../../../utils/routes.js");
38
40
  const index_js_namespaceObject = require("../bundlerPlugins/index.js");
39
41
  const external_lazyCompilation_js_namespaceObject = require("../lazyCompilation.js");
40
42
  const builderPluginAdapterSSR = (options)=>({
@@ -44,7 +46,7 @@ const builderPluginAdapterSSR = (options)=>({
44
46
  api.modifyRsbuildConfig((config)=>{
45
47
  const merged = (0, core_namespaceObject.mergeRsbuildConfig)(config, {
46
48
  html: {
47
- inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
49
+ inject: ({ entryName, value })=>isStreamingSSR(normalizedConfig, entryName, appContext) ? 'head' : value
48
50
  },
49
51
  server: {
50
52
  compress: isStreamingSSR(normalizedConfig) || (0, utils_namespaceObject.isUseRsc)(normalizedConfig) ? false : void 0
@@ -74,18 +76,20 @@ const builderPluginAdapterSSR = (options)=>({
74
76
  if (!(0, builder_namespaceObject.isHtmlDisabled)(builderConfig, target)) applyAsyncChunkHtmlPlugin({
75
77
  chain,
76
78
  modernConfig: options.normalizedConfig,
79
+ appContext,
77
80
  HtmlBundlerPlugin
78
81
  });
79
82
  });
80
83
  }
81
84
  });
82
- const isStreamingSSR = (userConfig)=>{
85
+ const isStreamingSSR = (userConfig, entryName, appContext)=>{
83
86
  const isStreaming = (ssr)=>{
84
87
  if (!ssr) return false;
85
88
  if ('boolean' == typeof ssr) return ssr;
86
89
  return 'string' !== ssr.mode;
87
90
  };
88
91
  const { server } = userConfig;
92
+ if (void 0 !== entryName) return isStreaming((0, utils_namespaceObject.getEntryOptions)(entryName, (0, routes_js_namespaceObject.isMainEntry)(entryName, userConfig.source?.mainEntryName), server.ssr, server.ssrByEntries, appContext?.packageName));
89
93
  if (isStreaming(server.ssr)) return true;
90
94
  if (server?.ssrByEntries && 'object' == typeof server.ssrByEntries) {
91
95
  for (const name of Object.keys(server.ssrByEntries))if (isStreaming(server.ssrByEntries[name])) return true;
@@ -108,9 +112,10 @@ function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
108
112
  const detail = Array.from(unresolvedByEntry).map(([entry, comps])=>`${entry}: ${comps.join(', ')}`).join('; ');
109
113
  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
114
  }
111
- function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
115
+ function applyAsyncChunkHtmlPlugin({ chain, modernConfig, appContext, HtmlBundlerPlugin }) {
112
116
  if (isStreamingSSR(modernConfig) || (0, utils_namespaceObject.isUseRsc)(modernConfig)) chain.plugin('html-async-chunk').use(index_js_namespaceObject.HtmlAsyncChunkPlugin, [
113
- HtmlBundlerPlugin
117
+ HtmlBundlerPlugin,
118
+ (entryName)=>(0, utils_namespaceObject.isUseRsc)(modernConfig) || isStreamingSSR(modernConfig, entryName, appContext)
114
119
  ]);
115
120
  }
116
121
  function applyRouterPlugin(chain, pluginName, options, HtmlBundlerPlugin) {
@@ -173,8 +178,10 @@ function applySSRDataLoader(chain, options) {
173
178
  chain.module.rule('ssr-data-loader').test(reg).use('data-loader').loader(require.resolve('@modern-js/plugin-data-loader/loader')).end();
174
179
  }
175
180
  exports.builderPluginAdapterSSR = __webpack_exports__.builderPluginAdapterSSR;
181
+ exports.isStreamingSSR = __webpack_exports__.isStreamingSSR;
176
182
  for(var __rspack_i in __webpack_exports__)if (-1 === [
177
- "builderPluginAdapterSSR"
183
+ "builderPluginAdapterSSR",
184
+ "isStreamingSSR"
178
185
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
179
186
  Object.defineProperty(exports, '__esModule', {
180
187
  value: true
@@ -31,7 +31,20 @@ __webpack_require__.d(__webpack_exports__, {
31
31
  HtmlAsyncChunkPlugin: ()=>HtmlAsyncChunkPlugin
32
32
  });
33
33
  const builder_namespaceObject = require("@modern-js/builder");
34
+ const utils_namespaceObject = require("@modern-js/utils");
34
35
  class HtmlAsyncChunkPlugin {
36
+ shouldProcess(plugin) {
37
+ if (!this.shouldProcessEntry) return true;
38
+ const { entryName } = plugin.options;
39
+ if (void 0 === entryName) {
40
+ if (!this.warnedMissingEntryName) {
41
+ this.warnedMissingEntryName = true;
42
+ utils_namespaceObject.logger.warn("[HtmlAsyncChunkPlugin] Skipping streaming script processing because HTML options are missing entryName. When returning a new object from tools.htmlPlugin, spread the original options to preserve entryName.");
43
+ }
44
+ return false;
45
+ }
46
+ return this.shouldProcessEntry(entryName);
47
+ }
35
48
  apply(compiler) {
36
49
  compiler.hooks.compilation.tap(this.name, (compilation)=>{
37
50
  const hooks = this.htmlPlugin.getCompilationHooks(compilation);
@@ -51,6 +64,7 @@ class HtmlAsyncChunkPlugin {
51
64
  name: this.name,
52
65
  stage: -1
53
66
  }, (assets)=>{
67
+ if (!this.shouldProcess(assets.plugin)) return assets;
54
68
  const headTags = [];
55
69
  const bodyTags = [];
56
70
  const entryRuntimeUrls = runtimeInEntry ? getEntryRuntimeUrls(assets.publicPath) : new Set();
@@ -79,6 +93,7 @@ class HtmlAsyncChunkPlugin {
79
93
  name: this.name,
80
94
  stage: 1
81
95
  }, (assets)=>{
96
+ if (!this.shouldProcess(assets.plugin)) return assets;
82
97
  const entryRuntimeUrls = getEntryRuntimeUrls(assets.publicPath);
83
98
  for (const tag of assets.bodyTags){
84
99
  const src = tag.attributes?.src;
@@ -113,7 +128,9 @@ class HtmlAsyncChunkPlugin {
113
128
  });
114
129
  });
115
130
  }
116
- constructor(htmlPlugin){
131
+ constructor(htmlPlugin, shouldProcessEntry){
132
+ this.shouldProcessEntry = shouldProcessEntry;
133
+ this.warnedMissingEntryName = false;
117
134
  this.name = 'HtmlAsyncChunkPlugin';
118
135
  this.htmlPlugin = htmlPlugin;
119
136
  }
@@ -143,16 +143,18 @@ const collectStaticRoutes = (appContext, config)=>{
143
143
  const { appDirectory } = appContext;
144
144
  const { source: { configDir }, server: { publicRoutes = {} } } = config;
145
145
  const publicFolder = external_path_default().resolve(appDirectory, configDir || '', 'public');
146
+ const configFolder = external_path_default().resolve(appDirectory, configDir || '');
147
+ const publicFolderLength = toPosix(publicFolder).length;
146
148
  const ignoreFiles = [
147
149
  '.gitkeep'
148
150
  ];
149
151
  return utils_namespaceObject.fs.existsSync(publicFolder) ? (0, external_utils_js_namespaceObject.walkDirectory)(publicFolder).filter((filePath)=>!ignoreFiles.includes(external_path_default().basename(filePath))).map((filePath)=>{
150
- const urlPath = `${(0, utils_namespaceObject.urlJoin)(toPosix(filePath).slice(toPosix(publicFolder).length))}`;
152
+ const urlPath = `${(0, utils_namespaceObject.urlJoin)(toPosix(filePath).slice(publicFolderLength))}`;
151
153
  return {
152
154
  urlPath: publicRoutes[(0, utils_namespaceObject.removeLeadingSlash)(urlPath)] || urlPath,
153
155
  isSPA: true,
154
156
  isSSR: false,
155
- entryPath: toPosix(external_path_default().relative(external_path_default().resolve(appDirectory, configDir || ''), filePath))
157
+ entryPath: toPosix(external_path_default().relative(configFolder, filePath))
156
158
  };
157
159
  }) : [];
158
160
  };
@@ -52,17 +52,21 @@ var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_na
52
52
  const utils_namespaceObject = require("@modern-js/utils");
53
53
  const core_namespaceObject = require("@swc/core");
54
54
  const external_es_module_lexer_namespaceObject = require("es-module-lexer");
55
- const walkDirectory = (dir)=>external_fs_default().readdirSync(dir).reduce((previous, filename)=>{
56
- const filePath = external_path_default().join(dir, filename);
57
- if (external_fs_default().statSync(filePath).isDirectory()) return [
58
- ...previous,
59
- ...walkDirectory(filePath)
60
- ];
61
- else return [
62
- ...previous,
63
- filePath
64
- ];
65
- }, []);
55
+ const walkDirectory = (dir)=>{
56
+ const files = [];
57
+ const walk = (current)=>{
58
+ for (const dirent of external_fs_default().readdirSync(current, {
59
+ withFileTypes: true
60
+ })){
61
+ const filePath = external_path_default().join(current, dirent.name);
62
+ const isDirectory = dirent.isSymbolicLink() ? external_fs_default().statSync(filePath).isDirectory() : dirent.isDirectory();
63
+ if (isDirectory) walk(filePath);
64
+ else files.push(filePath);
65
+ }
66
+ };
67
+ walk(dir);
68
+ return files;
69
+ };
66
70
  const replaceWithAlias = (base, filePath, alias)=>{
67
71
  if (filePath.includes(base)) return (0, utils_namespaceObject.normalizeToPosixPath)(external_path_default().join(alias, external_path_default().relative(base, filePath)));
68
72
  else return filePath;
@@ -42,7 +42,6 @@ __webpack_require__.d(__webpack_exports__, {
42
42
  const external_node_path_namespaceObject = require("node:path");
43
43
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
44
44
  const utils_namespaceObject = require("@modern-js/utils");
45
- const external_ndepe_namespaceObject = require("ndepe");
46
45
  const routes_js_namespaceObject = require("../../../utils/routes.js");
47
46
  const index_js_namespaceObject = require("../utils/index.js");
48
47
  const generator_js_namespaceObject = require("../utils/generator.js");
@@ -114,7 +113,8 @@ const createNetlifyPreset = ({ appContext, modernConfig, needModernServer })=>{
114
113
  const entry = isEsmProject ? await (0, index_js_namespaceObject.resolveESMDependency)('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
115
114
  const netlifyEntry = isEsmProject ? await (0, index_js_namespaceObject.resolveESMDependency)('@modern-js/prod-server/netlify') : require.resolve('@modern-js/prod-server/netlify');
116
115
  if (!entry || !netlifyEntry) throw new Error('Cannot find @modern-js/prod-server');
117
- await (0, external_ndepe_namespaceObject.nodeDepEmit)({
116
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
117
+ await handleDependencies({
118
118
  appDir: appDirectory,
119
119
  sourceDir: funcsDirectory,
120
120
  includeEntries: [
@@ -42,7 +42,6 @@ __webpack_require__.d(__webpack_exports__, {
42
42
  const external_node_path_namespaceObject = require("node:path");
43
43
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
44
44
  const utils_namespaceObject = require("@modern-js/utils");
45
- const external_ndepe_namespaceObject = require("ndepe");
46
45
  const index_js_namespaceObject = require("../utils/index.js");
47
46
  const generator_js_namespaceObject = require("../utils/generator.js");
48
47
  const createNodePreset = ({ appContext, modernConfig, api })=>{
@@ -72,7 +71,8 @@ const createNodePreset = ({ appContext, modernConfig, api })=>{
72
71
  const filter = (filePath)=>!filePath.startsWith(staticDirectory) && !filePath.endsWith('.map');
73
72
  const entry = isEsmProject ? await (0, index_js_namespaceObject.resolveESMDependency)('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
74
73
  if (!entry) throw new Error('Cannot find @modern-js/prod-server');
75
- await (0, external_ndepe_namespaceObject.nodeDepEmit)({
74
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
75
+ await handleDependencies({
76
76
  appDir: appDirectory,
77
77
  sourceDir: outputDirectory,
78
78
  includeEntries: [
@@ -42,7 +42,6 @@ __webpack_require__.d(__webpack_exports__, {
42
42
  const external_node_path_namespaceObject = require("node:path");
43
43
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
44
44
  const utils_namespaceObject = require("@modern-js/utils");
45
- const external_ndepe_namespaceObject = require("ndepe");
46
45
  const routes_js_namespaceObject = require("../../../utils/routes.js");
47
46
  const index_js_namespaceObject = require("../utils/index.js");
48
47
  const generator_js_namespaceObject = require("../utils/generator.js");
@@ -134,7 +133,8 @@ const createVercelPreset = ({ appContext, modernConfig, needModernServer })=>{
134
133
  if (!needModernServer) return;
135
134
  const entry = isEsmProject ? await (0, index_js_namespaceObject.resolveESMDependency)('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
136
135
  if (!entry) throw new Error('Cannot find @modern-js/prod-server');
137
- await (0, external_ndepe_namespaceObject.nodeDepEmit)({
136
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
137
+ await handleDependencies({
138
138
  appDir: appDirectory,
139
139
  sourceDir: funcsDirectory,
140
140
  includeEntries: [
@@ -1,7 +1,8 @@
1
1
  import { SERVICE_WORKER_ENVIRONMENT_NAME, isHtmlDisabled } from "@modern-js/builder";
2
- import { fs, isUseRsc, isUseSSRBundle, logger } from "@modern-js/utils";
2
+ import { fs, getEntryOptions, isUseRsc, isUseSSRBundle, logger } from "@modern-js/utils";
3
3
  import { mergeRsbuildConfig } from "@rsbuild/core";
4
4
  import { getServerCombinedModuleFile } from "../../../plugins/analyze/utils.mjs";
5
+ import { isMainEntry } from "../../../utils/routes.mjs";
5
6
  import { HtmlAsyncChunkPlugin, RouterPlugin } from "../bundlerPlugins/index.mjs";
6
7
  import { aggregateEagerRouteComponentFiles, planSSRLazyCompilation } from "../lazyCompilation.mjs";
7
8
  import * as __rspack_external_path from "path";
@@ -12,7 +13,7 @@ const builderPluginAdapterSSR = (options)=>({
12
13
  api.modifyRsbuildConfig((config)=>{
13
14
  const merged = mergeRsbuildConfig(config, {
14
15
  html: {
15
- inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
16
+ inject: ({ entryName, value })=>isStreamingSSR(normalizedConfig, entryName, appContext) ? 'head' : value
16
17
  },
17
18
  server: {
18
19
  compress: isStreamingSSR(normalizedConfig) || isUseRsc(normalizedConfig) ? false : void 0
@@ -42,18 +43,20 @@ const builderPluginAdapterSSR = (options)=>({
42
43
  if (!isHtmlDisabled(builderConfig, target)) applyAsyncChunkHtmlPlugin({
43
44
  chain,
44
45
  modernConfig: options.normalizedConfig,
46
+ appContext,
45
47
  HtmlBundlerPlugin
46
48
  });
47
49
  });
48
50
  }
49
51
  });
50
- const isStreamingSSR = (userConfig)=>{
52
+ const isStreamingSSR = (userConfig, entryName, appContext)=>{
51
53
  const isStreaming = (ssr)=>{
52
54
  if (!ssr) return false;
53
55
  if ('boolean' == typeof ssr) return ssr;
54
56
  return 'string' !== ssr.mode;
55
57
  };
56
58
  const { server } = userConfig;
59
+ if (void 0 !== entryName) return isStreaming(getEntryOptions(entryName, isMainEntry(entryName, userConfig.source?.mainEntryName), server.ssr, server.ssrByEntries, appContext?.packageName));
57
60
  if (isStreaming(server.ssr)) return true;
58
61
  if (server?.ssrByEntries && 'object' == typeof server.ssrByEntries) {
59
62
  for (const name of Object.keys(server.ssrByEntries))if (isStreaming(server.ssrByEntries[name])) return true;
@@ -76,9 +79,10 @@ function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
76
79
  const detail = Array.from(unresolvedByEntry).map(([entry, comps])=>`${entry}: ${comps.join(', ')}`).join('; ');
77
80
  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
81
  }
79
- function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
82
+ function applyAsyncChunkHtmlPlugin({ chain, modernConfig, appContext, HtmlBundlerPlugin }) {
80
83
  if (isStreamingSSR(modernConfig) || isUseRsc(modernConfig)) chain.plugin('html-async-chunk').use(HtmlAsyncChunkPlugin, [
81
- HtmlBundlerPlugin
84
+ HtmlBundlerPlugin,
85
+ (entryName)=>isUseRsc(modernConfig) || isStreamingSSR(modernConfig, entryName, appContext)
82
86
  ]);
83
87
  }
84
88
  function applyRouterPlugin(chain, pluginName, options, HtmlBundlerPlugin) {
@@ -140,4 +144,4 @@ function applySSRDataLoader(chain, options) {
140
144
  const reg = new RegExp(`${absolutePath}.*\\.(loader|data|data.client)\\.[t|j]sx?$`);
141
145
  chain.module.rule('ssr-data-loader').test(reg).use('data-loader').loader(require.resolve('@modern-js/plugin-data-loader/loader')).end();
142
146
  }
143
- export { builderPluginAdapterSSR };
147
+ export { builderPluginAdapterSSR, isStreamingSSR };
@@ -1,5 +1,18 @@
1
1
  import { RUNTIME_CHUNK_REGEX } from "@modern-js/builder";
2
+ import { logger } from "@modern-js/utils";
2
3
  class HtmlAsyncChunkPlugin {
4
+ shouldProcess(plugin) {
5
+ if (!this.shouldProcessEntry) return true;
6
+ const { entryName } = plugin.options;
7
+ if (void 0 === entryName) {
8
+ if (!this.warnedMissingEntryName) {
9
+ this.warnedMissingEntryName = true;
10
+ logger.warn("[HtmlAsyncChunkPlugin] Skipping streaming script processing because HTML options are missing entryName. When returning a new object from tools.htmlPlugin, spread the original options to preserve entryName.");
11
+ }
12
+ return false;
13
+ }
14
+ return this.shouldProcessEntry(entryName);
15
+ }
3
16
  apply(compiler) {
4
17
  compiler.hooks.compilation.tap(this.name, (compilation)=>{
5
18
  const hooks = this.htmlPlugin.getCompilationHooks(compilation);
@@ -19,6 +32,7 @@ class HtmlAsyncChunkPlugin {
19
32
  name: this.name,
20
33
  stage: -1
21
34
  }, (assets)=>{
35
+ if (!this.shouldProcess(assets.plugin)) return assets;
22
36
  const headTags = [];
23
37
  const bodyTags = [];
24
38
  const entryRuntimeUrls = runtimeInEntry ? getEntryRuntimeUrls(assets.publicPath) : new Set();
@@ -47,6 +61,7 @@ class HtmlAsyncChunkPlugin {
47
61
  name: this.name,
48
62
  stage: 1
49
63
  }, (assets)=>{
64
+ if (!this.shouldProcess(assets.plugin)) return assets;
50
65
  const entryRuntimeUrls = getEntryRuntimeUrls(assets.publicPath);
51
66
  for (const tag of assets.bodyTags){
52
67
  const src = tag.attributes?.src;
@@ -81,7 +96,9 @@ class HtmlAsyncChunkPlugin {
81
96
  });
82
97
  });
83
98
  }
84
- constructor(htmlPlugin){
99
+ constructor(htmlPlugin, shouldProcessEntry){
100
+ this.shouldProcessEntry = shouldProcessEntry;
101
+ this.warnedMissingEntryName = false;
85
102
  this.name = 'HtmlAsyncChunkPlugin';
86
103
  this.htmlPlugin = htmlPlugin;
87
104
  }
@@ -100,16 +100,18 @@ const collectStaticRoutes = (appContext, config)=>{
100
100
  const { appDirectory } = appContext;
101
101
  const { source: { configDir }, server: { publicRoutes = {} } } = config;
102
102
  const publicFolder = path.resolve(appDirectory, configDir || '', 'public');
103
+ const configFolder = path.resolve(appDirectory, configDir || '');
104
+ const publicFolderLength = toPosix(publicFolder).length;
103
105
  const ignoreFiles = [
104
106
  '.gitkeep'
105
107
  ];
106
108
  return fs.existsSync(publicFolder) ? walkDirectory(publicFolder).filter((filePath)=>!ignoreFiles.includes(path.basename(filePath))).map((filePath)=>{
107
- const urlPath = `${urlJoin(toPosix(filePath).slice(toPosix(publicFolder).length))}`;
109
+ const urlPath = `${urlJoin(toPosix(filePath).slice(publicFolderLength))}`;
108
110
  return {
109
111
  urlPath: publicRoutes[removeLeadingSlash(urlPath)] || urlPath,
110
112
  isSPA: true,
111
113
  isSSR: false,
112
- entryPath: toPosix(path.relative(path.resolve(appDirectory, configDir || ''), filePath))
114
+ entryPath: toPosix(path.relative(configFolder, filePath))
113
115
  };
114
116
  }) : [];
115
117
  };
@@ -3,17 +3,21 @@ import path from "path";
3
3
  import { JS_EXTENSIONS, getCommand, normalizeToPosixPath } from "@modern-js/utils";
4
4
  import { transform } from "@swc/core";
5
5
  import { parse } from "es-module-lexer";
6
- const walkDirectory = (dir)=>fs.readdirSync(dir).reduce((previous, filename)=>{
7
- const filePath = path.join(dir, filename);
8
- if (fs.statSync(filePath).isDirectory()) return [
9
- ...previous,
10
- ...walkDirectory(filePath)
11
- ];
12
- else return [
13
- ...previous,
14
- filePath
15
- ];
16
- }, []);
6
+ const walkDirectory = (dir)=>{
7
+ const files = [];
8
+ const walk = (current)=>{
9
+ for (const dirent of fs.readdirSync(current, {
10
+ withFileTypes: true
11
+ })){
12
+ const filePath = path.join(current, dirent.name);
13
+ const isDirectory = dirent.isSymbolicLink() ? fs.statSync(filePath).isDirectory() : dirent.isDirectory();
14
+ if (isDirectory) walk(filePath);
15
+ else files.push(filePath);
16
+ }
17
+ };
18
+ walk(dir);
19
+ return files;
20
+ };
17
21
  const replaceWithAlias = (base, filePath, alias)=>{
18
22
  if (filePath.includes(base)) return normalizeToPosixPath(path.join(alias, path.relative(base, filePath)));
19
23
  else return filePath;
@@ -1,6 +1,5 @@
1
1
  import node_path from "node:path";
2
2
  import { fs, removeModuleSyncFromExports } from "@modern-js/utils";
3
- import { nodeDepEmit } from "ndepe";
4
3
  import { isMainEntry } from "../../../utils/routes.mjs";
5
4
  import { readTemplate, resolveESMDependency } from "../utils/index.mjs";
6
5
  import { generateHandler } from "../utils/generator.mjs";
@@ -72,7 +71,8 @@ const createNetlifyPreset = ({ appContext, modernConfig, needModernServer })=>{
72
71
  const entry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
73
72
  const netlifyEntry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server/netlify') : require.resolve('@modern-js/prod-server/netlify');
74
73
  if (!entry || !netlifyEntry) throw new Error('Cannot find @modern-js/prod-server');
75
- await nodeDepEmit({
74
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
75
+ await handleDependencies({
76
76
  appDir: appDirectory,
77
77
  sourceDir: funcsDirectory,
78
78
  includeEntries: [
@@ -1,6 +1,5 @@
1
1
  import node_path from "node:path";
2
2
  import { chalk, fs, removeModuleSyncFromExports } from "@modern-js/utils";
3
- import { nodeDepEmit } from "ndepe";
4
3
  import { readTemplate, resolveESMDependency } from "../utils/index.mjs";
5
4
  import { generateHandler } from "../utils/generator.mjs";
6
5
  const createNodePreset = ({ appContext, modernConfig, api })=>{
@@ -30,7 +29,8 @@ const createNodePreset = ({ appContext, modernConfig, api })=>{
30
29
  const filter = (filePath)=>!filePath.startsWith(staticDirectory) && !filePath.endsWith('.map');
31
30
  const entry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
32
31
  if (!entry) throw new Error('Cannot find @modern-js/prod-server');
33
- await nodeDepEmit({
32
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
33
+ await handleDependencies({
34
34
  appDir: appDirectory,
35
35
  sourceDir: outputDirectory,
36
36
  includeEntries: [
@@ -1,6 +1,5 @@
1
1
  import node_path from "node:path";
2
2
  import { fs, removeModuleSyncFromExports } from "@modern-js/utils";
3
- import { nodeDepEmit } from "ndepe";
4
3
  import { isMainEntry } from "../../../utils/routes.mjs";
5
4
  import { readTemplate, resolveESMDependency } from "../utils/index.mjs";
6
5
  import { generateHandler } from "../utils/generator.mjs";
@@ -92,7 +91,8 @@ const createVercelPreset = ({ appContext, modernConfig, needModernServer })=>{
92
91
  if (!needModernServer) return;
93
92
  const entry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
94
93
  if (!entry) throw new Error('Cannot find @modern-js/prod-server');
95
- await nodeDepEmit({
94
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
95
+ await handleDependencies({
96
96
  appDir: appDirectory,
97
97
  sourceDir: funcsDirectory,
98
98
  includeEntries: [
@@ -1,9 +1,10 @@
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, logger } from "@modern-js/utils";
4
+ import { fs, getEntryOptions, isUseRsc, isUseSSRBundle, logger } from "@modern-js/utils";
5
5
  import { mergeRsbuildConfig } from "@rsbuild/core";
6
6
  import { getServerCombinedModuleFile } from "../../../plugins/analyze/utils.mjs";
7
+ import { isMainEntry } from "../../../utils/routes.mjs";
7
8
  import { HtmlAsyncChunkPlugin, RouterPlugin } from "../bundlerPlugins/index.mjs";
8
9
  import { aggregateEagerRouteComponentFiles, planSSRLazyCompilation } from "../lazyCompilation.mjs";
9
10
  import * as __rspack_external_path from "path";
@@ -14,7 +15,7 @@ const builderPluginAdapterSSR = (options)=>({
14
15
  api.modifyRsbuildConfig((config)=>{
15
16
  const merged = mergeRsbuildConfig(config, {
16
17
  html: {
17
- inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
18
+ inject: ({ entryName, value })=>isStreamingSSR(normalizedConfig, entryName, appContext) ? 'head' : value
18
19
  },
19
20
  server: {
20
21
  compress: isStreamingSSR(normalizedConfig) || isUseRsc(normalizedConfig) ? false : void 0
@@ -44,18 +45,20 @@ const builderPluginAdapterSSR = (options)=>({
44
45
  if (!isHtmlDisabled(builderConfig, target)) applyAsyncChunkHtmlPlugin({
45
46
  chain,
46
47
  modernConfig: options.normalizedConfig,
48
+ appContext,
47
49
  HtmlBundlerPlugin
48
50
  });
49
51
  });
50
52
  }
51
53
  });
52
- const isStreamingSSR = (userConfig)=>{
54
+ const isStreamingSSR = (userConfig, entryName, appContext)=>{
53
55
  const isStreaming = (ssr)=>{
54
56
  if (!ssr) return false;
55
57
  if ('boolean' == typeof ssr) return ssr;
56
58
  return 'string' !== ssr.mode;
57
59
  };
58
60
  const { server } = userConfig;
61
+ if (void 0 !== entryName) return isStreaming(getEntryOptions(entryName, isMainEntry(entryName, userConfig.source?.mainEntryName), server.ssr, server.ssrByEntries, appContext?.packageName));
59
62
  if (isStreaming(server.ssr)) return true;
60
63
  if (server?.ssrByEntries && 'object' == typeof server.ssrByEntries) {
61
64
  for (const name of Object.keys(server.ssrByEntries))if (isStreaming(server.ssrByEntries[name])) return true;
@@ -78,9 +81,10 @@ function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
78
81
  const detail = Array.from(unresolvedByEntry).map(([entry, comps])=>`${entry}: ${comps.join(', ')}`).join('; ');
79
82
  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
83
  }
81
- function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
84
+ function applyAsyncChunkHtmlPlugin({ chain, modernConfig, appContext, HtmlBundlerPlugin }) {
82
85
  if (isStreamingSSR(modernConfig) || isUseRsc(modernConfig)) chain.plugin('html-async-chunk').use(HtmlAsyncChunkPlugin, [
83
- HtmlBundlerPlugin
86
+ HtmlBundlerPlugin,
87
+ (entryName)=>isUseRsc(modernConfig) || isStreamingSSR(modernConfig, entryName, appContext)
84
88
  ]);
85
89
  }
86
90
  function applyRouterPlugin(chain, pluginName, options, HtmlBundlerPlugin) {
@@ -142,4 +146,4 @@ function applySSRDataLoader(chain, options) {
142
146
  const reg = new RegExp(`${absolutePath}.*\\.(loader|data|data.client)\\.[t|j]sx?$`);
143
147
  chain.module.rule('ssr-data-loader').test(reg).use('data-loader').loader(require.resolve('@modern-js/plugin-data-loader/loader')).end();
144
148
  }
145
- export { builderPluginAdapterSSR };
149
+ export { builderPluginAdapterSSR, isStreamingSSR };
@@ -1,6 +1,19 @@
1
1
  import "node:module";
2
2
  import { RUNTIME_CHUNK_REGEX } from "@modern-js/builder";
3
+ import { logger } from "@modern-js/utils";
3
4
  class HtmlAsyncChunkPlugin {
5
+ shouldProcess(plugin) {
6
+ if (!this.shouldProcessEntry) return true;
7
+ const { entryName } = plugin.options;
8
+ if (void 0 === entryName) {
9
+ if (!this.warnedMissingEntryName) {
10
+ this.warnedMissingEntryName = true;
11
+ logger.warn("[HtmlAsyncChunkPlugin] Skipping streaming script processing because HTML options are missing entryName. When returning a new object from tools.htmlPlugin, spread the original options to preserve entryName.");
12
+ }
13
+ return false;
14
+ }
15
+ return this.shouldProcessEntry(entryName);
16
+ }
4
17
  apply(compiler) {
5
18
  compiler.hooks.compilation.tap(this.name, (compilation)=>{
6
19
  const hooks = this.htmlPlugin.getCompilationHooks(compilation);
@@ -20,6 +33,7 @@ class HtmlAsyncChunkPlugin {
20
33
  name: this.name,
21
34
  stage: -1
22
35
  }, (assets)=>{
36
+ if (!this.shouldProcess(assets.plugin)) return assets;
23
37
  const headTags = [];
24
38
  const bodyTags = [];
25
39
  const entryRuntimeUrls = runtimeInEntry ? getEntryRuntimeUrls(assets.publicPath) : new Set();
@@ -48,6 +62,7 @@ class HtmlAsyncChunkPlugin {
48
62
  name: this.name,
49
63
  stage: 1
50
64
  }, (assets)=>{
65
+ if (!this.shouldProcess(assets.plugin)) return assets;
51
66
  const entryRuntimeUrls = getEntryRuntimeUrls(assets.publicPath);
52
67
  for (const tag of assets.bodyTags){
53
68
  const src = tag.attributes?.src;
@@ -82,7 +97,9 @@ class HtmlAsyncChunkPlugin {
82
97
  });
83
98
  });
84
99
  }
85
- constructor(htmlPlugin){
100
+ constructor(htmlPlugin, shouldProcessEntry){
101
+ this.shouldProcessEntry = shouldProcessEntry;
102
+ this.warnedMissingEntryName = false;
86
103
  this.name = 'HtmlAsyncChunkPlugin';
87
104
  this.htmlPlugin = htmlPlugin;
88
105
  }
@@ -101,16 +101,18 @@ const collectStaticRoutes = (appContext, config)=>{
101
101
  const { appDirectory } = appContext;
102
102
  const { source: { configDir }, server: { publicRoutes = {} } } = config;
103
103
  const publicFolder = path.resolve(appDirectory, configDir || '', 'public');
104
+ const configFolder = path.resolve(appDirectory, configDir || '');
105
+ const publicFolderLength = toPosix(publicFolder).length;
104
106
  const ignoreFiles = [
105
107
  '.gitkeep'
106
108
  ];
107
109
  return fs.existsSync(publicFolder) ? walkDirectory(publicFolder).filter((filePath)=>!ignoreFiles.includes(path.basename(filePath))).map((filePath)=>{
108
- const urlPath = `${urlJoin(toPosix(filePath).slice(toPosix(publicFolder).length))}`;
110
+ const urlPath = `${urlJoin(toPosix(filePath).slice(publicFolderLength))}`;
109
111
  return {
110
112
  urlPath: publicRoutes[removeLeadingSlash(urlPath)] || urlPath,
111
113
  isSPA: true,
112
114
  isSSR: false,
113
- entryPath: toPosix(path.relative(path.resolve(appDirectory, configDir || ''), filePath))
115
+ entryPath: toPosix(path.relative(configFolder, filePath))
114
116
  };
115
117
  }) : [];
116
118
  };
@@ -4,17 +4,21 @@ import path from "path";
4
4
  import { JS_EXTENSIONS, getCommand, normalizeToPosixPath } from "@modern-js/utils";
5
5
  import { transform } from "@swc/core";
6
6
  import { parse } from "es-module-lexer";
7
- const walkDirectory = (dir)=>fs.readdirSync(dir).reduce((previous, filename)=>{
8
- const filePath = path.join(dir, filename);
9
- if (fs.statSync(filePath).isDirectory()) return [
10
- ...previous,
11
- ...walkDirectory(filePath)
12
- ];
13
- else return [
14
- ...previous,
15
- filePath
16
- ];
17
- }, []);
7
+ const walkDirectory = (dir)=>{
8
+ const files = [];
9
+ const walk = (current)=>{
10
+ for (const dirent of fs.readdirSync(current, {
11
+ withFileTypes: true
12
+ })){
13
+ const filePath = path.join(current, dirent.name);
14
+ const isDirectory = dirent.isSymbolicLink() ? fs.statSync(filePath).isDirectory() : dirent.isDirectory();
15
+ if (isDirectory) walk(filePath);
16
+ else files.push(filePath);
17
+ }
18
+ };
19
+ walk(dir);
20
+ return files;
21
+ };
18
22
  const replaceWithAlias = (base, filePath, alias)=>{
19
23
  if (filePath.includes(base)) return normalizeToPosixPath(path.join(alias, path.relative(base, filePath)));
20
24
  else return filePath;
@@ -2,7 +2,6 @@ import __rslib_shim_module__ from "node:module";
2
2
  const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
3
3
  import node_path from "node:path";
4
4
  import { fs, removeModuleSyncFromExports } from "@modern-js/utils";
5
- import { nodeDepEmit } from "ndepe";
6
5
  import { isMainEntry } from "../../../utils/routes.mjs";
7
6
  import { readTemplate, resolveESMDependency } from "../utils/index.mjs";
8
7
  import { generateHandler } from "../utils/generator.mjs";
@@ -74,7 +73,8 @@ const createNetlifyPreset = ({ appContext, modernConfig, needModernServer })=>{
74
73
  const entry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
75
74
  const netlifyEntry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server/netlify') : require.resolve('@modern-js/prod-server/netlify');
76
75
  if (!entry || !netlifyEntry) throw new Error('Cannot find @modern-js/prod-server');
77
- await nodeDepEmit({
76
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
77
+ await handleDependencies({
78
78
  appDir: appDirectory,
79
79
  sourceDir: funcsDirectory,
80
80
  includeEntries: [
@@ -2,7 +2,6 @@ import __rslib_shim_module__ from "node:module";
2
2
  const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
3
3
  import node_path from "node:path";
4
4
  import { chalk, fs, removeModuleSyncFromExports } from "@modern-js/utils";
5
- import { nodeDepEmit } from "ndepe";
6
5
  import { readTemplate, resolveESMDependency } from "../utils/index.mjs";
7
6
  import { generateHandler } from "../utils/generator.mjs";
8
7
  const createNodePreset = ({ appContext, modernConfig, api })=>{
@@ -32,7 +31,8 @@ const createNodePreset = ({ appContext, modernConfig, api })=>{
32
31
  const filter = (filePath)=>!filePath.startsWith(staticDirectory) && !filePath.endsWith('.map');
33
32
  const entry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
34
33
  if (!entry) throw new Error('Cannot find @modern-js/prod-server');
35
- await nodeDepEmit({
34
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
35
+ await handleDependencies({
36
36
  appDir: appDirectory,
37
37
  sourceDir: outputDirectory,
38
38
  includeEntries: [
@@ -2,7 +2,6 @@ import __rslib_shim_module__ from "node:module";
2
2
  const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
3
3
  import node_path from "node:path";
4
4
  import { fs, removeModuleSyncFromExports } from "@modern-js/utils";
5
- import { nodeDepEmit } from "ndepe";
6
5
  import { isMainEntry } from "../../../utils/routes.mjs";
7
6
  import { readTemplate, resolveESMDependency } from "../utils/index.mjs";
8
7
  import { generateHandler } from "../utils/generator.mjs";
@@ -94,7 +93,8 @@ const createVercelPreset = ({ appContext, modernConfig, needModernServer })=>{
94
93
  if (!needModernServer) return;
95
94
  const entry = isEsmProject ? await resolveESMDependency('@modern-js/prod-server') : require.resolve('@modern-js/prod-server');
96
95
  if (!entry) throw new Error('Cannot find @modern-js/prod-server');
97
- await nodeDepEmit({
96
+ const { nodeDepEmit: handleDependencies } = await import("ndepe");
97
+ await handleDependencies({
98
98
  appDir: appDirectory,
99
99
  sourceDir: funcsDirectory,
100
100
  includeEntries: [
@@ -1,3 +1,6 @@
1
1
  import { type RsbuildPlugin } from '@rsbuild/core';
2
+ import type { AppNormalizedConfig } from '../../../types/index.js';
3
+ import type { AppToolsContext } from '../../../types/plugin.js';
2
4
  import type { BuilderOptions } from '../types.js';
3
5
  export declare const builderPluginAdapterSSR: (options: BuilderOptions) => RsbuildPlugin;
6
+ export declare const isStreamingSSR: (userConfig: AppNormalizedConfig, entryName?: string, appContext?: Pick<AppToolsContext, "packageName">) => boolean;
@@ -1,7 +1,10 @@
1
1
  import type { Rspack } from '@modern-js/builder';
2
2
  export declare class HtmlAsyncChunkPlugin {
3
+ private readonly shouldProcessEntry?;
3
4
  name: string;
4
5
  htmlPlugin: typeof Rspack.HtmlRspackPlugin;
5
- constructor(htmlPlugin: typeof Rspack.HtmlRspackPlugin);
6
+ private warnedMissingEntryName;
7
+ constructor(htmlPlugin: typeof Rspack.HtmlRspackPlugin, shouldProcessEntry?: ((entryName: string) => boolean) | undefined);
8
+ private shouldProcess;
6
9
  apply(compiler: Rspack.Compiler): void;
7
10
  }
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "modern",
16
16
  "modern.js"
17
17
  ],
18
- "version": "3.10.1",
18
+ "version": "3.10.3",
19
19
  "types": "./dist/types/index.d.ts",
20
20
  "main": "./dist/cjs/index.js",
21
21
  "exports": {
@@ -126,16 +126,16 @@
126
126
  "ndepe": "^0.1.13",
127
127
  "pkg-types": "^1.3.1",
128
128
  "std-env": "^3.10.0",
129
- "@modern-js/builder": "3.10.1",
130
- "@modern-js/i18n-utils": "3.10.1",
131
- "@modern-js/plugin-data-loader": "3.10.1",
132
- "@modern-js/plugin": "3.10.1",
133
- "@modern-js/prod-server": "3.10.1",
134
- "@modern-js/server-core": "3.10.1",
135
- "@modern-js/server-utils": "3.10.1",
136
- "@modern-js/server": "3.10.1",
137
- "@modern-js/types": "3.10.1",
138
- "@modern-js/utils": "3.10.1"
129
+ "@modern-js/builder": "3.10.3",
130
+ "@modern-js/plugin": "3.10.3",
131
+ "@modern-js/i18n-utils": "3.10.3",
132
+ "@modern-js/plugin-data-loader": "3.10.3",
133
+ "@modern-js/prod-server": "3.10.3",
134
+ "@modern-js/server": "3.10.3",
135
+ "@modern-js/server-core": "3.10.3",
136
+ "@modern-js/server-utils": "3.10.3",
137
+ "@modern-js/types": "3.10.3",
138
+ "@modern-js/utils": "3.10.3"
139
139
  },
140
140
  "devDependencies": {
141
141
  "@types/babel__traverse": "7.28.0",
@@ -143,8 +143,8 @@
143
143
  "ts-node": "^10.9.2",
144
144
  "tsconfig-paths": "^4.2.0",
145
145
  "typescript": "^5",
146
- "@modern-js/rslib": "2.68.10",
147
- "@scripts/rstest-config": "2.66.0"
146
+ "@scripts/rstest-config": "2.66.2",
147
+ "@modern-js/rslib": "3.10.3"
148
148
  },
149
149
  "peerDependencies": {
150
150
  "ts-node": "^10.9.2",