@octanejs/tanstack-start 0.1.27 → 0.1.29

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 (38) hide show
  1. package/README.md +19 -1
  2. package/THIRD_PARTY_NOTICES.md +6 -5
  3. package/package.json +18 -6
  4. package/src/client-only-server-strip-loader.js +7 -0
  5. package/src/client-only-server-strip.js +34 -25
  6. package/src/internal/README.md +7 -7
  7. package/src/internal/start-plugin-core/rsbuild/import-protection.d.ts +27 -0
  8. package/src/internal/start-plugin-core/rsbuild/import-protection.js +1193 -0
  9. package/src/internal/start-plugin-core/rsbuild/index.d.ts +9 -0
  10. package/src/internal/start-plugin-core/rsbuild/index.js +3 -0
  11. package/src/internal/start-plugin-core/rsbuild/normalized-client-build.d.ts +20 -0
  12. package/src/internal/start-plugin-core/rsbuild/normalized-client-build.js +261 -0
  13. package/src/internal/start-plugin-core/rsbuild/planning.d.ts +56 -0
  14. package/src/internal/start-plugin-core/rsbuild/planning.js +173 -0
  15. package/src/internal/start-plugin-core/rsbuild/plugin.d.ts +7 -0
  16. package/src/internal/start-plugin-core/rsbuild/plugin.js +504 -0
  17. package/src/internal/start-plugin-core/rsbuild/post-build.d.ts +10 -0
  18. package/src/internal/start-plugin-core/rsbuild/post-build.js +59 -0
  19. package/src/internal/start-plugin-core/rsbuild/schema.d.ts +2441 -0
  20. package/src/internal/start-plugin-core/rsbuild/schema.js +28 -0
  21. package/src/internal/start-plugin-core/rsbuild/server-middleware.d.ts +32 -0
  22. package/src/internal/start-plugin-core/rsbuild/server-middleware.js +139 -0
  23. package/src/internal/start-plugin-core/rsbuild/start-compiler-host.d.ts +36 -0
  24. package/src/internal/start-plugin-core/rsbuild/start-compiler-host.js +322 -0
  25. package/src/internal/start-plugin-core/rsbuild/start-compiler-metadata-loader.d.ts +10 -0
  26. package/src/internal/start-plugin-core/rsbuild/start-compiler-metadata-loader.js +12 -0
  27. package/src/internal/start-plugin-core/rsbuild/start-compiler-metadata.d.ts +14 -0
  28. package/src/internal/start-plugin-core/rsbuild/start-compiler-metadata.js +5 -0
  29. package/src/internal/start-plugin-core/rsbuild/start-router-plugin.d.ts +19 -0
  30. package/src/internal/start-plugin-core/rsbuild/start-router-plugin.js +69 -0
  31. package/src/internal/start-plugin-core/rsbuild/swc-rsc.d.ts +17 -0
  32. package/src/internal/start-plugin-core/rsbuild/swc-rsc.js +118 -0
  33. package/src/internal/start-plugin-core/rsbuild/types.d.ts +17 -0
  34. package/src/internal/start-plugin-core/rsbuild/types.js +0 -0
  35. package/src/internal/start-plugin-core/rsbuild/virtual-modules.d.ts +60 -0
  36. package/src/internal/start-plugin-core/rsbuild/virtual-modules.js +359 -0
  37. package/src/plugin-rsbuild.d.ts +25 -0
  38. package/src/plugin-rsbuild.js +79 -0
@@ -0,0 +1,69 @@
1
+ import { routesManifestPlugin } from '../start-router-plugin/generator-plugins/routes-manifest-plugin.js';
2
+ import { prerenderRoutesPlugin } from '../start-router-plugin/generator-plugins/prerender-routes-plugin.js';
3
+ import { buildRouteTreeFileFooterFromConfig } from '../start-router-plugin/route-tree-footer.js';
4
+ import { RSBUILD_ENVIRONMENT_NAMES } from './planning.js';
5
+ import path from 'pathe';
6
+ import { createRouterPluginContext } from '#tanstack-start/router-plugin/context';
7
+ import {
8
+ TanStackRouterCodeSplitterRspack,
9
+ TanStackRouterGeneratorRspack,
10
+ } from '#tanstack-start/router-plugin/rspack';
11
+ //#region src/rsbuild/start-router-plugin.ts
12
+ /**
13
+ * Registers the TanStack Router generator and code-splitter plugins
14
+ * as rspack plugins via `modifyRspackConfig`.
15
+ *
16
+ * The router-plugin package exports rspack-compatible unplugin wrappers:
17
+ * - TanStackRouterGeneratorRspack: file-based route generation
18
+ * - TanStackRouterCodeSplitterRspack: route code splitting
19
+ */
20
+ function registerRouterPlugins(api, opts) {
21
+ const routerPluginContext = createRouterPluginContext();
22
+ api.modifyRspackConfig((config, utils) => {
23
+ const envName = utils.environment.name;
24
+ const { startConfig } = opts.getConfig();
25
+ const routerConfig = startConfig.router;
26
+ if (envName === RSBUILD_ENVIRONMENT_NAMES.client) {
27
+ const generatorPlugin = TanStackRouterGeneratorRspack(
28
+ {
29
+ ...routerConfig,
30
+ target: opts.corePluginOpts.framework,
31
+ routeTreeFileFooter: () => {
32
+ return buildRouteTreeFileFooterFromConfig({
33
+ generatedRouteTreePath: path.resolve(routerConfig.generatedRouteTree),
34
+ getConfig: opts.getConfig,
35
+ corePluginOpts: opts.corePluginOpts,
36
+ });
37
+ },
38
+ plugins: [
39
+ routesManifestPlugin(),
40
+ ...(opts.startPluginOpts.prerender?.enabled === true ? [prerenderRoutesPlugin()] : []),
41
+ ],
42
+ },
43
+ routerPluginContext,
44
+ );
45
+ utils.appendPlugins(generatorPlugin);
46
+ }
47
+ if (
48
+ envName === RSBUILD_ENVIRONMENT_NAMES.client ||
49
+ envName === RSBUILD_ENVIRONMENT_NAMES.server
50
+ ) {
51
+ const isClient = envName === RSBUILD_ENVIRONMENT_NAMES.client;
52
+ const splitterPlugin = TanStackRouterCodeSplitterRspack(
53
+ {
54
+ ...routerConfig,
55
+ target: opts.corePluginOpts.framework,
56
+ codeSplittingOptions: {
57
+ ...routerConfig.codeSplittingOptions,
58
+ deleteNodes: isClient ? ['ssr', 'server', 'headers'] : void 0,
59
+ addHmr: isClient,
60
+ },
61
+ },
62
+ routerPluginContext,
63
+ );
64
+ utils.appendPlugins(splitterPlugin);
65
+ }
66
+ });
67
+ }
68
+ //#endregion
69
+ export { registerRouterPlugins };
@@ -0,0 +1,17 @@
1
+ import { ModifyRspackConfigFn } from '@rsbuild/core';
2
+ type RspackConfig = Parameters<ModifyRspackConfigFn>[0];
3
+ /**
4
+ * Walk the rspack config's module.rules and inject
5
+ * `rspackExperiments.reactServerComponents: true` into SWC loaders.
6
+ *
7
+ * Recurses into `oneOf` arrays because rsbuild nests the main SWC loader
8
+ * inside a `oneOf` rule (e.g. separate branches for asset/source vs
9
+ * javascript/auto). Without recursion, only the mimetype-based fallback
10
+ * SWC rule gets the flag, leaving most .js/.ts files without RSC
11
+ * directive detection.
12
+ */
13
+ export declare function enableSwcReactServerComponents(
14
+ config: RspackConfig,
15
+ scope: 'all' | 'rsc-subtree',
16
+ ): void;
17
+ export {};
@@ -0,0 +1,118 @@
1
+ import { RSBUILD_RSC_LAYERS } from './planning.js';
2
+ //#region src/rsbuild/swc-rsc.ts
3
+ /**
4
+ * Walk the rspack config's module.rules and inject
5
+ * `rspackExperiments.reactServerComponents: true` into SWC loaders.
6
+ *
7
+ * Recurses into `oneOf` arrays because rsbuild nests the main SWC loader
8
+ * inside a `oneOf` rule (e.g. separate branches for asset/source vs
9
+ * javascript/auto). Without recursion, only the mimetype-based fallback
10
+ * SWC rule gets the flag, leaving most .js/.ts files without RSC
11
+ * directive detection.
12
+ */
13
+ function enableSwcReactServerComponents(config, scope) {
14
+ const isRspackRule = (rule) => !!rule && rule !== '...';
15
+ const getRuleLoaders = (rule) => {
16
+ const { use } = rule;
17
+ if (!use) return [];
18
+ return typeof use === 'function' ? [] : Array.isArray(use) ? use : [use];
19
+ };
20
+ const getLoaderPath = (loader) => (typeof loader === 'string' ? loader : loader.loader);
21
+ const cloneLoader = (loader) => {
22
+ if (typeof loader === 'string') return loader;
23
+ const options = loader.options;
24
+ return {
25
+ ...loader,
26
+ ...(options && typeof options === 'object' && !Array.isArray(options)
27
+ ? {
28
+ options: {
29
+ ...options,
30
+ ...(options.rspackExperiments &&
31
+ typeof options.rspackExperiments === 'object' &&
32
+ !Array.isArray(options.rspackExperiments)
33
+ ? { rspackExperiments: { ...options.rspackExperiments } }
34
+ : {}),
35
+ },
36
+ }
37
+ : {}),
38
+ };
39
+ };
40
+ const cloneRuleUse = (use) => {
41
+ if (!use || typeof use === 'function') return use;
42
+ if (Array.isArray(use)) return use.map((loader) => cloneLoader(loader));
43
+ return cloneLoader(use);
44
+ };
45
+ const cloneRspackRule = (rule) => {
46
+ return {
47
+ ...rule,
48
+ use: cloneRuleUse(rule.use),
49
+ resolve: rule.resolve ? { ...rule.resolve } : rule.resolve,
50
+ oneOf: Array.isArray(rule.oneOf)
51
+ ? rule.oneOf.map((childRule) =>
52
+ isRspackRule(childRule) ? cloneRspackRule(childRule) : childRule,
53
+ )
54
+ : rule.oneOf,
55
+ };
56
+ };
57
+ const rootRules = (config.module.rules ??= []).filter(isRspackRule);
58
+ function processRules(rules = rootRules) {
59
+ for (const rule of rules) {
60
+ processRules(Array.isArray(rule.oneOf) ? rule.oneOf.filter(isRspackRule) : []);
61
+ if (
62
+ !getRuleLoaders(rule).some((loader) =>
63
+ Boolean(getLoaderPath(loader)?.includes('swc-loader')),
64
+ )
65
+ )
66
+ continue;
67
+ const enableReactServerComponentsOnRule = (nextRule) => {
68
+ for (const loader of getRuleLoaders(nextRule)) {
69
+ if (typeof loader === 'string') continue;
70
+ const loaderPath = getLoaderPath(loader);
71
+ if (!loaderPath || !loaderPath.includes('swc-loader')) continue;
72
+ const options =
73
+ loader.options && typeof loader.options === 'object'
74
+ ? loader.options
75
+ : (loader.options = {});
76
+ const experiments =
77
+ options.rspackExperiments && typeof options.rspackExperiments === 'object'
78
+ ? options.rspackExperiments
79
+ : (options.rspackExperiments = {});
80
+ const current = experiments.reactServerComponents;
81
+ experiments.reactServerComponents =
82
+ current === true || current == null
83
+ ? {}
84
+ : typeof current === 'object' && current !== null && !Array.isArray(current)
85
+ ? { ...current }
86
+ : {};
87
+ }
88
+ };
89
+ if (scope === 'all') {
90
+ enableReactServerComponentsOnRule(rule);
91
+ continue;
92
+ }
93
+ const originalRule = cloneRspackRule(rule);
94
+ const providerRule = cloneRspackRule(originalRule);
95
+ providerRule.resourceQuery = /(?:^|[?&])tss-serverfn-split(?:&|$)/;
96
+ enableReactServerComponentsOnRule(providerRule);
97
+ const routeSplitRule = cloneRspackRule(originalRule);
98
+ routeSplitRule.resourceQuery = /(?:^|[?&])tsr-split(?:=|&|$)/;
99
+ const routeSplitConditionNames = originalRule.resolve?.conditionNames;
100
+ routeSplitRule.resolve = {
101
+ ...originalRule.resolve,
102
+ conditionNames: Array.isArray(routeSplitConditionNames)
103
+ ? routeSplitConditionNames.includes('...')
104
+ ? [...routeSplitConditionNames]
105
+ : ['...', ...routeSplitConditionNames]
106
+ : ['...'],
107
+ };
108
+ const subtreeRule = cloneRspackRule(originalRule);
109
+ subtreeRule.issuerLayer = RSBUILD_RSC_LAYERS.rsc;
110
+ enableReactServerComponentsOnRule(subtreeRule);
111
+ for (const key of Object.keys(rule)) delete rule[key];
112
+ rule.oneOf = [providerRule, routeSplitRule, subtreeRule, originalRule];
113
+ }
114
+ }
115
+ processRules();
116
+ }
117
+ //#endregion
118
+ export { enableSwcReactServerComponents };
@@ -0,0 +1,17 @@
1
+ import { EnvironmentConfig } from '@rsbuild/core';
2
+ import { TanStackStartCoreOptions } from '../types.js';
3
+ export interface RsbuildEnvironmentOverrides {
4
+ all?: EnvironmentConfig | undefined;
5
+ client?: EnvironmentConfig | undefined;
6
+ server?: EnvironmentConfig | undefined;
7
+ provider?: EnvironmentConfig | undefined;
8
+ }
9
+ export interface RsbuildCoreOptions {
10
+ environments?: RsbuildEnvironmentOverrides | undefined;
11
+ }
12
+ export type TanStackStartRsbuildPluginCoreOptions = TanStackStartCoreOptions & {
13
+ providerEnvironmentName: string;
14
+ ssrIsProvider: boolean;
15
+ rsbuild?: RsbuildCoreOptions | undefined;
16
+ rsc?: boolean | undefined;
17
+ };
@@ -0,0 +1,60 @@
1
+ import { RsbuildPluginAPI, rspack as rspackNamespaceType } from '@rsbuild/core';
2
+ import { GetConfigFn, NormalizedClientBuild, SerializationAdapterConfig } from '../types.js';
3
+ import { ServerFn } from '../start-compiler/types.js';
4
+ import { ScriptFormat } from '@tanstack/router-core';
5
+ type RspackNamespace = typeof rspackNamespaceType;
6
+ type RspackVirtualModulesPlugin = InstanceType<
7
+ RspackNamespace['experiments']['VirtualModulesPlugin']
8
+ >;
9
+ export declare const START_MANIFEST_PLACEHOLDER = '__TSS_START_MANIFEST_PLACEHOLDER__';
10
+ export interface VirtualModuleState {
11
+ /** Call to update manifest content after client build completes */
12
+ updateManifest: (clientBuild: NormalizedClientBuild) => void;
13
+ /** Call to update server fn resolver content after compilation discovers fns */
14
+ updateServerFnResolver: () => void;
15
+ /** Try to write explicit resolver content now; queues if env not ready */
16
+ tryUpdateServerFnResolver: (content: string) => void;
17
+ /** Get the virtual path for a given module ID */
18
+ getVirtualPath: (moduleId: string) => string;
19
+ /** Generate resolver module content from current serverFnsById state.
20
+ * When forProvider=true, generates without isClientReferenced checks (RSC layer). */
21
+ generateCurrentResolverContent: (forProvider?: boolean) => string;
22
+ /** The absolute virtual path of the server fn resolver module */
23
+ serverFnResolverPath: string;
24
+ /** The absolute virtual path of the manifest module */
25
+ manifestPath: string;
26
+ /** Generate manifest module content from a given client build */
27
+ generateManifestContent: (clientBuild: NormalizedClientBuild) => string;
28
+ /** Generate the serialized manifest value literal for asset patching */
29
+ generateManifestValueLiteral: (clientBuild: NormalizedClientBuild) => string;
30
+ /** VirtualModulesPlugin instances keyed by environment name */
31
+ vmPlugins: Record<string, RspackVirtualModulesPlugin>;
32
+ }
33
+ export interface RegisterVirtualModulesOptions {
34
+ root: string;
35
+ getConfig: GetConfigFn;
36
+ serverFnsById: Record<string, ServerFn>;
37
+ providerEnvName: string;
38
+ ssrIsProvider: boolean;
39
+ serializationAdapters: Array<SerializationAdapterConfig> | undefined;
40
+ /**
41
+ * Get the URL at which the rsbuild dev server serves the client entry JS.
42
+ * Called lazily inside modifyRspackConfig when getConfig() is available.
43
+ * Example return: '/assets/js/index.js'
44
+ */
45
+ getDevClientEntryUrl: (publicBase: string) => string;
46
+ /** Whether RSC virtual modules should be registered. */
47
+ rscEnabled?: boolean | undefined;
48
+ scriptFormat: ScriptFormat;
49
+ }
50
+ /**
51
+ * Registers virtual modules for the rsbuild adapter using VirtualModulesPlugin.
52
+ *
53
+ * Creates one VirtualModulesPlugin per environment and registers them via
54
+ * `modifyBundlerChain`. Provides update functions to refresh content dynamically.
55
+ */
56
+ export declare function registerVirtualModules(
57
+ api: RsbuildPluginAPI,
58
+ opts: RegisterVirtualModulesOptions,
59
+ ): VirtualModuleState;
60
+ export {};
@@ -0,0 +1,359 @@
1
+ import { escapeRegExp } from '../utils.js';
2
+ import { generateServerFnResolverModule } from '../start-compiler/server-fn-resolver-module.js';
3
+ import { buildStartManifest } from '../start-manifest-plugin/manifestBuilder.js';
4
+ import { generateSerializationAdaptersModule } from '../serialization-adapters-module.js';
5
+ import { RSBUILD_ENVIRONMENT_NAMES } from './planning.js';
6
+ import { VIRTUAL_MODULES } from '@tanstack/start-server-core';
7
+ //#region src/rsbuild/virtual-modules.ts
8
+ var RSC_RUNTIME_VIRTUAL_ID = 'virtual:tanstack-rsc-runtime';
9
+ var RSC_HMR_VIRTUAL_ID = 'virtual:tanstack-rsc-hmr';
10
+ var RSC_BROWSER_DECODE_VIRTUAL_ID = 'virtual:tanstack-rsc-browser-decode';
11
+ var RSC_SSR_DECODE_VIRTUAL_ID = 'virtual:tanstack-rsc-ssr-decode';
12
+ var START_MANIFEST_PLACEHOLDER = '__TSS_START_MANIFEST_PLACEHOLDER__';
13
+ var DEV_START_MANIFEST_GLOBAL = '__TSS_DEV_START_MANIFEST__';
14
+ /**
15
+ * VirtualModulesPlugin resolves module paths relative to compiler.context.
16
+ * Prefix them under the app root so they are unique and watcher-friendly.
17
+ */
18
+ function virtualPath(root, moduleId) {
19
+ return `${root}/node_modules/.virtual/${moduleId.replace(/[:#]/g, '_')}.js`;
20
+ }
21
+ function getScriptFormatProperty(scriptFormat) {
22
+ return scriptFormat === 'iife' ? ` scriptFormat: 'iife',\n` : '';
23
+ }
24
+ function getEntryScriptAttrs(entryUrl, scriptFormat) {
25
+ return scriptFormat === 'module'
26
+ ? `{ type: 'module', async: true, src: '${entryUrl}' }`
27
+ : `{ async: true, src: '${entryUrl}' }`;
28
+ }
29
+ function generateManifestModuleDev(devClientEntryUrl, scriptFormat) {
30
+ return `const fallbackManifest = {
31
+ ${getScriptFormatProperty(scriptFormat)} routes: {
32
+ __root__: {
33
+ preloads: ['${devClientEntryUrl}'],
34
+ scripts: [{ attrs: ${getEntryScriptAttrs(devClientEntryUrl, scriptFormat)} }],
35
+ },
36
+ },
37
+ }
38
+ export const tsrStartManifest = () => globalThis[${JSON.stringify(DEV_START_MANIFEST_GLOBAL)}] ?? fallbackManifest`;
39
+ }
40
+ function buildStartManifestData(clientBuild, publicBase, inlineCss, scriptFormat) {
41
+ const routeTreeRoutes = globalThis.TSS_ROUTES_MANIFEST;
42
+ return buildStartManifest({
43
+ clientBuild,
44
+ routeTreeRoutes,
45
+ basePath: publicBase,
46
+ inlineCss,
47
+ scriptFormat,
48
+ });
49
+ }
50
+ function serializeStartManifestData(clientBuild, publicBase, inlineCss, scriptFormat) {
51
+ return JSON.stringify(buildStartManifestData(clientBuild, publicBase, inlineCss, scriptFormat));
52
+ }
53
+ function generateManifestModuleBuild(
54
+ clientBuild,
55
+ publicBase,
56
+ _devClientEntryUrl,
57
+ inlineCss,
58
+ scriptFormat,
59
+ ) {
60
+ if (!clientBuild)
61
+ return `const tsrStartManifestData = ${JSON.stringify(START_MANIFEST_PLACEHOLDER)}
62
+ export const tsrStartManifest = () => tsrStartManifestData`;
63
+ return `export const tsrStartManifest = () => (${serializeStartManifestData(clientBuild, publicBase, inlineCss, scriptFormat)})`;
64
+ }
65
+ /**
66
+ * Generate virtual:tanstack-rsc-runtime content.
67
+ * In the RSC layer this re-exports from react-server-dom-rspack/server.
68
+ * In other layers it provides stubs that throw.
69
+ */
70
+ function generateRscRuntimeModule(isRscLayer) {
71
+ if (isRscLayer)
72
+ return `export { renderToReadableStream, createTemporaryReferenceSet, decodeReply, decodeAction, decodeFormState } from 'react-server-dom-rspack/server'
73
+ export function createFromReadableStream() { throw new Error('createFromReadableStream is not available in RSC environment (use SSR or browser decode instead)'); }
74
+ // loadServerAction is provided by the RSC entry, not react-server-dom-rspack
75
+ import { getServerFnById } from '#tanstack-start-server-fn-resolver'
76
+ export const loadServerAction = async (id) => getServerFnById(id, { origin: 'server' })`;
77
+ return `
78
+ export function renderToReadableStream() { throw new Error('renderToReadableStream can only be used in RSC environment'); }
79
+ export function createFromReadableStream() { throw new Error('createFromReadableStream can only be used in RSC environment'); }
80
+ export function createTemporaryReferenceSet() { throw new Error('createTemporaryReferenceSet can only be used in RSC environment'); }
81
+ export function decodeReply() { throw new Error('decodeReply can only be used in RSC environment'); }
82
+ export function loadServerAction() { throw new Error('loadServerAction can only be used in RSC environment'); }
83
+ export function decodeAction() { throw new Error('decodeAction can only be used in RSC environment'); }
84
+ export function decodeFormState() { throw new Error('decodeFormState can only be used in RSC environment'); }
85
+ `;
86
+ }
87
+ /**
88
+ * Generate virtual:tanstack-rsc-hmr content.
89
+ * In the client env during dev, listens for rsc:update WebSocket events
90
+ * and invalidates the router. In all other contexts, exports nothing.
91
+ */
92
+ function generateRscHmrModule(isClientEnv, isDev) {
93
+ if (!isClientEnv || !isDev) return 'export function setupRscHmr() {}';
94
+ return `
95
+ // RSC HMR listener for rsbuild dev server
96
+ // Listens for 'rsc:update' custom events sent via sockWrite
97
+ export function setupRscHmr() {
98
+ let __invalidateQueued = false
99
+
100
+ function __queueInvalidate() {
101
+ if (__invalidateQueued) return
102
+ __invalidateQueued = true
103
+ queueMicrotask(async () => {
104
+ __invalidateQueued = false
105
+ try {
106
+ const router = window.__TSR_ROUTER__
107
+ if (!router) {
108
+ console.warn('[rsc:hmr] No router found on window.__TSR_ROUTER__')
109
+ return
110
+ }
111
+ await router.invalidate()
112
+ } catch (e) {
113
+ console.warn('[rsc:hmr] Failed to invalidate router:', e)
114
+ }
115
+ })
116
+ }
117
+
118
+ if (import.meta.webpackHot) {
119
+ import.meta.webpackHot.on('rsc:update', () => {
120
+ __queueInvalidate()
121
+ })
122
+ }
123
+ }
124
+ `;
125
+ }
126
+ /**
127
+ * Registers virtual modules for the rsbuild adapter using VirtualModulesPlugin.
128
+ *
129
+ * Creates one VirtualModulesPlugin per environment and registers them via
130
+ * `modifyBundlerChain`. Provides update functions to refresh content dynamically.
131
+ */
132
+ function registerVirtualModules(api, opts) {
133
+ const isDev = api.context.action === 'dev';
134
+ const root = opts.root;
135
+ const paths = {
136
+ manifest: virtualPath(root, VIRTUAL_MODULES.startManifest),
137
+ serverFnResolver: virtualPath(root, VIRTUAL_MODULES.serverFnResolver),
138
+ pluginAdapters: virtualPath(root, VIRTUAL_MODULES.pluginAdapters),
139
+ };
140
+ const rscPaths = opts.rscEnabled
141
+ ? {
142
+ rscRuntime: virtualPath(root, RSC_RUNTIME_VIRTUAL_ID),
143
+ rscHmr: virtualPath(root, RSC_HMR_VIRTUAL_ID),
144
+ rscBrowserDecode: virtualPath(root, RSC_BROWSER_DECODE_VIRTUAL_ID),
145
+ rscSsrDecode: virtualPath(root, RSC_SSR_DECODE_VIRTUAL_ID),
146
+ }
147
+ : null;
148
+ const vmPlugins = {};
149
+ const readyVmPlugins = {};
150
+ const pendingWrites = /* @__PURE__ */ new Map();
151
+ let clientBuild;
152
+ const lastResolverContentByEnvironment = {};
153
+ const hasSeparateProviderEnvironment =
154
+ !opts.rscEnabled && opts.providerEnvName !== RSBUILD_ENVIRONMENT_NAMES.server;
155
+ const hasSerializationAdapters = Boolean(opts.serializationAdapters?.length);
156
+ function isProviderEnvironment(environmentName) {
157
+ return environmentName === opts.providerEnvName;
158
+ }
159
+ function needsServerFnResolver(environmentName) {
160
+ return (
161
+ environmentName === RSBUILD_ENVIRONMENT_NAMES.server ||
162
+ (hasSeparateProviderEnvironment && isProviderEnvironment(environmentName))
163
+ );
164
+ }
165
+ function generateResolverContent(environmentName) {
166
+ return generateServerFnResolverModule({
167
+ serverFnsById: opts.serverFnsById,
168
+ includeClientReferencedCheck: !isProviderEnvironment(environmentName),
169
+ useStaticImports: Boolean(opts.rscEnabled && isDev),
170
+ });
171
+ }
172
+ function writeResolverContent(environmentName, content) {
173
+ if (!isDev || content !== lastResolverContentByEnvironment[environmentName]) {
174
+ lastResolverContentByEnvironment[environmentName] = content;
175
+ tryWriteModule(environmentName, paths.serverFnResolver, content);
176
+ }
177
+ }
178
+ function queuePendingWrite(environmentName, filePath, content) {
179
+ let writes = pendingWrites.get(environmentName);
180
+ if (!writes) {
181
+ writes = /* @__PURE__ */ new Map();
182
+ pendingWrites.set(environmentName, writes);
183
+ }
184
+ writes.set(filePath, content);
185
+ }
186
+ function tryWriteModule(environmentName, filePath, content) {
187
+ const vmPlugin = vmPlugins[environmentName];
188
+ if (!vmPlugin || !readyVmPlugins[environmentName]) {
189
+ queuePendingWrite(environmentName, filePath, content);
190
+ return false;
191
+ }
192
+ vmPlugin.writeModule(filePath, content);
193
+ return true;
194
+ }
195
+ function flushPendingWrites(environmentName) {
196
+ if (!readyVmPlugins[environmentName]) return;
197
+ const writes = pendingWrites.get(environmentName);
198
+ if (!writes?.size) return;
199
+ for (const [filePath, content] of writes) {
200
+ if (!tryWriteModule(environmentName, filePath, content)) return;
201
+ writes.delete(filePath);
202
+ }
203
+ if (writes.size === 0) pendingWrites.delete(environmentName);
204
+ }
205
+ function getInitialContent(environmentName) {
206
+ const { resolvedStartConfig, startConfig } = opts.getConfig();
207
+ const isServerEnv = environmentName === RSBUILD_ENVIRONMENT_NAMES.server;
208
+ const isClientEnv = environmentName === RSBUILD_ENVIRONMENT_NAMES.client;
209
+ const content = {};
210
+ if (isServerEnv) {
211
+ const devClientEntryUrl = opts.getDevClientEntryUrl(resolvedStartConfig.basePaths.publicBase);
212
+ content[paths.manifest] = isDev
213
+ ? generateManifestModuleDev(devClientEntryUrl, opts.scriptFormat)
214
+ : generateManifestModuleBuild(
215
+ clientBuild,
216
+ resolvedStartConfig.basePaths.publicBase,
217
+ devClientEntryUrl,
218
+ startConfig.server.build.inlineCss,
219
+ opts.scriptFormat,
220
+ );
221
+ } else content[paths.manifest] = 'export default {}';
222
+ if (needsServerFnResolver(environmentName))
223
+ content[paths.serverFnResolver] = generateResolverContent(environmentName);
224
+ else content[paths.serverFnResolver] = 'export {}';
225
+ if (hasSerializationAdapters)
226
+ content[paths.pluginAdapters] = generateSerializationAdaptersModule({
227
+ adapters: opts.serializationAdapters,
228
+ runtime: environmentName === RSBUILD_ENVIRONMENT_NAMES.client ? 'client' : 'server',
229
+ });
230
+ if (rscPaths) {
231
+ if (isServerEnv) content[rscPaths.rscRuntime] = generateRscRuntimeModule(true);
232
+ else content[rscPaths.rscRuntime] = generateRscRuntimeModule(false);
233
+ content[rscPaths.rscHmr] = generateRscHmrModule(isClientEnv, isDev);
234
+ content[rscPaths.rscBrowserDecode] = isClientEnv
235
+ ? `export * from '@tanstack/react-start/rsbuild/browser-decode'`
236
+ : `export function createFromReadableStream() { throw new Error('RSC browser decode is only available in the client environment') }
237
+ export function createFromFetch() { throw new Error('RSC browser decode is only available in the client environment') }`;
238
+ content[rscPaths.rscSsrDecode] = isServerEnv
239
+ ? `export { setOnClientReference, createFromReadableStream } from '@tanstack/react-start/rsbuild/ssr-decode'`
240
+ : `export function setOnClientReference() {}
241
+ export function createFromReadableStream() { throw new Error('RSC SSR decode is only available in the server environment') }`;
242
+ }
243
+ return content;
244
+ }
245
+ const aliasMap = {
246
+ [VIRTUAL_MODULES.startManifest]: paths.manifest,
247
+ [VIRTUAL_MODULES.serverFnResolver]: paths.serverFnResolver,
248
+ };
249
+ if (hasSerializationAdapters) aliasMap[VIRTUAL_MODULES.pluginAdapters] = paths.pluginAdapters;
250
+ if (rscPaths) {
251
+ aliasMap[RSC_RUNTIME_VIRTUAL_ID] = rscPaths.rscRuntime;
252
+ aliasMap[RSC_HMR_VIRTUAL_ID] = rscPaths.rscHmr;
253
+ aliasMap[RSC_BROWSER_DECODE_VIRTUAL_ID] = rscPaths.rscBrowserDecode;
254
+ aliasMap[RSC_SSR_DECODE_VIRTUAL_ID] = rscPaths.rscSsrDecode;
255
+ }
256
+ api.modifyRspackConfig((config, utils) => {
257
+ const envName = utils.environment.name;
258
+ const initialContent = getInitialContent(envName);
259
+ const VMP = utils.rspack.experiments.VirtualModulesPlugin;
260
+ const vmPlugin = new VMP(initialContent);
261
+ vmPlugins[envName] = vmPlugin;
262
+ readyVmPlugins[envName] = false;
263
+ config.plugins.push(vmPlugin);
264
+ config.plugins.push({
265
+ apply(compiler) {
266
+ compiler.hooks.thisCompilation.tap('TanStackStartFlushPendingVirtualModules', () => {
267
+ readyVmPlugins[envName] = true;
268
+ flushPendingWrites(envName);
269
+ });
270
+ },
271
+ });
272
+ for (const [moduleId, virtualFilePath] of Object.entries(aliasMap)) {
273
+ const NMR = utils.rspack.NormalModuleReplacementPlugin;
274
+ config.plugins.push(new NMR(new RegExp(`^${escapeRegExp(moduleId)}$`), virtualFilePath));
275
+ }
276
+ const resolve = config.resolve;
277
+ const resolveAlias = (resolve.alias ??= {});
278
+ resolveAlias[VIRTUAL_MODULES.serverFnResolver] = paths.serverFnResolver;
279
+ if (hasSerializationAdapters)
280
+ resolveAlias[VIRTUAL_MODULES.pluginAdapters] = paths.pluginAdapters;
281
+ if (rscPaths) {
282
+ resolveAlias[RSC_RUNTIME_VIRTUAL_ID] = rscPaths.rscRuntime;
283
+ resolveAlias[RSC_HMR_VIRTUAL_ID] = rscPaths.rscHmr;
284
+ resolveAlias[RSC_BROWSER_DECODE_VIRTUAL_ID] = rscPaths.rscBrowserDecode;
285
+ resolveAlias[RSC_SSR_DECODE_VIRTUAL_ID] = rscPaths.rscSsrDecode;
286
+ }
287
+ });
288
+ return {
289
+ serverFnResolverPath: paths.serverFnResolver,
290
+ manifestPath: paths.manifest,
291
+ vmPlugins,
292
+ generateCurrentResolverContent(forProvider) {
293
+ return generateResolverContent(
294
+ forProvider ? opts.providerEnvName : RSBUILD_ENVIRONMENT_NAMES.server,
295
+ );
296
+ },
297
+ generateManifestContent(newClientBuild) {
298
+ const { resolvedStartConfig, startConfig } = opts.getConfig();
299
+ const devClientEntryUrl = opts.getDevClientEntryUrl(resolvedStartConfig.basePaths.publicBase);
300
+ return generateManifestModuleBuild(
301
+ newClientBuild,
302
+ resolvedStartConfig.basePaths.publicBase,
303
+ devClientEntryUrl,
304
+ !isDev
305
+ ? startConfig.server.build.inlineCss
306
+ : {
307
+ enabled: false,
308
+ transformAssets: false,
309
+ },
310
+ opts.scriptFormat,
311
+ );
312
+ },
313
+ generateManifestValueLiteral(newClientBuild) {
314
+ const { resolvedStartConfig, startConfig } = opts.getConfig();
315
+ return serializeStartManifestData(
316
+ newClientBuild,
317
+ resolvedStartConfig.basePaths.publicBase,
318
+ !isDev
319
+ ? startConfig.server.build.inlineCss
320
+ : {
321
+ enabled: false,
322
+ transformAssets: false,
323
+ },
324
+ opts.scriptFormat,
325
+ );
326
+ },
327
+ updateManifest(newClientBuild) {
328
+ clientBuild = newClientBuild;
329
+ const { resolvedStartConfig } = opts.getConfig();
330
+ if (isDev)
331
+ globalThis[DEV_START_MANIFEST_GLOBAL] = buildStartManifestData(
332
+ clientBuild,
333
+ resolvedStartConfig.basePaths.publicBase,
334
+ {
335
+ enabled: false,
336
+ transformAssets: false,
337
+ },
338
+ opts.scriptFormat,
339
+ );
340
+ },
341
+ updateServerFnResolver() {
342
+ const updateEnvironment = (environmentName) => {
343
+ if (!needsServerFnResolver(environmentName)) return;
344
+ writeResolverContent(environmentName, generateResolverContent(environmentName));
345
+ };
346
+ updateEnvironment(RSBUILD_ENVIRONMENT_NAMES.server);
347
+ if (hasSeparateProviderEnvironment) updateEnvironment(opts.providerEnvName);
348
+ },
349
+ tryUpdateServerFnResolver(content) {
350
+ lastResolverContentByEnvironment[RSBUILD_ENVIRONMENT_NAMES.server] = content;
351
+ tryWriteModule(RSBUILD_ENVIRONMENT_NAMES.server, paths.serverFnResolver, content);
352
+ },
353
+ getVirtualPath(moduleId) {
354
+ return virtualPath(root, moduleId);
355
+ },
356
+ };
357
+ }
358
+ //#endregion
359
+ export { START_MANIFEST_PLACEHOLDER, registerVirtualModules };
@@ -0,0 +1,25 @@
1
+ import type { OctaneRspackPluginOptions } from '@octanejs/rspack-plugin';
2
+ import type { RsbuildPlugin } from '@rsbuild/core';
3
+ import type { TanStackStartRsbuildInputConfig } from '#tanstack-start/plugin-core/rsbuild';
4
+
5
+ export type OctaneCompilerOptions = Omit<
6
+ OctaneRspackPluginOptions,
7
+ 'environment' | 'root' | 'transpile'
8
+ > & {
9
+ /**
10
+ * Enables profiling during `rsbuild dev` and compiles it out of production
11
+ * builds. An explicit `profile` option takes precedence.
12
+ */
13
+ devtools?: boolean;
14
+ };
15
+
16
+ export type TanStackOctaneStartRsbuildInputConfig = Omit<
17
+ TanStackStartRsbuildInputConfig,
18
+ 'octane'
19
+ > & {
20
+ octane?: OctaneCompilerOptions;
21
+ };
22
+
23
+ export declare function tanstackStart(
24
+ options?: TanStackOctaneStartRsbuildInputConfig,
25
+ ): RsbuildPlugin;