@bleedingdev/modern-js-app-tools 3.4.0-ultramodern.1 → 3.4.0-ultramodern.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/builder/generator/getBuilderEnvironments.js +49 -5
- package/dist/cjs/builder/generator/index.js +1 -0
- package/dist/cjs/builder/shared/builderPlugins/adapterLazyCompilation.js +74 -0
- package/dist/cjs/builder/shared/builderPlugins/adapterSSR.js +3 -28
- package/dist/cjs/builder/shared/builderPlugins/index.js +13 -6
- package/dist/cjs/builder/shared/lazyCompilation.js +11 -3
- package/dist/cjs/esm/register-esm.js +14 -1
- package/dist/cjs/esm/register-esm.mjs +14 -1
- package/dist/cjs/plugins/deploy/index.js +9 -1
- package/dist/cjs/plugins/deploy/platforms/cloudflare.js +13 -4
- package/dist/cjs/rsbuild.js +7 -3
- package/dist/cjs/utils/initAppContext.js +12 -1
- package/dist/esm/builder/generator/getBuilderEnvironments.mjs +49 -5
- package/dist/esm/builder/generator/index.mjs +2 -1
- package/dist/esm/builder/shared/builderPlugins/adapterLazyCompilation.mjs +36 -0
- package/dist/esm/builder/shared/builderPlugins/adapterSSR.mjs +4 -29
- package/dist/esm/builder/shared/builderPlugins/index.mjs +1 -0
- package/dist/esm/builder/shared/lazyCompilation.mjs +6 -4
- package/dist/esm/esm/register-esm.mjs +14 -1
- package/dist/esm/plugins/deploy/index.mjs +9 -1
- package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +13 -4
- package/dist/esm/rsbuild.mjs +7 -3
- package/dist/esm/utils/initAppContext.mjs +12 -1
- package/dist/esm-node/builder/generator/getBuilderEnvironments.mjs +49 -5
- package/dist/esm-node/builder/generator/index.mjs +2 -1
- package/dist/esm-node/builder/shared/builderPlugins/adapterLazyCompilation.mjs +37 -0
- package/dist/esm-node/builder/shared/builderPlugins/adapterSSR.mjs +4 -29
- package/dist/esm-node/builder/shared/builderPlugins/index.mjs +1 -0
- package/dist/esm-node/builder/shared/lazyCompilation.mjs +6 -4
- package/dist/esm-node/esm/register-esm.mjs +14 -1
- package/dist/esm-node/plugins/deploy/index.mjs +9 -1
- package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +13 -4
- package/dist/esm-node/rsbuild.mjs +7 -3
- package/dist/esm-node/utils/initAppContext.mjs +12 -1
- package/dist/types/builder/shared/builderPlugins/adapterLazyCompilation.d.ts +3 -0
- package/dist/types/builder/shared/builderPlugins/index.d.ts +1 -0
- package/dist/types/builder/shared/lazyCompilation.d.ts +7 -5
- package/dist/types/builder/shared/types.d.ts +3 -3
- package/dist/types/esm/register-esm.d.mts +1 -1
- package/dist/types/rsbuild.d.ts +1 -0
- 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
|
|
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
|
|
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
|
|
@@ -11,7 +11,7 @@ function aggregateEagerRouteComponentFiles(byEntry) {
|
|
|
11
11
|
unresolvedByEntry
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
function
|
|
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
|
-
|
|
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:
|
|
41
|
+
test: buildRouteEagerLazyCompilationTest(info.files, userTest)
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
|
-
|
|
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
|
|
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
|
-
|
|
319
|
-
|
|
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: '
|
|
351
|
+
type: 'module'
|
|
343
352
|
});
|
|
344
353
|
},
|
|
345
354
|
async genEntry () {
|
package/dist/esm/rsbuild.mjs
CHANGED
|
@@ -23,9 +23,13 @@ async function resolveModernRsbuildConfig(options) {
|
|
|
23
23
|
plugins: modernConfig.builderPlugins
|
|
24
24
|
};
|
|
25
25
|
const appContext = getAppContext();
|
|
26
|
-
const
|
|
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
|
|
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
|
-
'.
|
|
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
|
|
96
|
-
return
|
|
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
|
|
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
|
|
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
|
|
@@ -12,7 +12,7 @@ function aggregateEagerRouteComponentFiles(byEntry) {
|
|
|
12
12
|
unresolvedByEntry
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
function
|
|
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
|
-
|
|
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:
|
|
42
|
+
test: buildRouteEagerLazyCompilationTest(info.files, userTest)
|
|
42
43
|
}
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
|
-
|
|
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
|
|
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
|
-
|
|
320
|
-
|
|
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: '
|
|
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
|
|
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
|
|
28
|
+
internalDirectory: tempDir ? path.resolve(appDirectory, tempDir) : getDefaultInternalDirectory(appDirectory, metaName),
|
|
18
29
|
htmlTemplates: {},
|
|
19
30
|
serverRoutes: [],
|
|
20
31
|
entrypoints: [],
|