@bleedingdev/modern-js-app-tools 3.4.0-ultramodern.0 → 3.4.0-ultramodern.10

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 (41) hide show
  1. package/dist/cjs/builder/generator/getBuilderEnvironments.js +49 -5
  2. package/dist/cjs/builder/generator/index.js +1 -0
  3. package/dist/cjs/builder/shared/builderPlugins/adapterLazyCompilation.js +74 -0
  4. package/dist/cjs/builder/shared/builderPlugins/adapterSSR.js +3 -28
  5. package/dist/cjs/builder/shared/builderPlugins/index.js +13 -6
  6. package/dist/cjs/builder/shared/lazyCompilation.js +11 -3
  7. package/dist/cjs/esm/register-esm.js +14 -1
  8. package/dist/cjs/esm/register-esm.mjs +14 -1
  9. package/dist/cjs/plugins/deploy/index.js +9 -1
  10. package/dist/cjs/plugins/deploy/platforms/cloudflare.js +13 -4
  11. package/dist/cjs/rsbuild.js +7 -3
  12. package/dist/cjs/utils/initAppContext.js +12 -1
  13. package/dist/esm/builder/generator/getBuilderEnvironments.mjs +49 -5
  14. package/dist/esm/builder/generator/index.mjs +2 -1
  15. package/dist/esm/builder/shared/builderPlugins/adapterLazyCompilation.mjs +36 -0
  16. package/dist/esm/builder/shared/builderPlugins/adapterSSR.mjs +4 -29
  17. package/dist/esm/builder/shared/builderPlugins/index.mjs +1 -0
  18. package/dist/esm/builder/shared/lazyCompilation.mjs +6 -4
  19. package/dist/esm/esm/register-esm.mjs +14 -1
  20. package/dist/esm/plugins/deploy/index.mjs +9 -1
  21. package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +13 -4
  22. package/dist/esm/rsbuild.mjs +7 -3
  23. package/dist/esm/utils/initAppContext.mjs +12 -1
  24. package/dist/esm-node/builder/generator/getBuilderEnvironments.mjs +49 -5
  25. package/dist/esm-node/builder/generator/index.mjs +2 -1
  26. package/dist/esm-node/builder/shared/builderPlugins/adapterLazyCompilation.mjs +37 -0
  27. package/dist/esm-node/builder/shared/builderPlugins/adapterSSR.mjs +4 -29
  28. package/dist/esm-node/builder/shared/builderPlugins/index.mjs +1 -0
  29. package/dist/esm-node/builder/shared/lazyCompilation.mjs +6 -4
  30. package/dist/esm-node/esm/register-esm.mjs +14 -1
  31. package/dist/esm-node/plugins/deploy/index.mjs +9 -1
  32. package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +13 -4
  33. package/dist/esm-node/rsbuild.mjs +7 -3
  34. package/dist/esm-node/utils/initAppContext.mjs +12 -1
  35. package/dist/types/builder/shared/builderPlugins/adapterLazyCompilation.d.ts +3 -0
  36. package/dist/types/builder/shared/builderPlugins/index.d.ts +1 -0
  37. package/dist/types/builder/shared/lazyCompilation.d.ts +7 -5
  38. package/dist/types/builder/shared/types.d.ts +3 -3
  39. package/dist/types/esm/register-esm.d.mts +1 -1
  40. package/dist/types/rsbuild.d.ts +1 -0
  41. package/package.json +21 -29
@@ -1,30 +1,21 @@
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, isUseRsc, isUseSSRBundle } 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";
7
6
  import * as __rspack_external_path from "path";
8
7
  const builderPluginAdapterSSR = (options)=>({
9
8
  name: 'builder-plugin-adapter-modern-ssr',
10
9
  setup (api) {
11
- const { normalizedConfig, appContext, eagerRouteComponentFilesByEntry } = options;
12
- api.modifyRsbuildConfig((config)=>{
13
- const merged = mergeRsbuildConfig(config, {
10
+ const { normalizedConfig, appContext } = options;
11
+ api.modifyRsbuildConfig((config)=>mergeRsbuildConfig(config, {
14
12
  html: {
15
13
  inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
16
14
  },
17
15
  server: {
18
16
  compress: isStreamingSSR(normalizedConfig) || isUseRsc(normalizedConfig) ? false : void 0
19
17
  }
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
- });
18
+ }));
28
19
  api.modifyBundlerChain(async (chain, { target, isProd, HtmlPlugin: HtmlBundlerPlugin, isServer, environment })=>{
29
20
  const builderConfig = environment.config;
30
21
  const { normalizedConfig } = options;
@@ -60,22 +51,6 @@ const isStreamingSSR = (userConfig)=>{
60
51
  }
61
52
  return false;
62
53
  };
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
- }
79
54
  function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
80
55
  if (isStreamingSSR(modernConfig) || isUseRsc(modernConfig)) chain.plugin('html-async-chunk').use(HtmlAsyncChunkPlugin, [
81
56
  HtmlBundlerPlugin
@@ -1,5 +1,6 @@
1
1
  export * from "./adapterBasic.mjs";
2
2
  export * from "./adapterHtml.mjs";
3
+ export * from "./adapterLazyCompilation.mjs";
3
4
  export * from "./adapterPrecompress.mjs";
4
5
  export * from "./adapterSSR.mjs";
5
6
  export * from "./builderHooks.mjs";
@@ -11,7 +11,7 @@ function aggregateEagerRouteComponentFiles(byEntry) {
11
11
  unresolvedByEntry
12
12
  };
13
13
  }
14
- function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
14
+ function buildRouteEagerLazyCompilationTest(eagerRouteFiles, userTest) {
15
15
  const userTestFn = 'function' == typeof userTest ? userTest : userTest instanceof RegExp ? (m)=>userTest.test(m.resource || '') : ()=>true;
16
16
  return (m)=>{
17
17
  const resource = m.resource;
@@ -20,7 +20,8 @@ function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
20
20
  return userTestFn(m);
21
21
  };
22
22
  }
23
- function planSSRLazyCompilation(current, info) {
23
+ const buildSSRLazyCompilationTest = buildRouteEagerLazyCompilationTest;
24
+ function planRouteEagerLazyCompilation(current, info) {
24
25
  if (!current) return {
25
26
  apply: false
26
27
  };
@@ -37,8 +38,9 @@ function planSSRLazyCompilation(current, info) {
37
38
  apply: true,
38
39
  lazyCompilation: {
39
40
  ...base,
40
- test: buildSSRLazyCompilationTest(info.files, userTest)
41
+ test: buildRouteEagerLazyCompilationTest(info.files, userTest)
41
42
  }
42
43
  };
43
44
  }
44
- export { aggregateEagerRouteComponentFiles, buildSSRLazyCompilationTest, collectRouteComponentFiles, normalizeModulePath, planSSRLazyCompilation };
45
+ const planSSRLazyCompilation = planRouteEagerLazyCompilation;
46
+ export { aggregateEagerRouteComponentFiles, buildRouteEagerLazyCompilationTest, buildSSRLazyCompilationTest, collectRouteComponentFiles, normalizeModulePath, planRouteEagerLazyCompilation, planSSRLazyCompilation };
@@ -1,5 +1,18 @@
1
+ let registeredPathHooks;
1
2
  const registerPathsLoader = async ({ appDir, baseUrl, paths })=>{
2
- const { register } = await import("node:module");
3
+ const { register, registerHooks } = await import("node:module");
4
+ if ('function' == typeof registerHooks) {
5
+ const loader = await import("./ts-paths-loader.mjs");
6
+ await loader.initialize({
7
+ appDir,
8
+ baseUrl,
9
+ paths
10
+ });
11
+ registeredPathHooks ??= registerHooks({
12
+ resolve: loader.resolve
13
+ });
14
+ return registeredPathHooks;
15
+ }
3
16
  register('./ts-paths-loader.mjs', import.meta.url, {
4
17
  data: {
5
18
  appDir,
@@ -14,7 +14,15 @@ const deployPresets = {
14
14
  };
15
15
  const getSupportedDeployTargets = ()=>Object.keys(deployPresets);
16
16
  const isDeployTarget = (target)=>Object.prototype.hasOwnProperty.call(deployPresets, target);
17
- const resolveDeployTarget = (modernConfig, envDeployTarget = process.env.MODERNJS_DEPLOY, detectedProvider = provider)=>modernConfig.deploy?.target || envDeployTarget || detectedProvider || 'node';
17
+ const providerDeployTargets = {
18
+ vercel: 'vercel',
19
+ netlify: 'netlify',
20
+ cloudflare: 'cloudflare',
21
+ cloudflare_pages: 'cloudflare',
22
+ cloudflare_workers: 'cloudflare'
23
+ };
24
+ const normalizeDetectedProvider = (value)=>value ? providerDeployTargets[value] : void 0;
25
+ const resolveDeployTarget = (modernConfig, envDeployTarget = process.env.MODERNJS_DEPLOY, detectedProvider = provider)=>modernConfig.deploy?.target || envDeployTarget || normalizeDetectedProvider(detectedProvider) || 'node';
18
26
  async function getDeployPreset(appContext, modernConfig, deployTarget, api) {
19
27
  const { appDirectory, distDirectory, metaName } = appContext;
20
28
  const { useSSR, useAPI, useWebServer } = getProjectUsage(appDirectory, distDirectory, metaName);
@@ -12,6 +12,7 @@ const PUBLIC_ASSETS_DIRECTORY = 'public';
12
12
  const WORKER_BUNDLE_DIRECTORY = 'worker';
13
13
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
14
14
  const BFF_EFFECT_WORKER_ENTRY = `${WORKER_BUNDLE_DIRECTORY}/__modern_bff_effect.js`;
15
+ const EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE = 'Ensure the Effect BFF entry exists at api/effect/index.ts or bff.effect.entry, and import Cloudflare edge handlers from @modern-js/plugin-bff/effect-edge instead of lambda/Hono server helpers.';
15
16
  const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
16
17
  const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
17
18
  const DEFAULT_SECURITY_HEADERS = {
@@ -205,6 +206,7 @@ const readRouteSpec = async (outputDirectory)=>{
205
206
  routes: Array.isArray(routeSpec.routes) ? routeSpec.routes : []
206
207
  };
207
208
  };
209
+ const createMissingEffectBffWorkerError = (outputDirectory, worker)=>new Error(`Cloudflare Effect BFF is configured, but the BFF worker bundle is missing: ${node_path.join(outputDirectory, worker)}. ${EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE}`);
208
210
  const createWorkerManifest = async (outputDirectory, modernConfig)=>{
209
211
  const routeSpec = await readRouteSpec(outputDirectory);
210
212
  const routes = await Promise.all(routeSpec.routes.map(async (route)=>{
@@ -222,6 +224,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
222
224
  const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
223
225
  const isEffectBff = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
224
226
  const effectBffWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
227
+ if (isEffectBff && primaryBffPrefix && !effectBffWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
225
228
  return {
226
229
  version: 1,
227
230
  runtime: {
@@ -315,9 +318,15 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
315
318
  const routeSpecSourcePath = node_path.join(distDirectory, ROUTE_SPEC_FILE);
316
319
  if (await fs.pathExists(routeSpecSourcePath)) await fs.copy(routeSpecSourcePath, routeSpecOutputPath);
317
320
  const workerBundleSourceDirectory = node_path.join(distDirectory, WORKER_BUNDLE_DIRECTORY);
318
- if (await fs.pathExists(workerBundleSourceDirectory)) await fs.copy(workerBundleSourceDirectory, node_path.join(outputDirectory, WORKER_BUNDLE_DIRECTORY), {
319
- filter: (src)=>shouldCopyToWorkerBundle(src, workerBundleSourceDirectory)
320
- });
321
+ const workerBundleOutputDirectory = node_path.join(outputDirectory, WORKER_BUNDLE_DIRECTORY);
322
+ if (await fs.pathExists(workerBundleSourceDirectory)) {
323
+ await fs.copy(workerBundleSourceDirectory, workerBundleOutputDirectory, {
324
+ filter: (src)=>shouldCopyToWorkerBundle(src, workerBundleSourceDirectory)
325
+ });
326
+ await fs.writeJSON(node_path.join(workerBundleOutputDirectory, 'package.json'), {
327
+ type: 'commonjs'
328
+ });
329
+ }
321
330
  await fs.writeJSON(wranglerConfigPath, {
322
331
  $schema: 'node_modules/wrangler/config-schema.json',
323
332
  name: workerName,
@@ -339,7 +348,7 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
339
348
  spaces: 2
340
349
  });
341
350
  await fs.writeJSON(node_path.join(outputDirectory, 'package.json'), {
342
- type: 'commonjs'
351
+ type: 'module'
343
352
  });
344
353
  },
345
354
  async genEntry () {
@@ -23,9 +23,13 @@ async function resolveModernRsbuildConfig(options) {
23
23
  plugins: modernConfig.builderPlugins
24
24
  };
25
25
  const appContext = getAppContext();
26
- const { rsbuildConfig, rsbuildPlugins } = await parseRspackConfig(nonStandardConfig, {
27
- cwd
28
- });
26
+ const builderOptions = {
27
+ cwd,
28
+ ...void 0 === options.disableReactCompiler ? {} : {
29
+ disableReactCompiler: options.disableReactCompiler
30
+ }
31
+ };
32
+ const { rsbuildConfig, rsbuildPlugins } = await parseRspackConfig(nonStandardConfig, builderOptions);
29
33
  const adapterParams = {
30
34
  appContext,
31
35
  normalizedConfig: modernConfig
@@ -1,5 +1,16 @@
1
1
  import { address, fs } from "@modern-js/utils";
2
2
  import path from "path";
3
+ function isSymlinkedNodeModules(appDirectory) {
4
+ try {
5
+ return fs.lstatSync(path.resolve(appDirectory, 'node_modules')).isSymbolicLink();
6
+ } catch {
7
+ return false;
8
+ }
9
+ }
10
+ function getDefaultInternalDirectory(appDirectory, metaName) {
11
+ if (isSymlinkedNodeModules(appDirectory)) return path.resolve(appDirectory, `.${metaName}`);
12
+ return path.resolve(appDirectory, `./node_modules/.${metaName}`);
13
+ }
3
14
  const initAppContext = ({ metaName, appDirectory, runtimeConfigFile, options, tempDir })=>{
4
15
  const { apiDir = 'api', sharedDir = 'shared', bffRuntimeFramework = 'hono' } = options || {};
5
16
  const pkgPath = path.resolve(appDirectory, './package.json');
@@ -13,7 +24,7 @@ const initAppContext = ({ metaName, appDirectory, runtimeConfigFile, options, te
13
24
  lambdaDirectory: path.resolve(appDirectory, apiDir, 'lambda'),
14
25
  sharedDirectory: path.resolve(appDirectory, sharedDir),
15
26
  serverPlugins: [],
16
- internalDirectory: path.resolve(appDirectory, tempDir || `./node_modules/.${metaName}`),
27
+ internalDirectory: tempDir ? path.resolve(appDirectory, tempDir) : getDefaultInternalDirectory(appDirectory, metaName),
17
28
  htmlTemplates: {},
18
29
  serverRoutes: [],
19
30
  entrypoints: [],
@@ -10,12 +10,14 @@ var getBuilderEnvironments_dirname = __rspack_dirname(__rspack_fileURLToPath(imp
10
10
  const BFF_EFFECT_WORKER_ENTRY_NAME = '__modern_bff_effect';
11
11
  const BFF_EFFECT_WORKER_RUNTIME_QUERY = 'modern-bff-runtime';
12
12
  const JS_OR_TS_EXTS = [
13
- '.ts',
14
- '.tsx',
15
13
  '.js',
16
14
  '.jsx',
15
+ '.ts',
16
+ '.tsx',
17
17
  '.mjs',
18
- '.cjs'
18
+ '.mts',
19
+ '.cjs',
20
+ '.cts'
19
21
  ];
20
22
  const CLOUDFLARE_WORKER_NODE_BUILTINS = [
21
23
  'async_hooks',
@@ -35,6 +37,11 @@ const CLOUDFLARE_WORKER_COMPAT_TEMPLATE_DIR = node_path.resolve(getBuilderEnviro
35
37
  function findExistingFile(candidates) {
36
38
  return candidates.find((candidate)=>node_fs.existsSync(candidate));
37
39
  }
40
+ function resolveJsOrTsEntry(entryWithoutOrWithExt) {
41
+ const extension = node_path.extname(entryWithoutOrWithExt);
42
+ if (JS_OR_TS_EXTS.includes(extension)) return node_fs.existsSync(entryWithoutOrWithExt) ? entryWithoutOrWithExt : void 0;
43
+ return findExistingFile(JS_OR_TS_EXTS.map((extension)=>`${entryWithoutOrWithExt}${extension}`));
44
+ }
38
45
  function resolvePackageEntry(packageName, paths) {
39
46
  try {
40
47
  return node_fs.realpathSync(require.resolve(packageName, {
@@ -92,13 +99,50 @@ function getCloudflareWorkerNodeExternals() {
92
99
  function getEffectBffEntry(normalizedConfig, appContext) {
93
100
  if (!normalizedConfig.bff || 'hono' === normalizedConfig.bff.runtimeFramework) return;
94
101
  const configuredEntry = normalizedConfig.bff.effect?.entry;
95
- const entryWithoutExtension = configuredEntry ? node_path.isAbsolute(configuredEntry) ? configuredEntry : node_path.resolve(appContext.appDirectory, configuredEntry) : node_path.resolve(appContext.apiDirectory, 'effect', 'index');
96
- return findExistingFile(JS_OR_TS_EXTS.map((extension)=>`${entryWithoutExtension}${extension}`));
102
+ const entryWithoutOrWithExtension = configuredEntry ? node_path.isAbsolute(configuredEntry) ? configuredEntry : node_path.resolve(appContext.appDirectory, configuredEntry) : node_path.resolve(appContext.apiDirectory, 'effect', 'index');
103
+ return resolveJsOrTsEntry(entryWithoutOrWithExtension);
97
104
  }
98
105
  function isCloudflareWorkerDeploy(normalizedConfig) {
99
106
  return normalizedConfig.deploy?.target === 'cloudflare' || 'cloudflare' === process.env.MODERNJS_DEPLOY;
100
107
  }
108
+ function getConsumingReactRuntimeAliases(appContext) {
109
+ const resolvePaths = [
110
+ appContext.appDirectory,
111
+ process.cwd()
112
+ ];
113
+ return {
114
+ react$: resolvePackageFile('react', 'index.js', resolvePaths),
115
+ 'react/jsx-runtime$': resolvePackageFile('react', 'jsx-runtime.js', resolvePaths),
116
+ 'react/jsx-dev-runtime$': resolvePackageFile('react', 'jsx-dev-runtime.js', resolvePaths),
117
+ 'react/compiler-runtime$': resolvePackageFile('react', 'compiler-runtime.js', resolvePaths)
118
+ };
119
+ }
120
+ function setResolvedAliases(alias, aliases) {
121
+ for (const [name, value] of Object.entries(aliases))setAliasIfPresent(alias, name, value);
122
+ }
123
+ function appendBundlerChain(config, handler) {
124
+ const bundlerChain = config.tools?.bundlerChain;
125
+ config.tools = {
126
+ ...config.tools,
127
+ bundlerChain: bundlerChain ? Array.isArray(bundlerChain) ? [
128
+ ...bundlerChain,
129
+ handler
130
+ ] : [
131
+ bundlerChain,
132
+ handler
133
+ ] : handler
134
+ };
135
+ }
136
+ function applySourceBuildReactRuntimeAliases(normalizedConfig, appContext, tempBuilderConfig) {
137
+ if (!normalizedConfig.experiments?.sourceBuild) return;
138
+ const aliases = getConsumingReactRuntimeAliases(appContext);
139
+ if (!Object.values(aliases).some(Boolean)) return;
140
+ appendBundlerChain(tempBuilderConfig, (chain)=>{
141
+ setResolvedAliases(chain.resolve.alias, aliases);
142
+ });
143
+ }
101
144
  function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig) {
145
+ applySourceBuildReactRuntimeAliases(normalizedConfig, appContext, tempBuilderConfig);
102
146
  const entries = {};
103
147
  const { entrypoints = [], checkedEntries } = appContext;
104
148
  for (const { entryName, internalEntry, entry } of entrypoints){
@@ -1,7 +1,7 @@
1
1
  import "node:module";
2
2
  import { createBuilder } from "@modern-js/builder";
3
3
  import { mergeRsbuildConfig } from "@rsbuild/core";
4
- import { builderPluginAdapterBasic, builderPluginAdapterHooks, builderPluginAdapterHtml, builderPluginAdapterPrecompress, builderPluginAdapterSSR } from "../shared/builderPlugins/index.mjs";
4
+ import { builderPluginAdapterBasic, builderPluginAdapterHooks, builderPluginAdapterHtml, builderPluginAdapterLazyCompilation, builderPluginAdapterPrecompress, builderPluginAdapterSSR } from "../shared/builderPlugins/index.mjs";
5
5
  import { builderPluginAdapterCopy } from "./adapterCopy.mjs";
6
6
  import { createBuilderProviderConfig } from "./createBuilderProviderConfig.mjs";
7
7
  import { getBuilderEnvironments } from "./getBuilderEnvironments.mjs";
@@ -39,6 +39,7 @@ async function generateBuilder(options, bundlerType) {
39
39
  async function applyBuilderPlugins(builder, options) {
40
40
  builder.addPlugins([
41
41
  builderPluginAdapterBasic(options),
42
+ builderPluginAdapterLazyCompilation(options),
42
43
  builderPluginAdapterSSR(options),
43
44
  builderPluginAdapterHtml(options),
44
45
  builderPluginAdapterPrecompress(options),
@@ -0,0 +1,37 @@
1
+ import "node:module";
2
+ import { isUseRsc, logger } from "@modern-js/utils";
3
+ import { aggregateEagerRouteComponentFiles, planRouteEagerLazyCompilation } from "../lazyCompilation.mjs";
4
+ const builderPluginAdapterLazyCompilation = (options)=>({
5
+ name: 'builder-plugin-adapter-modern-lazy-compilation',
6
+ setup (api) {
7
+ api.modifyRsbuildConfig((config)=>{
8
+ const lazyCompilation = getRouteEagerLazyCompilation(options, config);
9
+ if (void 0 === lazyCompilation) return config;
10
+ return {
11
+ ...config,
12
+ dev: {
13
+ ...config.dev,
14
+ lazyCompilation
15
+ }
16
+ };
17
+ });
18
+ }
19
+ });
20
+ function getRouteEagerLazyCompilation(options, config) {
21
+ const current = config.dev?.lazyCompilation;
22
+ if (!current || isUseRsc(options.normalizedConfig)) return;
23
+ const plan = planRouteEagerLazyCompilation(current, aggregateEagerRouteComponentFiles(options.eagerRouteComponentFilesByEntry));
24
+ if (!plan.apply) {
25
+ if (plan.unresolvedByEntry) warnUnresolvedRouteComponents(options.appContext.appDirectory, plan.unresolvedByEntry);
26
+ return;
27
+ }
28
+ return plan.lazyCompilation;
29
+ }
30
+ const warnedLazyApps = new Set();
31
+ function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
32
+ if (warnedLazyApps.has(appDirectory)) return;
33
+ warnedLazyApps.add(appDirectory);
34
+ const detail = Array.from(unresolvedByEntry).map(([entry, components])=>`${entry}: ${components.join(', ')}`).join('; ');
35
+ logger.warn(`[lazyCompilation] Skipped route-eager optimization because some route components could not be resolved to a file (${detail}). Lazy compilation may delay route rendering for these routes.`);
36
+ }
37
+ export { builderPluginAdapterLazyCompilation };
@@ -1,32 +1,23 @@
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, isUseRsc, isUseSSRBundle } 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";
9
8
  import * as __rspack_external_path from "path";
10
9
  const builderPluginAdapterSSR = (options)=>({
11
10
  name: 'builder-plugin-adapter-modern-ssr',
12
11
  setup (api) {
13
- const { normalizedConfig, appContext, eagerRouteComponentFilesByEntry } = options;
14
- api.modifyRsbuildConfig((config)=>{
15
- const merged = mergeRsbuildConfig(config, {
12
+ const { normalizedConfig, appContext } = options;
13
+ api.modifyRsbuildConfig((config)=>mergeRsbuildConfig(config, {
16
14
  html: {
17
15
  inject: isStreamingSSR(normalizedConfig) ? 'head' : void 0
18
16
  },
19
17
  server: {
20
18
  compress: isStreamingSSR(normalizedConfig) || isUseRsc(normalizedConfig) ? false : void 0
21
19
  }
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
- });
20
+ }));
30
21
  api.modifyBundlerChain(async (chain, { target, isProd, HtmlPlugin: HtmlBundlerPlugin, isServer, environment })=>{
31
22
  const builderConfig = environment.config;
32
23
  const { normalizedConfig } = options;
@@ -62,22 +53,6 @@ const isStreamingSSR = (userConfig)=>{
62
53
  }
63
54
  return false;
64
55
  };
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
- }
81
56
  function applyAsyncChunkHtmlPlugin({ chain, modernConfig, HtmlBundlerPlugin }) {
82
57
  if (isStreamingSSR(modernConfig) || isUseRsc(modernConfig)) chain.plugin('html-async-chunk').use(HtmlAsyncChunkPlugin, [
83
58
  HtmlBundlerPlugin
@@ -1,6 +1,7 @@
1
1
  import "node:module";
2
2
  export * from "./adapterBasic.mjs";
3
3
  export * from "./adapterHtml.mjs";
4
+ export * from "./adapterLazyCompilation.mjs";
4
5
  export * from "./adapterPrecompress.mjs";
5
6
  export * from "./adapterSSR.mjs";
6
7
  export * from "./builderHooks.mjs";
@@ -12,7 +12,7 @@ function aggregateEagerRouteComponentFiles(byEntry) {
12
12
  unresolvedByEntry
13
13
  };
14
14
  }
15
- function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
15
+ function buildRouteEagerLazyCompilationTest(eagerRouteFiles, userTest) {
16
16
  const userTestFn = 'function' == typeof userTest ? userTest : userTest instanceof RegExp ? (m)=>userTest.test(m.resource || '') : ()=>true;
17
17
  return (m)=>{
18
18
  const resource = m.resource;
@@ -21,7 +21,8 @@ function buildSSRLazyCompilationTest(eagerRouteFiles, userTest) {
21
21
  return userTestFn(m);
22
22
  };
23
23
  }
24
- function planSSRLazyCompilation(current, info) {
24
+ const buildSSRLazyCompilationTest = buildRouteEagerLazyCompilationTest;
25
+ function planRouteEagerLazyCompilation(current, info) {
25
26
  if (!current) return {
26
27
  apply: false
27
28
  };
@@ -38,8 +39,9 @@ function planSSRLazyCompilation(current, info) {
38
39
  apply: true,
39
40
  lazyCompilation: {
40
41
  ...base,
41
- test: buildSSRLazyCompilationTest(info.files, userTest)
42
+ test: buildRouteEagerLazyCompilationTest(info.files, userTest)
42
43
  }
43
44
  };
44
45
  }
45
- export { aggregateEagerRouteComponentFiles, buildSSRLazyCompilationTest, collectRouteComponentFiles, normalizeModulePath, planSSRLazyCompilation };
46
+ const planSSRLazyCompilation = planRouteEagerLazyCompilation;
47
+ export { aggregateEagerRouteComponentFiles, buildRouteEagerLazyCompilationTest, buildSSRLazyCompilationTest, collectRouteComponentFiles, normalizeModulePath, planRouteEagerLazyCompilation, planSSRLazyCompilation };
@@ -1,6 +1,19 @@
1
1
  import "node:module";
2
+ let registeredPathHooks;
2
3
  const registerPathsLoader = async ({ appDir, baseUrl, paths })=>{
3
- const { register } = await import("node:module");
4
+ const { register, registerHooks } = await import("node:module");
5
+ if ('function' == typeof registerHooks) {
6
+ const loader = await import("./ts-paths-loader.mjs");
7
+ await loader.initialize({
8
+ appDir,
9
+ baseUrl,
10
+ paths
11
+ });
12
+ registeredPathHooks ??= registerHooks({
13
+ resolve: loader.resolve
14
+ });
15
+ return registeredPathHooks;
16
+ }
4
17
  register('./ts-paths-loader.mjs', import.meta.url, {
5
18
  data: {
6
19
  appDir,
@@ -15,7 +15,15 @@ const deployPresets = {
15
15
  };
16
16
  const getSupportedDeployTargets = ()=>Object.keys(deployPresets);
17
17
  const isDeployTarget = (target)=>Object.prototype.hasOwnProperty.call(deployPresets, target);
18
- const resolveDeployTarget = (modernConfig, envDeployTarget = process.env.MODERNJS_DEPLOY, detectedProvider = provider)=>modernConfig.deploy?.target || envDeployTarget || detectedProvider || 'node';
18
+ const providerDeployTargets = {
19
+ vercel: 'vercel',
20
+ netlify: 'netlify',
21
+ cloudflare: 'cloudflare',
22
+ cloudflare_pages: 'cloudflare',
23
+ cloudflare_workers: 'cloudflare'
24
+ };
25
+ const normalizeDetectedProvider = (value)=>value ? providerDeployTargets[value] : void 0;
26
+ const resolveDeployTarget = (modernConfig, envDeployTarget = process.env.MODERNJS_DEPLOY, detectedProvider = provider)=>modernConfig.deploy?.target || envDeployTarget || normalizeDetectedProvider(detectedProvider) || 'node';
19
27
  async function getDeployPreset(appContext, modernConfig, deployTarget, api) {
20
28
  const { appDirectory, distDirectory, metaName } = appContext;
21
29
  const { useSSR, useAPI, useWebServer } = getProjectUsage(appDirectory, distDirectory, metaName);
@@ -13,6 +13,7 @@ const PUBLIC_ASSETS_DIRECTORY = 'public';
13
13
  const WORKER_BUNDLE_DIRECTORY = 'worker';
14
14
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
15
15
  const BFF_EFFECT_WORKER_ENTRY = `${WORKER_BUNDLE_DIRECTORY}/__modern_bff_effect.js`;
16
+ const EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE = 'Ensure the Effect BFF entry exists at api/effect/index.ts or bff.effect.entry, and import Cloudflare edge handlers from @modern-js/plugin-bff/effect-edge instead of lambda/Hono server helpers.';
16
17
  const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
17
18
  const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
18
19
  const DEFAULT_SECURITY_HEADERS = {
@@ -206,6 +207,7 @@ const readRouteSpec = async (outputDirectory)=>{
206
207
  routes: Array.isArray(routeSpec.routes) ? routeSpec.routes : []
207
208
  };
208
209
  };
210
+ const createMissingEffectBffWorkerError = (outputDirectory, worker)=>new Error(`Cloudflare Effect BFF is configured, but the BFF worker bundle is missing: ${node_path.join(outputDirectory, worker)}. ${EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE}`);
209
211
  const createWorkerManifest = async (outputDirectory, modernConfig)=>{
210
212
  const routeSpec = await readRouteSpec(outputDirectory);
211
213
  const routes = await Promise.all(routeSpec.routes.map(async (route)=>{
@@ -223,6 +225,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
223
225
  const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
224
226
  const isEffectBff = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
225
227
  const effectBffWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
228
+ if (isEffectBff && primaryBffPrefix && !effectBffWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
226
229
  return {
227
230
  version: 1,
228
231
  runtime: {
@@ -316,9 +319,15 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
316
319
  const routeSpecSourcePath = node_path.join(distDirectory, ROUTE_SPEC_FILE);
317
320
  if (await fs.pathExists(routeSpecSourcePath)) await fs.copy(routeSpecSourcePath, routeSpecOutputPath);
318
321
  const workerBundleSourceDirectory = node_path.join(distDirectory, WORKER_BUNDLE_DIRECTORY);
319
- if (await fs.pathExists(workerBundleSourceDirectory)) await fs.copy(workerBundleSourceDirectory, node_path.join(outputDirectory, WORKER_BUNDLE_DIRECTORY), {
320
- filter: (src)=>shouldCopyToWorkerBundle(src, workerBundleSourceDirectory)
321
- });
322
+ const workerBundleOutputDirectory = node_path.join(outputDirectory, WORKER_BUNDLE_DIRECTORY);
323
+ if (await fs.pathExists(workerBundleSourceDirectory)) {
324
+ await fs.copy(workerBundleSourceDirectory, workerBundleOutputDirectory, {
325
+ filter: (src)=>shouldCopyToWorkerBundle(src, workerBundleSourceDirectory)
326
+ });
327
+ await fs.writeJSON(node_path.join(workerBundleOutputDirectory, 'package.json'), {
328
+ type: 'commonjs'
329
+ });
330
+ }
322
331
  await fs.writeJSON(wranglerConfigPath, {
323
332
  $schema: 'node_modules/wrangler/config-schema.json',
324
333
  name: workerName,
@@ -340,7 +349,7 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
340
349
  spaces: 2
341
350
  });
342
351
  await fs.writeJSON(node_path.join(outputDirectory, 'package.json'), {
343
- type: 'commonjs'
352
+ type: 'module'
344
353
  });
345
354
  },
346
355
  async genEntry () {
@@ -24,9 +24,13 @@ async function resolveModernRsbuildConfig(options) {
24
24
  plugins: modernConfig.builderPlugins
25
25
  };
26
26
  const appContext = getAppContext();
27
- const { rsbuildConfig, rsbuildPlugins } = await parseRspackConfig(nonStandardConfig, {
28
- cwd
29
- });
27
+ const builderOptions = {
28
+ cwd,
29
+ ...void 0 === options.disableReactCompiler ? {} : {
30
+ disableReactCompiler: options.disableReactCompiler
31
+ }
32
+ };
33
+ const { rsbuildConfig, rsbuildPlugins } = await parseRspackConfig(nonStandardConfig, builderOptions);
30
34
  const adapterParams = {
31
35
  appContext,
32
36
  normalizedConfig: modernConfig
@@ -1,6 +1,17 @@
1
1
  import "node:module";
2
2
  import { address, fs } from "@modern-js/utils";
3
3
  import path from "path";
4
+ function isSymlinkedNodeModules(appDirectory) {
5
+ try {
6
+ return fs.lstatSync(path.resolve(appDirectory, 'node_modules')).isSymbolicLink();
7
+ } catch {
8
+ return false;
9
+ }
10
+ }
11
+ function getDefaultInternalDirectory(appDirectory, metaName) {
12
+ if (isSymlinkedNodeModules(appDirectory)) return path.resolve(appDirectory, `.${metaName}`);
13
+ return path.resolve(appDirectory, `./node_modules/.${metaName}`);
14
+ }
4
15
  const initAppContext = ({ metaName, appDirectory, runtimeConfigFile, options, tempDir })=>{
5
16
  const { apiDir = 'api', sharedDir = 'shared', bffRuntimeFramework = 'hono' } = options || {};
6
17
  const pkgPath = path.resolve(appDirectory, './package.json');
@@ -14,7 +25,7 @@ const initAppContext = ({ metaName, appDirectory, runtimeConfigFile, options, te
14
25
  lambdaDirectory: path.resolve(appDirectory, apiDir, 'lambda'),
15
26
  sharedDirectory: path.resolve(appDirectory, sharedDir),
16
27
  serverPlugins: [],
17
- internalDirectory: path.resolve(appDirectory, tempDir || `./node_modules/.${metaName}`),
28
+ internalDirectory: tempDir ? path.resolve(appDirectory, tempDir) : getDefaultInternalDirectory(appDirectory, metaName),
18
29
  htmlTemplates: {},
19
30
  serverRoutes: [],
20
31
  entrypoints: [],
@@ -0,0 +1,3 @@
1
+ import type { RsbuildPlugin } from '@rsbuild/core';
2
+ import type { BuilderOptions } from '../types';
3
+ export declare const builderPluginAdapterLazyCompilation: (options: BuilderOptions) => RsbuildPlugin;
@@ -1,5 +1,6 @@
1
1
  export * from './adapterBasic';
2
2
  export * from './adapterHtml';
3
+ export * from './adapterLazyCompilation';
3
4
  export * from './adapterPrecompress';
4
5
  export * from './adapterSSR';
5
6
  export * from './builderHooks';