@bleedingdev/modern-js-app-tools 3.4.0-ultramodern.2 → 3.4.0-ultramodern.20

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 +45 -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/plugins/deploy/index.js +9 -1
  8. package/dist/cjs/plugins/deploy/platforms/cloudflare.js +112 -27
  9. package/dist/cjs/plugins/deploy/platforms/templates/cloudflare-entry.mjs +14 -1
  10. package/dist/cjs/rsbuild.js +7 -3
  11. package/dist/cjs/utils/initAppContext.js +12 -1
  12. package/dist/esm/builder/generator/getBuilderEnvironments.mjs +45 -5
  13. package/dist/esm/builder/generator/index.mjs +2 -1
  14. package/dist/esm/builder/shared/builderPlugins/adapterLazyCompilation.mjs +36 -0
  15. package/dist/esm/builder/shared/builderPlugins/adapterSSR.mjs +4 -29
  16. package/dist/esm/builder/shared/builderPlugins/index.mjs +1 -0
  17. package/dist/esm/builder/shared/lazyCompilation.mjs +6 -4
  18. package/dist/esm/plugins/deploy/index.mjs +9 -1
  19. package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +112 -27
  20. package/dist/esm/plugins/deploy/platforms/templates/cloudflare-entry.mjs +14 -1
  21. package/dist/esm/rsbuild.mjs +7 -3
  22. package/dist/esm/utils/initAppContext.mjs +12 -1
  23. package/dist/esm-node/builder/generator/getBuilderEnvironments.mjs +45 -5
  24. package/dist/esm-node/builder/generator/index.mjs +2 -1
  25. package/dist/esm-node/builder/shared/builderPlugins/adapterLazyCompilation.mjs +37 -0
  26. package/dist/esm-node/builder/shared/builderPlugins/adapterSSR.mjs +4 -29
  27. package/dist/esm-node/builder/shared/builderPlugins/index.mjs +1 -0
  28. package/dist/esm-node/builder/shared/lazyCompilation.mjs +6 -4
  29. package/dist/esm-node/plugins/deploy/index.mjs +9 -1
  30. package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +112 -27
  31. package/dist/esm-node/plugins/deploy/platforms/templates/cloudflare-entry.mjs +14 -1
  32. package/dist/esm-node/rsbuild.mjs +7 -3
  33. package/dist/esm-node/utils/initAppContext.mjs +12 -1
  34. package/dist/types/builder/shared/builderPlugins/adapterLazyCompilation.d.ts +3 -0
  35. package/dist/types/builder/shared/builderPlugins/index.d.ts +1 -0
  36. package/dist/types/builder/shared/lazyCompilation.d.ts +7 -5
  37. package/dist/types/builder/shared/types.d.ts +3 -3
  38. package/dist/types/rsbuild.d.ts +1 -0
  39. package/dist/types/types/config/cloudflareDeploy.d.ts +14 -0
  40. package/dist/types/types/config/deploy.d.ts +21 -2
  41. package/package.json +19 -27
@@ -94,13 +94,53 @@ function getCloudflareWorkerNodeExternals() {
94
94
  function getEffectBffEntry(normalizedConfig, appContext) {
95
95
  if (!normalizedConfig.bff || 'hono' === normalizedConfig.bff.runtimeFramework) return;
96
96
  const configuredEntry = normalizedConfig.bff.effect?.entry;
97
- const entryWithoutOrWithExtension = configuredEntry ? node_path.isAbsolute(configuredEntry) ? configuredEntry : node_path.resolve(appContext.appDirectory, configuredEntry) : node_path.resolve(appContext.apiDirectory, 'effect', 'index');
98
- return resolveJsOrTsEntry(entryWithoutOrWithExtension);
97
+ if (configuredEntry) {
98
+ const entryWithoutOrWithExtension = node_path.isAbsolute(configuredEntry) ? configuredEntry : node_path.resolve(appContext.appDirectory, configuredEntry);
99
+ return resolveJsOrTsEntry(entryWithoutOrWithExtension);
100
+ }
101
+ return resolveJsOrTsEntry(node_path.resolve(appContext.apiDirectory, 'index'));
99
102
  }
100
103
  function isCloudflareWorkerDeploy(normalizedConfig) {
101
104
  return normalizedConfig.deploy?.target === 'cloudflare' || 'cloudflare' === process.env.MODERNJS_DEPLOY;
102
105
  }
106
+ function getConsumingReactRuntimeAliases(appContext) {
107
+ const resolvePaths = [
108
+ appContext.appDirectory,
109
+ process.cwd()
110
+ ];
111
+ return {
112
+ react$: resolvePackageFile('react', 'index.js', resolvePaths),
113
+ 'react/jsx-runtime$': resolvePackageFile('react', 'jsx-runtime.js', resolvePaths),
114
+ 'react/jsx-dev-runtime$': resolvePackageFile('react', 'jsx-dev-runtime.js', resolvePaths),
115
+ 'react/compiler-runtime$': resolvePackageFile('react', 'compiler-runtime.js', resolvePaths)
116
+ };
117
+ }
118
+ function setResolvedAliases(alias, aliases) {
119
+ for (const [name, value] of Object.entries(aliases))setAliasIfPresent(alias, name, value);
120
+ }
121
+ function appendBundlerChain(config, handler) {
122
+ const bundlerChain = config.tools?.bundlerChain;
123
+ config.tools = {
124
+ ...config.tools,
125
+ bundlerChain: bundlerChain ? Array.isArray(bundlerChain) ? [
126
+ ...bundlerChain,
127
+ handler
128
+ ] : [
129
+ bundlerChain,
130
+ handler
131
+ ] : handler
132
+ };
133
+ }
134
+ function applySourceBuildReactRuntimeAliases(normalizedConfig, appContext, tempBuilderConfig) {
135
+ if (!normalizedConfig.experiments?.sourceBuild) return;
136
+ const aliases = getConsumingReactRuntimeAliases(appContext);
137
+ if (!Object.values(aliases).some(Boolean)) return;
138
+ appendBundlerChain(tempBuilderConfig, (chain)=>{
139
+ setResolvedAliases(chain.resolve.alias, aliases);
140
+ });
141
+ }
103
142
  function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig) {
143
+ applySourceBuildReactRuntimeAliases(normalizedConfig, appContext, tempBuilderConfig);
104
144
  const entries = {};
105
145
  const { entrypoints = [], checkedEntries } = appContext;
106
146
  for (const { entryName, internalEntry, entry } of entrypoints){
@@ -147,7 +187,7 @@ function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig)
147
187
  const useWorkerTarget = isServiceWorker(normalizedConfig);
148
188
  if (useWorkerTarget) {
149
189
  const useCloudflareModuleWorker = isCloudflareWorkerDeploy(normalizedConfig);
150
- const effectBffEntry = useCloudflareModuleWorker ? getEffectBffEntry(normalizedConfig, appContext) : void 0;
190
+ const effectApiEntry = useCloudflareModuleWorker ? getEffectBffEntry(normalizedConfig, appContext) : void 0;
151
191
  const tanstackRouterSsrServerFile = useCloudflareModuleWorker ? resolvePackageFile('@tanstack/router-core', 'dist/esm/ssr/ssr-server.js', [
152
192
  appContext.appDirectory,
153
193
  process.cwd()
@@ -189,10 +229,10 @@ function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig)
189
229
  const fsPromisesWorkerFile = useCloudflareModuleWorker ? getCloudflareWorkerCompatFile('cloudflare-worker-fs-promises.mjs') : void 0;
190
230
  const pathWorkerFile = useCloudflareModuleWorker ? getCloudflareWorkerCompatFile('cloudflare-worker-path.mjs') : void 0;
191
231
  const baseWorkerEntries = useCloudflareModuleWorker ? cloudflareWorkerServerEntries : serverEntries;
192
- const workerEntries = effectBffEntry ? {
232
+ const workerEntries = effectApiEntry ? {
193
233
  ...baseWorkerEntries,
194
234
  [BFF_EFFECT_WORKER_ENTRY_NAME]: [
195
- `${effectBffEntry}?${BFF_EFFECT_WORKER_RUNTIME_QUERY}`
235
+ `${effectApiEntry}?${BFF_EFFECT_WORKER_RUNTIME_QUERY}`
196
236
  ]
197
237
  } : baseWorkerEntries;
198
238
  environments[SERVICE_WORKER_ENVIRONMENT_NAME] = {
@@ -1,6 +1,6 @@
1
1
  import { createBuilder } from "@modern-js/builder";
2
2
  import { mergeRsbuildConfig } from "@rsbuild/core";
3
- import { builderPluginAdapterBasic, builderPluginAdapterHooks, builderPluginAdapterHtml, builderPluginAdapterPrecompress, builderPluginAdapterSSR } from "../shared/builderPlugins/index.mjs";
3
+ import { builderPluginAdapterBasic, builderPluginAdapterHooks, builderPluginAdapterHtml, builderPluginAdapterLazyCompilation, builderPluginAdapterPrecompress, builderPluginAdapterSSR } from "../shared/builderPlugins/index.mjs";
4
4
  import { builderPluginAdapterCopy } from "./adapterCopy.mjs";
5
5
  import { createBuilderProviderConfig } from "./createBuilderProviderConfig.mjs";
6
6
  import { getBuilderEnvironments } from "./getBuilderEnvironments.mjs";
@@ -38,6 +38,7 @@ async function generateBuilder(options, bundlerType) {
38
38
  async function applyBuilderPlugins(builder, options) {
39
39
  builder.addPlugins([
40
40
  builderPluginAdapterBasic(options),
41
+ builderPluginAdapterLazyCompilation(options),
41
42
  builderPluginAdapterSSR(options),
42
43
  builderPluginAdapterHtml(options),
43
44
  builderPluginAdapterPrecompress(options),
@@ -0,0 +1,36 @@
1
+ import { isUseRsc, logger } from "@modern-js/utils";
2
+ import { aggregateEagerRouteComponentFiles, planRouteEagerLazyCompilation } from "../lazyCompilation.mjs";
3
+ const builderPluginAdapterLazyCompilation = (options)=>({
4
+ name: 'builder-plugin-adapter-modern-lazy-compilation',
5
+ setup (api) {
6
+ api.modifyRsbuildConfig((config)=>{
7
+ const lazyCompilation = getRouteEagerLazyCompilation(options, config);
8
+ if (void 0 === lazyCompilation) return config;
9
+ return {
10
+ ...config,
11
+ dev: {
12
+ ...config.dev,
13
+ lazyCompilation
14
+ }
15
+ };
16
+ });
17
+ }
18
+ });
19
+ function getRouteEagerLazyCompilation(options, config) {
20
+ const current = config.dev?.lazyCompilation;
21
+ if (!current || isUseRsc(options.normalizedConfig)) return;
22
+ const plan = planRouteEagerLazyCompilation(current, aggregateEagerRouteComponentFiles(options.eagerRouteComponentFilesByEntry));
23
+ if (!plan.apply) {
24
+ if (plan.unresolvedByEntry) warnUnresolvedRouteComponents(options.appContext.appDirectory, plan.unresolvedByEntry);
25
+ return;
26
+ }
27
+ return plan.lazyCompilation;
28
+ }
29
+ const warnedLazyApps = new Set();
30
+ function warnUnresolvedRouteComponents(appDirectory, unresolvedByEntry) {
31
+ if (warnedLazyApps.has(appDirectory)) return;
32
+ warnedLazyApps.add(appDirectory);
33
+ const detail = Array.from(unresolvedByEntry).map(([entry, components])=>`${entry}: ${components.join(', ')}`).join('; ');
34
+ 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.`);
35
+ }
36
+ export { builderPluginAdapterLazyCompilation };
@@ -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 };
@@ -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);
@@ -3,6 +3,8 @@ import { fs } from "@modern-js/utils";
3
3
  import { readTemplate } from "../utils/index.mjs";
4
4
  const WORKER_ENTRY = 'server/index.mjs';
5
5
  const WORKER_MANIFEST = 'server/modern-worker-manifest.json';
6
+ const WRANGLER_CONFIG_FILE = 'wrangler.json';
7
+ const OUTPUT_PACKAGE_FILE = 'package.json';
6
8
  const ASSETS_BINDING = 'ASSETS';
7
9
  const ROUTE_SPEC_FILE = 'route.json';
8
10
  const ROUTE_SPEC_OUTPUT = `server/${ROUTE_SPEC_FILE}`;
@@ -11,10 +13,28 @@ const ROUTE_MANIFEST_FILE = 'routes-manifest.json';
11
13
  const PUBLIC_ASSETS_DIRECTORY = 'public';
12
14
  const WORKER_BUNDLE_DIRECTORY = 'worker';
13
15
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
16
+ const SERVER_OUTPUT_DIRECTORY = 'server';
17
+ const DEFAULT_SERVER_ONLY_PUBLIC_ASSET_EXCLUDES = [
18
+ 'api',
19
+ 'shared'
20
+ ];
14
21
  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.';
22
+ const EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE = 'Ensure the Effect API entry exists at api/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
23
  const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
17
24
  const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
25
+ const REQUIRED_COMPATIBILITY_FLAGS = [
26
+ 'nodejs_compat',
27
+ 'global_fetch_strictly_public'
28
+ ];
29
+ const RESERVED_ARTIFACT_DESTINATION_FILES = new Set([
30
+ WRANGLER_CONFIG_FILE,
31
+ OUTPUT_PACKAGE_FILE
32
+ ]);
33
+ const RESERVED_ARTIFACT_DESTINATION_DIRECTORIES = new Set([
34
+ PUBLIC_ASSETS_DIRECTORY,
35
+ SERVER_OUTPUT_DIRECTORY,
36
+ WORKER_BUNDLE_DIRECTORY
37
+ ]);
18
38
  const DEFAULT_SECURITY_HEADERS = {
19
39
  referrerPolicy: 'strict-origin-when-cross-origin',
20
40
  contentTypeOptions: 'nosniff',
@@ -103,6 +123,13 @@ const getConfiguredWorkerName = (appDirectory, modernConfig)=>{
103
123
  const configuredName = modernConfig.deploy?.worker?.name?.trim();
104
124
  return configuredName || getWorkerName(appDirectory);
105
125
  };
126
+ const normalizeRelativePath = (value, label, scope = 'app output')=>{
127
+ if ('string' != typeof value) throw new Error(`${label} must be a relative path inside the ${scope}.`);
128
+ const normalized = value.trim().replace(/\\/g, '/').replace(/^\.\/+/u, '').replace(/\/+$/u, '');
129
+ const segments = normalized.split('/');
130
+ if (!normalized || node_path.isAbsolute(normalized) || '.' === normalized || segments.includes('..')) throw new Error(`${label} must be a relative path inside the ${scope}.`);
131
+ return normalized;
132
+ };
106
133
  const normalizeDirectiveValues = (value)=>{
107
134
  const values = Array.isArray(value) ? value : [
108
135
  value
@@ -195,6 +222,66 @@ const createCloudflareWorkerSecurityPolicy = (modernConfig)=>{
195
222
  reason: security?.reason
196
223
  };
197
224
  };
225
+ const isJsonRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
226
+ const getConfiguredWrangler = (modernConfig)=>{
227
+ const wrangler = modernConfig.deploy?.worker?.wrangler;
228
+ if (void 0 === wrangler) return {};
229
+ if (!isJsonRecord(wrangler)) throw new Error('deploy.worker.wrangler must be a JSON object.');
230
+ return wrangler;
231
+ };
232
+ const createWranglerCompatibilityFlags = (configuredFlags)=>{
233
+ if (void 0 === configuredFlags) return [
234
+ ...REQUIRED_COMPATIBILITY_FLAGS
235
+ ];
236
+ if (!Array.isArray(configuredFlags) || configuredFlags.some((flag)=>'string' != typeof flag)) throw new Error('deploy.worker.wrangler.compatibility_flags must be an array of strings.');
237
+ return [
238
+ ...new Set([
239
+ ...configuredFlags,
240
+ ...REQUIRED_COMPATIBILITY_FLAGS
241
+ ])
242
+ ];
243
+ };
244
+ const createWranglerAssetsConfig = (configuredAssets)=>{
245
+ if (void 0 !== configuredAssets && !isJsonRecord(configuredAssets)) throw new Error('deploy.worker.wrangler.assets must be an object.');
246
+ return {
247
+ ...isJsonRecord(configuredAssets) ? configuredAssets : {},
248
+ directory: `./${PUBLIC_ASSETS_DIRECTORY}`,
249
+ binding: ASSETS_BINDING,
250
+ run_worker_first: true
251
+ };
252
+ };
253
+ const createWranglerConfig = (appDirectory, modernConfig)=>{
254
+ const wrangler = getConfiguredWrangler(modernConfig);
255
+ return {
256
+ $schema: 'node_modules/wrangler/config-schema.json',
257
+ name: getConfiguredWorkerName(appDirectory, modernConfig),
258
+ compatibility_date: getCompatibilityDate(modernConfig),
259
+ ...wrangler,
260
+ main: WORKER_ENTRY,
261
+ compatibility_flags: createWranglerCompatibilityFlags(wrangler.compatibility_flags),
262
+ assets: createWranglerAssetsConfig(wrangler.assets)
263
+ };
264
+ };
265
+ const normalizeCloudflareArtifact = (artifact, index)=>{
266
+ const from = normalizeRelativePath(artifact.from, `deploy.worker.artifacts[${index}].from`, 'app root');
267
+ const to = normalizeRelativePath(artifact.to, `deploy.worker.artifacts[${index}].to`, 'Cloudflare output');
268
+ const [topLevelDestination] = to.split('/');
269
+ const reservedDestination = RESERVED_ARTIFACT_DESTINATION_FILES.has(to) ? to : topLevelDestination;
270
+ if (RESERVED_ARTIFACT_DESTINATION_FILES.has(to) || RESERVED_ARTIFACT_DESTINATION_DIRECTORIES.has(topLevelDestination)) throw new Error(`deploy.worker.artifacts[${index}].to must not target generated Cloudflare output path ${JSON.stringify(reservedDestination)}.`);
271
+ return {
272
+ from,
273
+ to,
274
+ index
275
+ };
276
+ };
277
+ const getCloudflareArtifacts = (modernConfig)=>(modernConfig.deploy?.worker?.artifacts ?? []).map(normalizeCloudflareArtifact);
278
+ const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)=>{
279
+ for (const artifact of artifacts){
280
+ const sourcePath = node_path.join(appDirectory, artifact.from);
281
+ if (!await fs.pathExists(sourcePath)) throw new Error(`deploy.worker.artifacts[${artifact.index}].from does not exist: ${artifact.from}`);
282
+ await fs.copy(sourcePath, node_path.join(outputDirectory, artifact.to));
283
+ }
284
+ };
198
285
  const readRouteSpec = async (outputDirectory)=>{
199
286
  const routeSpecPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
200
287
  if (!await fs.pathExists(routeSpecPath)) return {
@@ -206,7 +293,7 @@ const readRouteSpec = async (outputDirectory)=>{
206
293
  routes: Array.isArray(routeSpec.routes) ? routeSpec.routes : []
207
294
  };
208
295
  };
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}`);
296
+ const createMissingEffectBffWorkerError = (outputDirectory, worker)=>new Error(`Cloudflare Effect API runtime is configured, but the BFF worker bundle is missing: ${node_path.join(outputDirectory, worker)}. ${EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE}`);
210
297
  const createWorkerManifest = async (outputDirectory, modernConfig)=>{
211
298
  const routeSpec = await readRouteSpec(outputDirectory);
212
299
  const routes = await Promise.all(routeSpec.routes.map(async (route)=>{
@@ -222,9 +309,9 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
222
309
  }));
223
310
  const bffPrefix = modernConfig.bff?.prefix;
224
311
  const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
225
- const isEffectBff = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
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);
312
+ const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
313
+ const effectApiWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
314
+ if (isEffectApi && primaryBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
228
315
  return {
229
316
  version: 1,
230
317
  runtime: {
@@ -253,7 +340,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
253
340
  routeManifest: ROUTE_MANIFEST_FILE
254
341
  },
255
342
  security: createCloudflareWorkerSecurityPolicy(modernConfig),
256
- bff: isEffectBff && primaryBffPrefix && effectBffWorkerExists ? {
343
+ bff: isEffectApi && primaryBffPrefix && effectApiWorkerExists ? {
257
344
  runtimeFramework: 'effect',
258
345
  prefix: primaryBffPrefix,
259
346
  worker: BFF_EFFECT_WORKER_ENTRY
@@ -276,12 +363,22 @@ const createWorkerModuleLoaders = (manifest)=>{
276
363
  ].map(([worker, loader])=>`${JSON.stringify(worker)}: ${loader}`);
277
364
  return `{\n${loaders.join(',\n')}\n}`;
278
365
  };
279
- const shouldCopyToPublicAssets = (src, distDirectory)=>{
366
+ const getPublicAssetExcludes = (appDirectory, modernConfig)=>[
367
+ ...DEFAULT_SERVER_ONLY_PUBLIC_ASSET_EXCLUDES.filter((directory)=>{
368
+ try {
369
+ return fs.statSync(node_path.join(appDirectory, directory)).isDirectory();
370
+ } catch {
371
+ return false;
372
+ }
373
+ }),
374
+ ...modernConfig.deploy?.worker?.publicAssetExcludes ?? []
375
+ ].map((entry)=>normalizeRelativePath(entry, 'deploy.worker.publicAssetExcludes'));
376
+ const shouldCopyToPublicAssets = (src, distDirectory, publicAssetExcludes)=>{
280
377
  const relativePath = node_path.relative(distDirectory, src);
281
378
  if (!relativePath) return true;
282
379
  const normalizedRelativePath = relativePath.replace(/\\/g, '/');
283
380
  const [topLevelDirectory] = normalizedRelativePath.split('/');
284
- return normalizedRelativePath !== ROUTE_SPEC_FILE && topLevelDirectory !== WORKER_BUNDLE_DIRECTORY && topLevelDirectory !== SERVER_BUNDLE_DIRECTORY;
381
+ return normalizedRelativePath !== ROUTE_SPEC_FILE && topLevelDirectory !== WORKER_BUNDLE_DIRECTORY && topLevelDirectory !== SERVER_BUNDLE_DIRECTORY && !publicAssetExcludes.some((exclude)=>normalizedRelativePath === exclude || normalizedRelativePath.startsWith(`${exclude}/`));
285
382
  };
286
383
  const shouldCopyToWorkerBundle = (src, workerBundleDirectory)=>{
287
384
  const relativePath = node_path.relative(workerBundleDirectory, src);
@@ -303,15 +400,16 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
303
400
  const workerEntryPath = node_path.join(outputDirectory, WORKER_ENTRY);
304
401
  const workerManifestPath = node_path.join(outputDirectory, WORKER_MANIFEST);
305
402
  const routeSpecOutputPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
306
- const wranglerConfigPath = node_path.join(outputDirectory, 'wrangler.json');
307
- const workerName = getConfiguredWorkerName(appDirectory, modernConfig);
403
+ const wranglerConfigPath = node_path.join(outputDirectory, WRANGLER_CONFIG_FILE);
404
+ const cloudflareArtifacts = getCloudflareArtifacts(modernConfig);
405
+ const publicAssetExcludes = getPublicAssetExcludes(appDirectory, modernConfig);
308
406
  return {
309
407
  async prepare () {
310
408
  await fs.remove(outputDirectory);
311
409
  },
312
410
  async writeOutput () {
313
411
  await fs.copy(distDirectory, publicDirectory, {
314
- filter: (src)=>shouldCopyToPublicAssets(src, distDirectory)
412
+ filter: (src)=>shouldCopyToPublicAssets(src, distDirectory, publicAssetExcludes)
315
413
  });
316
414
  await fs.ensureDir(node_path.dirname(workerEntryPath));
317
415
  await fs.ensureDir(node_path.dirname(workerManifestPath));
@@ -327,27 +425,14 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
327
425
  type: 'commonjs'
328
426
  });
329
427
  }
330
- await fs.writeJSON(wranglerConfigPath, {
331
- $schema: 'node_modules/wrangler/config-schema.json',
332
- name: workerName,
333
- main: WORKER_ENTRY,
334
- compatibility_date: getCompatibilityDate(modernConfig),
335
- compatibility_flags: [
336
- 'nodejs_compat',
337
- 'global_fetch_strictly_public'
338
- ],
339
- assets: {
340
- directory: `./${PUBLIC_ASSETS_DIRECTORY}`,
341
- binding: ASSETS_BINDING,
342
- run_worker_first: true
343
- }
344
- }, {
428
+ await copyCloudflareArtifacts(appDirectory, outputDirectory, cloudflareArtifacts);
429
+ await fs.writeJSON(wranglerConfigPath, createWranglerConfig(appDirectory, modernConfig), {
345
430
  spaces: 2
346
431
  });
347
432
  await fs.writeJSON(workerManifestPath, await createWorkerManifest(outputDirectory, modernConfig), {
348
433
  spaces: 2
349
434
  });
350
- await fs.writeJSON(node_path.join(outputDirectory, 'package.json'), {
435
+ await fs.writeJSON(node_path.join(outputDirectory, OUTPUT_PACKAGE_FILE), {
351
436
  type: 'module'
352
437
  });
353
438
  },
@@ -312,6 +312,19 @@ function getRemoteManifestUrl(remote, request) {
312
312
  if ('string' != typeof entry || 0 === entry.length) return;
313
313
  return new URL(entry, request.url).toString();
314
314
  }
315
+ function ensureTrailingSlash(value) {
316
+ return value.endsWith('/') ? value : `${value}/`;
317
+ }
318
+ function getRemoteAssetBase(remoteManifest, manifestUrl) {
319
+ const publicPath = 'string' == typeof remoteManifest?.metaData?.publicPath ? remoteManifest.metaData.publicPath : void 0;
320
+ const fallbackBase = new URL('.', manifestUrl).toString();
321
+ if (!publicPath || 'auto' === publicPath) return fallbackBase;
322
+ try {
323
+ return new URL(ensureTrailingSlash(publicPath), manifestUrl).toString();
324
+ } catch {
325
+ return fallbackBase;
326
+ }
327
+ }
315
328
  async function fetchRemoteJson(jsonUrl) {
316
329
  if (!remoteJsonPromises.has(jsonUrl)) remoteJsonPromises.set(jsonUrl, fetch(jsonUrl).then((response)=>{
317
330
  if (!response.ok) {
@@ -374,7 +387,7 @@ async function collectRenderedRemoteCssHrefs(html, request, env) {
374
387
  if (!manifestUrl) return;
375
388
  const remoteManifest = await fetchRemoteJson(manifestUrl);
376
389
  const remoteExpose = findRemoteExpose(remoteManifest, expose);
377
- const publicPath = 'string' == typeof remoteManifest?.metaData?.publicPath ? remoteManifest.metaData.publicPath : manifestUrl;
390
+ const publicPath = getRemoteAssetBase(remoteManifest, manifestUrl);
378
391
  const remoteRouteManifest = await fetchRemoteJson(new URL('routes-manifest.json', publicPath).toString());
379
392
  for (const asset of collectCssAssetEntries(remoteExpose?.assets))hrefs.add(new URL(asset, publicPath).toString());
380
393
  for (const asset of collectRouteManifestCssAssets(remoteRouteManifest))hrefs.add(new URL(asset, publicPath).toString());
@@ -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: [],
@@ -99,13 +99,53 @@ function getCloudflareWorkerNodeExternals() {
99
99
  function getEffectBffEntry(normalizedConfig, appContext) {
100
100
  if (!normalizedConfig.bff || 'hono' === normalizedConfig.bff.runtimeFramework) return;
101
101
  const configuredEntry = normalizedConfig.bff.effect?.entry;
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);
102
+ if (configuredEntry) {
103
+ const entryWithoutOrWithExtension = node_path.isAbsolute(configuredEntry) ? configuredEntry : node_path.resolve(appContext.appDirectory, configuredEntry);
104
+ return resolveJsOrTsEntry(entryWithoutOrWithExtension);
105
+ }
106
+ return resolveJsOrTsEntry(node_path.resolve(appContext.apiDirectory, 'index'));
104
107
  }
105
108
  function isCloudflareWorkerDeploy(normalizedConfig) {
106
109
  return normalizedConfig.deploy?.target === 'cloudflare' || 'cloudflare' === process.env.MODERNJS_DEPLOY;
107
110
  }
111
+ function getConsumingReactRuntimeAliases(appContext) {
112
+ const resolvePaths = [
113
+ appContext.appDirectory,
114
+ process.cwd()
115
+ ];
116
+ return {
117
+ react$: resolvePackageFile('react', 'index.js', resolvePaths),
118
+ 'react/jsx-runtime$': resolvePackageFile('react', 'jsx-runtime.js', resolvePaths),
119
+ 'react/jsx-dev-runtime$': resolvePackageFile('react', 'jsx-dev-runtime.js', resolvePaths),
120
+ 'react/compiler-runtime$': resolvePackageFile('react', 'compiler-runtime.js', resolvePaths)
121
+ };
122
+ }
123
+ function setResolvedAliases(alias, aliases) {
124
+ for (const [name, value] of Object.entries(aliases))setAliasIfPresent(alias, name, value);
125
+ }
126
+ function appendBundlerChain(config, handler) {
127
+ const bundlerChain = config.tools?.bundlerChain;
128
+ config.tools = {
129
+ ...config.tools,
130
+ bundlerChain: bundlerChain ? Array.isArray(bundlerChain) ? [
131
+ ...bundlerChain,
132
+ handler
133
+ ] : [
134
+ bundlerChain,
135
+ handler
136
+ ] : handler
137
+ };
138
+ }
139
+ function applySourceBuildReactRuntimeAliases(normalizedConfig, appContext, tempBuilderConfig) {
140
+ if (!normalizedConfig.experiments?.sourceBuild) return;
141
+ const aliases = getConsumingReactRuntimeAliases(appContext);
142
+ if (!Object.values(aliases).some(Boolean)) return;
143
+ appendBundlerChain(tempBuilderConfig, (chain)=>{
144
+ setResolvedAliases(chain.resolve.alias, aliases);
145
+ });
146
+ }
108
147
  function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig) {
148
+ applySourceBuildReactRuntimeAliases(normalizedConfig, appContext, tempBuilderConfig);
109
149
  const entries = {};
110
150
  const { entrypoints = [], checkedEntries } = appContext;
111
151
  for (const { entryName, internalEntry, entry } of entrypoints){
@@ -152,7 +192,7 @@ function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig)
152
192
  const useWorkerTarget = isServiceWorker(normalizedConfig);
153
193
  if (useWorkerTarget) {
154
194
  const useCloudflareModuleWorker = isCloudflareWorkerDeploy(normalizedConfig);
155
- const effectBffEntry = useCloudflareModuleWorker ? getEffectBffEntry(normalizedConfig, appContext) : void 0;
195
+ const effectApiEntry = useCloudflareModuleWorker ? getEffectBffEntry(normalizedConfig, appContext) : void 0;
156
196
  const tanstackRouterSsrServerFile = useCloudflareModuleWorker ? resolvePackageFile('@tanstack/router-core', 'dist/esm/ssr/ssr-server.js', [
157
197
  appContext.appDirectory,
158
198
  process.cwd()
@@ -194,10 +234,10 @@ function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig)
194
234
  const fsPromisesWorkerFile = useCloudflareModuleWorker ? getCloudflareWorkerCompatFile('cloudflare-worker-fs-promises.mjs') : void 0;
195
235
  const pathWorkerFile = useCloudflareModuleWorker ? getCloudflareWorkerCompatFile('cloudflare-worker-path.mjs') : void 0;
196
236
  const baseWorkerEntries = useCloudflareModuleWorker ? cloudflareWorkerServerEntries : serverEntries;
197
- const workerEntries = effectBffEntry ? {
237
+ const workerEntries = effectApiEntry ? {
198
238
  ...baseWorkerEntries,
199
239
  [BFF_EFFECT_WORKER_ENTRY_NAME]: [
200
- `${effectBffEntry}?${BFF_EFFECT_WORKER_RUNTIME_QUERY}`
240
+ `${effectApiEntry}?${BFF_EFFECT_WORKER_RUNTIME_QUERY}`
201
241
  ]
202
242
  } : baseWorkerEntries;
203
243
  environments[SERVICE_WORKER_ENVIRONMENT_NAME] = {
@@ -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),