@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
@@ -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 };
@@ -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);
@@ -4,6 +4,8 @@ import { fs } from "@modern-js/utils";
4
4
  import { readTemplate } from "../utils/index.mjs";
5
5
  const WORKER_ENTRY = 'server/index.mjs';
6
6
  const WORKER_MANIFEST = 'server/modern-worker-manifest.json';
7
+ const WRANGLER_CONFIG_FILE = 'wrangler.json';
8
+ const OUTPUT_PACKAGE_FILE = 'package.json';
7
9
  const ASSETS_BINDING = 'ASSETS';
8
10
  const ROUTE_SPEC_FILE = 'route.json';
9
11
  const ROUTE_SPEC_OUTPUT = `server/${ROUTE_SPEC_FILE}`;
@@ -12,10 +14,28 @@ const ROUTE_MANIFEST_FILE = 'routes-manifest.json';
12
14
  const PUBLIC_ASSETS_DIRECTORY = 'public';
13
15
  const WORKER_BUNDLE_DIRECTORY = 'worker';
14
16
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
17
+ const SERVER_OUTPUT_DIRECTORY = 'server';
18
+ const DEFAULT_SERVER_ONLY_PUBLIC_ASSET_EXCLUDES = [
19
+ 'api',
20
+ 'shared'
21
+ ];
15
22
  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.';
23
+ 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.';
17
24
  const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
18
25
  const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
26
+ const REQUIRED_COMPATIBILITY_FLAGS = [
27
+ 'nodejs_compat',
28
+ 'global_fetch_strictly_public'
29
+ ];
30
+ const RESERVED_ARTIFACT_DESTINATION_FILES = new Set([
31
+ WRANGLER_CONFIG_FILE,
32
+ OUTPUT_PACKAGE_FILE
33
+ ]);
34
+ const RESERVED_ARTIFACT_DESTINATION_DIRECTORIES = new Set([
35
+ PUBLIC_ASSETS_DIRECTORY,
36
+ SERVER_OUTPUT_DIRECTORY,
37
+ WORKER_BUNDLE_DIRECTORY
38
+ ]);
19
39
  const DEFAULT_SECURITY_HEADERS = {
20
40
  referrerPolicy: 'strict-origin-when-cross-origin',
21
41
  contentTypeOptions: 'nosniff',
@@ -104,6 +124,13 @@ const getConfiguredWorkerName = (appDirectory, modernConfig)=>{
104
124
  const configuredName = modernConfig.deploy?.worker?.name?.trim();
105
125
  return configuredName || getWorkerName(appDirectory);
106
126
  };
127
+ const normalizeRelativePath = (value, label, scope = 'app output')=>{
128
+ if ('string' != typeof value) throw new Error(`${label} must be a relative path inside the ${scope}.`);
129
+ const normalized = value.trim().replace(/\\/g, '/').replace(/^\.\/+/u, '').replace(/\/+$/u, '');
130
+ const segments = normalized.split('/');
131
+ if (!normalized || node_path.isAbsolute(normalized) || '.' === normalized || segments.includes('..')) throw new Error(`${label} must be a relative path inside the ${scope}.`);
132
+ return normalized;
133
+ };
107
134
  const normalizeDirectiveValues = (value)=>{
108
135
  const values = Array.isArray(value) ? value : [
109
136
  value
@@ -196,6 +223,66 @@ const createCloudflareWorkerSecurityPolicy = (modernConfig)=>{
196
223
  reason: security?.reason
197
224
  };
198
225
  };
226
+ const isJsonRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
227
+ const getConfiguredWrangler = (modernConfig)=>{
228
+ const wrangler = modernConfig.deploy?.worker?.wrangler;
229
+ if (void 0 === wrangler) return {};
230
+ if (!isJsonRecord(wrangler)) throw new Error('deploy.worker.wrangler must be a JSON object.');
231
+ return wrangler;
232
+ };
233
+ const createWranglerCompatibilityFlags = (configuredFlags)=>{
234
+ if (void 0 === configuredFlags) return [
235
+ ...REQUIRED_COMPATIBILITY_FLAGS
236
+ ];
237
+ if (!Array.isArray(configuredFlags) || configuredFlags.some((flag)=>'string' != typeof flag)) throw new Error('deploy.worker.wrangler.compatibility_flags must be an array of strings.');
238
+ return [
239
+ ...new Set([
240
+ ...configuredFlags,
241
+ ...REQUIRED_COMPATIBILITY_FLAGS
242
+ ])
243
+ ];
244
+ };
245
+ const createWranglerAssetsConfig = (configuredAssets)=>{
246
+ if (void 0 !== configuredAssets && !isJsonRecord(configuredAssets)) throw new Error('deploy.worker.wrangler.assets must be an object.');
247
+ return {
248
+ ...isJsonRecord(configuredAssets) ? configuredAssets : {},
249
+ directory: `./${PUBLIC_ASSETS_DIRECTORY}`,
250
+ binding: ASSETS_BINDING,
251
+ run_worker_first: true
252
+ };
253
+ };
254
+ const createWranglerConfig = (appDirectory, modernConfig)=>{
255
+ const wrangler = getConfiguredWrangler(modernConfig);
256
+ return {
257
+ $schema: 'node_modules/wrangler/config-schema.json',
258
+ name: getConfiguredWorkerName(appDirectory, modernConfig),
259
+ compatibility_date: getCompatibilityDate(modernConfig),
260
+ ...wrangler,
261
+ main: WORKER_ENTRY,
262
+ compatibility_flags: createWranglerCompatibilityFlags(wrangler.compatibility_flags),
263
+ assets: createWranglerAssetsConfig(wrangler.assets)
264
+ };
265
+ };
266
+ const normalizeCloudflareArtifact = (artifact, index)=>{
267
+ const from = normalizeRelativePath(artifact.from, `deploy.worker.artifacts[${index}].from`, 'app root');
268
+ const to = normalizeRelativePath(artifact.to, `deploy.worker.artifacts[${index}].to`, 'Cloudflare output');
269
+ const [topLevelDestination] = to.split('/');
270
+ const reservedDestination = RESERVED_ARTIFACT_DESTINATION_FILES.has(to) ? to : topLevelDestination;
271
+ 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)}.`);
272
+ return {
273
+ from,
274
+ to,
275
+ index
276
+ };
277
+ };
278
+ const getCloudflareArtifacts = (modernConfig)=>(modernConfig.deploy?.worker?.artifacts ?? []).map(normalizeCloudflareArtifact);
279
+ const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)=>{
280
+ for (const artifact of artifacts){
281
+ const sourcePath = node_path.join(appDirectory, artifact.from);
282
+ if (!await fs.pathExists(sourcePath)) throw new Error(`deploy.worker.artifacts[${artifact.index}].from does not exist: ${artifact.from}`);
283
+ await fs.copy(sourcePath, node_path.join(outputDirectory, artifact.to));
284
+ }
285
+ };
199
286
  const readRouteSpec = async (outputDirectory)=>{
200
287
  const routeSpecPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
201
288
  if (!await fs.pathExists(routeSpecPath)) return {
@@ -207,7 +294,7 @@ const readRouteSpec = async (outputDirectory)=>{
207
294
  routes: Array.isArray(routeSpec.routes) ? routeSpec.routes : []
208
295
  };
209
296
  };
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}`);
297
+ 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}`);
211
298
  const createWorkerManifest = async (outputDirectory, modernConfig)=>{
212
299
  const routeSpec = await readRouteSpec(outputDirectory);
213
300
  const routes = await Promise.all(routeSpec.routes.map(async (route)=>{
@@ -223,9 +310,9 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
223
310
  }));
224
311
  const bffPrefix = modernConfig.bff?.prefix;
225
312
  const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
226
- const isEffectBff = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
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);
313
+ const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
314
+ const effectApiWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
315
+ if (isEffectApi && primaryBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
229
316
  return {
230
317
  version: 1,
231
318
  runtime: {
@@ -254,7 +341,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
254
341
  routeManifest: ROUTE_MANIFEST_FILE
255
342
  },
256
343
  security: createCloudflareWorkerSecurityPolicy(modernConfig),
257
- bff: isEffectBff && primaryBffPrefix && effectBffWorkerExists ? {
344
+ bff: isEffectApi && primaryBffPrefix && effectApiWorkerExists ? {
258
345
  runtimeFramework: 'effect',
259
346
  prefix: primaryBffPrefix,
260
347
  worker: BFF_EFFECT_WORKER_ENTRY
@@ -277,12 +364,22 @@ const createWorkerModuleLoaders = (manifest)=>{
277
364
  ].map(([worker, loader])=>`${JSON.stringify(worker)}: ${loader}`);
278
365
  return `{\n${loaders.join(',\n')}\n}`;
279
366
  };
280
- const shouldCopyToPublicAssets = (src, distDirectory)=>{
367
+ const getPublicAssetExcludes = (appDirectory, modernConfig)=>[
368
+ ...DEFAULT_SERVER_ONLY_PUBLIC_ASSET_EXCLUDES.filter((directory)=>{
369
+ try {
370
+ return fs.statSync(node_path.join(appDirectory, directory)).isDirectory();
371
+ } catch {
372
+ return false;
373
+ }
374
+ }),
375
+ ...modernConfig.deploy?.worker?.publicAssetExcludes ?? []
376
+ ].map((entry)=>normalizeRelativePath(entry, 'deploy.worker.publicAssetExcludes'));
377
+ const shouldCopyToPublicAssets = (src, distDirectory, publicAssetExcludes)=>{
281
378
  const relativePath = node_path.relative(distDirectory, src);
282
379
  if (!relativePath) return true;
283
380
  const normalizedRelativePath = relativePath.replace(/\\/g, '/');
284
381
  const [topLevelDirectory] = normalizedRelativePath.split('/');
285
- return normalizedRelativePath !== ROUTE_SPEC_FILE && topLevelDirectory !== WORKER_BUNDLE_DIRECTORY && topLevelDirectory !== SERVER_BUNDLE_DIRECTORY;
382
+ return normalizedRelativePath !== ROUTE_SPEC_FILE && topLevelDirectory !== WORKER_BUNDLE_DIRECTORY && topLevelDirectory !== SERVER_BUNDLE_DIRECTORY && !publicAssetExcludes.some((exclude)=>normalizedRelativePath === exclude || normalizedRelativePath.startsWith(`${exclude}/`));
286
383
  };
287
384
  const shouldCopyToWorkerBundle = (src, workerBundleDirectory)=>{
288
385
  const relativePath = node_path.relative(workerBundleDirectory, src);
@@ -304,15 +401,16 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
304
401
  const workerEntryPath = node_path.join(outputDirectory, WORKER_ENTRY);
305
402
  const workerManifestPath = node_path.join(outputDirectory, WORKER_MANIFEST);
306
403
  const routeSpecOutputPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
307
- const wranglerConfigPath = node_path.join(outputDirectory, 'wrangler.json');
308
- const workerName = getConfiguredWorkerName(appDirectory, modernConfig);
404
+ const wranglerConfigPath = node_path.join(outputDirectory, WRANGLER_CONFIG_FILE);
405
+ const cloudflareArtifacts = getCloudflareArtifacts(modernConfig);
406
+ const publicAssetExcludes = getPublicAssetExcludes(appDirectory, modernConfig);
309
407
  return {
310
408
  async prepare () {
311
409
  await fs.remove(outputDirectory);
312
410
  },
313
411
  async writeOutput () {
314
412
  await fs.copy(distDirectory, publicDirectory, {
315
- filter: (src)=>shouldCopyToPublicAssets(src, distDirectory)
413
+ filter: (src)=>shouldCopyToPublicAssets(src, distDirectory, publicAssetExcludes)
316
414
  });
317
415
  await fs.ensureDir(node_path.dirname(workerEntryPath));
318
416
  await fs.ensureDir(node_path.dirname(workerManifestPath));
@@ -328,27 +426,14 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
328
426
  type: 'commonjs'
329
427
  });
330
428
  }
331
- await fs.writeJSON(wranglerConfigPath, {
332
- $schema: 'node_modules/wrangler/config-schema.json',
333
- name: workerName,
334
- main: WORKER_ENTRY,
335
- compatibility_date: getCompatibilityDate(modernConfig),
336
- compatibility_flags: [
337
- 'nodejs_compat',
338
- 'global_fetch_strictly_public'
339
- ],
340
- assets: {
341
- directory: `./${PUBLIC_ASSETS_DIRECTORY}`,
342
- binding: ASSETS_BINDING,
343
- run_worker_first: true
344
- }
345
- }, {
429
+ await copyCloudflareArtifacts(appDirectory, outputDirectory, cloudflareArtifacts);
430
+ await fs.writeJSON(wranglerConfigPath, createWranglerConfig(appDirectory, modernConfig), {
346
431
  spaces: 2
347
432
  });
348
433
  await fs.writeJSON(workerManifestPath, await createWorkerManifest(outputDirectory, modernConfig), {
349
434
  spaces: 2
350
435
  });
351
- await fs.writeJSON(node_path.join(outputDirectory, 'package.json'), {
436
+ await fs.writeJSON(node_path.join(outputDirectory, OUTPUT_PACKAGE_FILE), {
352
437
  type: 'module'
353
438
  });
354
439
  },
@@ -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());
@@ -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';
@@ -15,8 +15,8 @@ export type EagerRouteComponentInfo = {
15
15
  * Aggregate the per-entry route component data (collected by the router plugin
16
16
  * during route generation and threaded in as
17
17
  * `BuilderOptions.eagerRouteComponentFilesByEntry`) into the flat shape
18
- * {@link planSSRLazyCompilation} expects: one Set of all route files plus the
19
- * unresolved specifiers keyed by entry.
18
+ * {@link planRouteEagerLazyCompilation} expects: one Set of all route files
19
+ * plus the unresolved specifiers keyed by entry.
20
20
  */
21
21
  export declare function aggregateEagerRouteComponentFiles(byEntry: EagerRouteComponentFilesByEntry | undefined): EagerRouteComponentInfo;
22
22
  /**
@@ -25,7 +25,8 @@ export declare function aggregateEagerRouteComponentFiles(byEntry: EagerRouteCom
25
25
  * render time), while delegating all other modules to the user's `test`
26
26
  * (defaulting to lazy when the user did not provide one).
27
27
  */
28
- export declare function buildSSRLazyCompilationTest(eagerRouteFiles: Set<string>, userTest?: LazyCompilationTest): LazyCompilationTestFn;
28
+ export declare function buildRouteEagerLazyCompilationTest(eagerRouteFiles: Set<string>, userTest?: LazyCompilationTest): LazyCompilationTestFn;
29
+ export declare const buildSSRLazyCompilationTest: typeof buildRouteEagerLazyCompilationTest;
29
30
  export type SSRLazyPlan = {
30
31
  apply: false;
31
32
  unresolvedByEntry?: Map<string, string[]>;
@@ -34,10 +35,11 @@ export type SSRLazyPlan = {
34
35
  lazyCompilation: Record<string, unknown>;
35
36
  };
36
37
  /**
37
- * Decide whether to apply the route-eager lazy compilation for an SSR project.
38
+ * Decide whether to apply route-eager lazy compilation for a project.
38
39
  * Checks unresolved route components FIRST: if any exist we cannot guarantee
39
40
  * they are eager, so we skip the optimization (and surface them so the caller
40
41
  * can warn) rather than silently leaving a route lazy. `current` is the
41
42
  * existing `dev.lazyCompilation` value (lazy must be enabled for this to apply).
42
43
  */
43
- export declare function planSSRLazyCompilation(current: unknown, info: EagerRouteComponentInfo): SSRLazyPlan;
44
+ export declare function planRouteEagerLazyCompilation(current: unknown, info: EagerRouteComponentInfo): SSRLazyPlan;
45
+ export declare const planSSRLazyCompilation: typeof planRouteEagerLazyCompilation;
@@ -8,9 +8,9 @@ export type BuilderOptions = {
8
8
  * Route component files collected from the FINAL file-system routes (after
9
9
  * all `modifyFileSystemRoutes` consumers ran), keyed by entry name. Populated
10
10
  * by the router plugin during route generation and threaded in here (read
11
- * FRESH from the app context AFTER `generateEntryCode` runs) so the SSR
12
- * builder plugin can force route component chunks eager under lazy
13
- * compilation. Explicit param instead of a direct `_internalContext` read.
11
+ * FRESH from the app context AFTER `generateEntryCode` runs) so builder
12
+ * plugins can force route component chunks eager under lazy compilation.
13
+ * Explicit param instead of a direct `_internalContext` read.
14
14
  */
15
15
  eagerRouteComponentFilesByEntry?: EagerRouteComponentFilesByEntry;
16
16
  };
@@ -3,6 +3,7 @@ type ResolveModernRsbuildConfigOptions = {
3
3
  command: string;
4
4
  configPath?: string;
5
5
  cwd?: string;
6
+ disableReactCompiler?: boolean;
6
7
  metaName?: string;
7
8
  modifyModernConfig?: (config: AppUserConfig) => AppUserConfig | Promise<AppUserConfig>;
8
9
  };
@@ -1,5 +1,19 @@
1
1
  export type DeployTarget = 'node' | 'vercel' | 'netlify' | 'ghPages' | 'cloudflare';
2
2
  export type CloudflareWorkerSecurityCspMode = 'enforce' | 'report-only' | 'off';
3
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
4
+ [key: string]: JsonValue;
5
+ };
6
+ export interface CloudflareWorkerArtifactConfig {
7
+ /**
8
+ * Source file or directory, relative to the app root, to copy into
9
+ * `.output` after framework output has been staged.
10
+ */
11
+ from: string;
12
+ /**
13
+ * Destination path relative to `.output`.
14
+ */
15
+ to: string;
16
+ }
3
17
  export interface CloudflareWorkerSecurityCspConfig {
4
18
  mode?: CloudflareWorkerSecurityCspMode;
5
19
  directives?: Record<string, string[] | string | false>;
@@ -1,5 +1,5 @@
1
- import type { CloudflareWorkerSecurityConfig, DeployTarget } from './cloudflareDeploy';
2
- export type { CloudflareWorkerSecurityConfig, CloudflareWorkerSecurityCorsConfig, CloudflareWorkerSecurityCspConfig, CloudflareWorkerSecurityCspMode, CloudflareWorkerSecurityNoindexConfig, DeployTarget, } from './cloudflareDeploy';
1
+ import type { CloudflareWorkerArtifactConfig, CloudflareWorkerSecurityConfig, DeployTarget, JsonValue } from './cloudflareDeploy';
2
+ export type { CloudflareWorkerArtifactConfig, CloudflareWorkerSecurityConfig, CloudflareWorkerSecurityCorsConfig, CloudflareWorkerSecurityCspConfig, CloudflareWorkerSecurityCspMode, CloudflareWorkerSecurityNoindexConfig, DeployTarget, JsonValue, } from './cloudflareDeploy';
3
3
  export interface MicroFrontend {
4
4
  /**
5
5
  * Specifies whether to enable the HTML entry.
@@ -36,5 +36,24 @@ export interface DeployUserConfig {
36
36
  compatibilityDate?: string;
37
37
  ssr?: boolean;
38
38
  security?: CloudflareWorkerSecurityConfig;
39
+ /**
40
+ * Raw Wrangler-compatible config merged into `.output/wrangler.json`.
41
+ * Framework-owned worker invariants still win for `main`, the assets
42
+ * binding/directory/run mode, and required compatibility flags.
43
+ */
44
+ wrangler?: Record<string, JsonValue>;
45
+ /**
46
+ * Additional app-root files or directories to stage under `.output`.
47
+ * Use this for provider resources such as migrations or generated config.
48
+ */
49
+ artifacts?: CloudflareWorkerArtifactConfig[];
50
+ /**
51
+ * Dist output paths that must not be copied into Cloudflare public assets.
52
+ * Entries are slash-normalized path prefixes relative to the app dist root.
53
+ * Top-level `api` and `shared` dist directories are excluded when matching
54
+ * source directories exist in the app root because they conventionally
55
+ * contain server-only implementation code.
56
+ */
57
+ publicAssetExcludes?: string[];
39
58
  };
40
59
  }