@octanejs/rsbuild-plugin 0.1.0 → 0.1.4
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/README.md +11 -8
- package/package.json +6 -5
- package/src/client-assets-plugin.js +44 -0
- package/src/index.js +44 -6
- package/types/index.d.ts +16 -3
package/README.md
CHANGED
|
@@ -70,17 +70,20 @@ and browser targets cannot be mixed in the same array.
|
|
|
70
70
|
Options are declarative and cache-stable:
|
|
71
71
|
|
|
72
72
|
- `hmr` controls browser component handoff;
|
|
73
|
-
- `
|
|
73
|
+
- `profile` enables component profiling in the browser environment;
|
|
74
74
|
- `exclude` skips path fragments in the plain `.ts`/`.js` hook-slot pass; and
|
|
75
75
|
- `clientEnvironment` / `serverEnvironment` rename the generated environments.
|
|
76
76
|
|
|
77
77
|
App mode currently serves from the root path and uses Rsbuild's default asset
|
|
78
78
|
prefix. Keep `server.base` at `/` and `output.assetPrefix` at `auto` or `/`; for
|
|
79
79
|
a subpath deployment, rewrite that prefix to the app root in the hosting proxy.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
When `octane.config.ts` or one of its imported helpers changes, `rsbuild dev`
|
|
81
|
+
restarts the dev server and applies the complete config atomically. This is
|
|
82
|
+
required for `compiler.renderers`, because renderer selection is part of each
|
|
83
|
+
Rspack compiler's cache and loader identity. Source-module edits continue to
|
|
84
|
+
use the normal HMR or browser-reload path.
|
|
85
|
+
|
|
86
|
+
The package forwards normalized `compiler.renderers` registry, filename-rule,
|
|
87
|
+
and renderer-boundary metadata through the same Rspack compiler path used by
|
|
88
|
+
Vite and direct compilation. This enables the experimental universal client
|
|
89
|
+
target; a concrete Lynx runtime and cross-thread transport remain future work.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/rsbuild-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -48,19 +48,20 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@octanejs/app-core": "0.0.
|
|
52
|
-
"@octanejs/rspack-plugin": "0.1.
|
|
51
|
+
"@octanejs/app-core": "0.0.5",
|
|
52
|
+
"@octanejs/rspack-plugin": "0.1.4"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@rsbuild/core": "^2.0.0",
|
|
56
|
-
"octane": "0.1.
|
|
56
|
+
"octane": "0.1.9"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@rsbuild/core": "^2.1.5",
|
|
60
60
|
"@rspack/core": "^2.1.3",
|
|
61
61
|
"@types/node": "^24.3.0",
|
|
62
|
+
"playwright": "^1.61.0",
|
|
62
63
|
"type-fest": "^5.6.0",
|
|
63
64
|
"vitest": "^4.1.9",
|
|
64
|
-
"octane": "0.1.
|
|
65
|
+
"octane": "0.1.9"
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -3,11 +3,23 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { rspack } from '@rsbuild/core';
|
|
6
|
+
import { HYDRATE_QUERY_PARAM } from 'octane/compiler/bundler';
|
|
6
7
|
|
|
7
8
|
import { resolveProjectModule } from './project.js';
|
|
8
9
|
|
|
9
10
|
const PLUGIN_NAME = 'OctaneClientAssetsPlugin';
|
|
10
11
|
|
|
12
|
+
/** @param {unknown} request */
|
|
13
|
+
function isDeferredHydrationRequest(request) {
|
|
14
|
+
if (typeof request !== 'string' || request.length === 0) return false;
|
|
15
|
+
const resource = request.slice(request.lastIndexOf('!') + 1);
|
|
16
|
+
const queryStart = resource.indexOf('?');
|
|
17
|
+
if (queryStart === -1) return false;
|
|
18
|
+
const hashStart = resource.indexOf('#', queryStart);
|
|
19
|
+
const query = resource.slice(queryStart + 1, hashStart === -1 ? undefined : hashStart);
|
|
20
|
+
return new URLSearchParams(query).has(HYDRATE_QUERY_PARAM);
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
/** @param {string} file */
|
|
12
24
|
function canonicalResource(file) {
|
|
13
25
|
if (!file) return '';
|
|
@@ -76,6 +88,37 @@ function collectCss(files, chunk) {
|
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Collect CSS from compiler-generated deferred hydration groups and every
|
|
93
|
+
* async descendant below them. Their JavaScript deliberately stays out of the
|
|
94
|
+
* route asset entry so the production server does not modulepreload it.
|
|
95
|
+
*
|
|
96
|
+
* @param {Set<string>} files
|
|
97
|
+
* @param {any} group
|
|
98
|
+
* @param {boolean} [insideDeferredHydration]
|
|
99
|
+
* @param {{ eager: Set<any>, deferred: Set<any> }} [seen]
|
|
100
|
+
*/
|
|
101
|
+
function collectDeferredHydrationCss(
|
|
102
|
+
files,
|
|
103
|
+
group,
|
|
104
|
+
insideDeferredHydration = false,
|
|
105
|
+
seen = { eager: new Set(), deferred: new Set() },
|
|
106
|
+
) {
|
|
107
|
+
if (!group) return;
|
|
108
|
+
const deferredHydration =
|
|
109
|
+
insideDeferredHydration ||
|
|
110
|
+
[...iterable(group.origins)].some((origin) => isDeferredHydrationRequest(origin?.request));
|
|
111
|
+
const visited = deferredHydration ? seen.deferred : seen.eager;
|
|
112
|
+
if (visited.has(group)) return;
|
|
113
|
+
visited.add(group);
|
|
114
|
+
if (deferredHydration) {
|
|
115
|
+
for (const chunk of iterable(group.chunks)) collectCss(files, chunk);
|
|
116
|
+
}
|
|
117
|
+
for (const child of iterable(group.childrenIterable)) {
|
|
118
|
+
collectDeferredHydrationCss(files, child, deferredHydration, seen);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
79
122
|
/** @param {any} compilation @param {string} message */
|
|
80
123
|
function addCompilationError(compilation, message) {
|
|
81
124
|
const error = new Error(`[octane] ${message}`);
|
|
@@ -172,6 +215,7 @@ export class OctaneClientAssetsPlugin {
|
|
|
172
215
|
for (const group of routeGroups.get(id) ?? []) {
|
|
173
216
|
const chunks = [...iterable(group?.chunks)];
|
|
174
217
|
for (const chunk of chunks) collectCss(css, chunk);
|
|
218
|
+
collectDeferredHydrationCss(css, group);
|
|
175
219
|
// SplitChunks inserts shared chunks before the original async chunk,
|
|
176
220
|
// so scan from the end for the route script. Do not call
|
|
177
221
|
// getEntrypointChunk(): normal Rspack async groups are not entrypoints.
|
package/src/index.js
CHANGED
|
@@ -150,6 +150,7 @@ function configSignature(config) {
|
|
|
150
150
|
preHydrate: config.router.preHydrate,
|
|
151
151
|
rootBoundary: config.rootBoundary,
|
|
152
152
|
server: config.server,
|
|
153
|
+
compiler: { renderers: config.compiler.renderers.signature },
|
|
153
154
|
});
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -226,8 +227,9 @@ function assertRootPublicPaths(config, clientEnvironment) {
|
|
|
226
227
|
*
|
|
227
228
|
* @param {{
|
|
228
229
|
* hmr?: boolean,
|
|
229
|
-
*
|
|
230
|
+
* profile?: boolean,
|
|
230
231
|
* exclude?: string[],
|
|
232
|
+
* requireDirective?: boolean,
|
|
231
233
|
* clientEnvironment?: string,
|
|
232
234
|
* serverEnvironment?: string,
|
|
233
235
|
* }} [inlineOptions]
|
|
@@ -333,8 +335,23 @@ export function pluginOctane(inlineOptions = {}) {
|
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
+
// Renderer selection is serialized into each Rspack compiler's loader
|
|
339
|
+
// options, so a browser reload cannot apply config changes safely. Ask
|
|
340
|
+
// Rsbuild to reconstruct the dev server (and therefore every compiler)
|
|
341
|
+
// whenever the Octane config or one of its imported helpers changes.
|
|
342
|
+
// This also covers compiler-only projects without application routes.
|
|
343
|
+
const watchedConfig = initialLoaded
|
|
344
|
+
? mergeRsbuildConfig(config, {
|
|
345
|
+
dev: {
|
|
346
|
+
watchFiles: {
|
|
347
|
+
paths: [...initialLoaded.dependencies],
|
|
348
|
+
type: 'reload-server',
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
})
|
|
352
|
+
: config;
|
|
353
|
+
if (!appEnabled) return watchedConfig;
|
|
354
|
+
assertRootPublicPaths(watchedConfig, clientEnvironment);
|
|
338
355
|
const productionBuild = isProductionBuild();
|
|
339
356
|
const octaneConfig = /** @type {import('@octanejs/app-core').ResolvedOctaneConfig} */ (
|
|
340
357
|
initialConfig
|
|
@@ -353,7 +370,7 @@ export function pluginOctane(inlineOptions = {}) {
|
|
|
353
370
|
const targetOutput = buildTargetPlan?.browserslist
|
|
354
371
|
? { overrideBrowserslist: buildTargetPlan.browserslist }
|
|
355
372
|
: {};
|
|
356
|
-
return mergeRsbuildConfig(
|
|
373
|
+
return mergeRsbuildConfig(watchedConfig, {
|
|
357
374
|
server: { htmlFallback: false, historyApiFallback: false },
|
|
358
375
|
environments: {
|
|
359
376
|
[clientEnvironment]: {
|
|
@@ -395,6 +412,23 @@ export function pluginOctane(inlineOptions = {}) {
|
|
|
395
412
|
});
|
|
396
413
|
});
|
|
397
414
|
|
|
415
|
+
api.modifyEnvironmentConfig((config, { mergeEnvironmentConfig }) => {
|
|
416
|
+
// Octane and several framework bindings use this conventional guard for
|
|
417
|
+
// diagnostics and production-only branches. Rsbuild does not define it
|
|
418
|
+
// when a programmatic build inherits mode "none", so scope the define to
|
|
419
|
+
// browser environments where the Node `process` global is unavailable.
|
|
420
|
+
if (config.output.target === 'node') return config;
|
|
421
|
+
return mergeEnvironmentConfig(config, {
|
|
422
|
+
source: {
|
|
423
|
+
define: {
|
|
424
|
+
'process.env.NODE_ENV': JSON.stringify(
|
|
425
|
+
isProductionBuild() ? 'production' : 'development',
|
|
426
|
+
),
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
398
432
|
if (appEnabled) {
|
|
399
433
|
api.transform(
|
|
400
434
|
{
|
|
@@ -481,10 +515,14 @@ export function pluginOctane(inlineOptions = {}) {
|
|
|
481
515
|
environment,
|
|
482
516
|
transpile: false,
|
|
483
517
|
...(inlineOptions.hmr === undefined ? null : { hmr: inlineOptions.hmr }),
|
|
484
|
-
...(inlineOptions.
|
|
518
|
+
...(inlineOptions.profile === undefined
|
|
485
519
|
? null
|
|
486
|
-
: {
|
|
520
|
+
: { profile: environment === 'client' && inlineOptions.profile }),
|
|
487
521
|
...(inlineOptions.exclude === undefined ? null : { exclude: inlineOptions.exclude }),
|
|
522
|
+
...(inlineOptions.requireDirective === undefined
|
|
523
|
+
? null
|
|
524
|
+
: { requireDirective: inlineOptions.requireDirective }),
|
|
525
|
+
renderers: initialConfig?.compiler.renderers,
|
|
488
526
|
}),
|
|
489
527
|
);
|
|
490
528
|
config.resolve ??= {};
|
package/types/index.d.ts
CHANGED
|
@@ -11,10 +11,23 @@ export {
|
|
|
11
11
|
export interface OctaneRsbuildPluginOptions {
|
|
12
12
|
/** Override component HMR in the browser environment. */
|
|
13
13
|
hmr?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/**
|
|
14
|
+
/** Enable component profiling in the browser environment. */
|
|
15
|
+
profile?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Ad-hoc path fragments skipped by the plain TypeScript/JavaScript
|
|
18
|
+
* hook-slot pass. With `requireDirective`, excluded paths are exempt from
|
|
19
|
+
* Octane ownership entirely — including `.tsrx`/`.tsx` — for projects
|
|
20
|
+
* routing those paths through a different tsrx compiler (e.g.
|
|
21
|
+
* `@tsrx/react`).
|
|
22
|
+
*/
|
|
17
23
|
exclude?: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Mixed-toolchain ownership gate: compile only project modules declaring
|
|
26
|
+
* `'use octane'`; undirected project `.tsx`/`.ts` pass through to the host
|
|
27
|
+
* framework's own pipeline. See `@octanejs/rspack-plugin` for details.
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
requireDirective?: boolean;
|
|
18
31
|
/** Rsbuild environment name used for the browser bundle. @default 'web' */
|
|
19
32
|
clientEnvironment?: string;
|
|
20
33
|
/** Rsbuild environment name used for the Node SSR bundle. @default 'node' */
|