@octanejs/rspack-plugin 0.1.34 → 0.1.35

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 CHANGED
@@ -26,7 +26,7 @@ export default {
26
26
  The plugin:
27
27
 
28
28
  - adds `.tsrx`, `.tsx`, and `.ts` resolution;
29
- - installs the Octane pre-loader for local and linked/raw dependency sources;
29
+ - compiles local and linked/raw dependency sources in parallel Rspack workers;
30
30
  - selects client or server codegen from Rspack's `target` (or an explicit
31
31
  `environment` option);
32
32
  - resolves every exact bare `octane` import to one client runtime, or to
@@ -47,6 +47,14 @@ Set `transpile: false` when an existing rule already strips TypeScript. Set
47
47
  Set `profile: true` to produce a client profiling build; server compilations
48
48
  always keep profiling disabled.
49
49
 
50
+ Octane uses up to four Rspack loader workers by default. Set `parallel: false`
51
+ to compile on the main thread, or provide `parallel: { maxWorkers: 2 }` to
52
+ request a different worker-pool limit. Rspack shares one loader worker pool
53
+ per process, so the first parallel loader determines its effective size.
54
+ Source maps, module layers, compiler metadata, and watched package manifests
55
+ are preserved in both modes. Worker startup has a fixed cost, so very small
56
+ builds may be faster with `parallel: false`.
57
+
50
58
  Set `strong: true` to reject state updates during rendering, state updates from
51
59
  effects, and ref writes during rendering in your application code:
52
60
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/rspack-plugin",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,13 +45,13 @@
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@rspack/core": "^2.0.0",
48
- "octane": "0.1.39"
48
+ "octane": "0.1.40"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@rspack/core": "^2.1.4",
52
52
  "@types/node": "^24.13.3",
53
53
  "vitest": "^4.1.10",
54
- "octane": "0.1.39"
54
+ "octane": "0.1.40"
55
55
  },
56
56
  "scripts": {
57
57
  "test": "vitest run --config vitest.config.js"
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Rspack does not copy a module's layer or buildInfo into parallel-loader
3
+ * workers. Pitch data is structured-cloned into the worker before compilation.
4
+ */
5
+ export function pitch() {
6
+ this.data ??= {};
7
+ this.data.octaneLayer = this._module?.layer ?? null;
8
+ if (this._module?.buildInfo && typeof this._module.buildInfo === 'object') {
9
+ delete this._module.buildInfo.octane;
10
+ }
11
+ }
12
+
13
+ /** Restore worker-owned Octane metadata and missing-file watches on the main thread. */
14
+ export default function finalizeOctaneLoader(source, sourceMap, metadata) {
15
+ const result = metadata?.__octaneParallelLoader;
16
+ if (result?.buildInfo) {
17
+ if (!this._module.buildInfo || typeof this._module.buildInfo !== 'object') {
18
+ this._module.buildInfo = {};
19
+ }
20
+ this._module.buildInfo.octane = result.buildInfo;
21
+ }
22
+ for (const dependency of result?.missingDependencies ?? []) {
23
+ this.addMissingDependency(dependency);
24
+ }
25
+ if (metadata && typeof metadata === 'object') {
26
+ const { __octaneParallelLoader, ...remainingMetadata } = metadata;
27
+ if (Object.keys(remainingMetadata).length > 0) {
28
+ this.callback(null, source, sourceMap, remainingMetadata);
29
+ return;
30
+ }
31
+ }
32
+ this.callback(null, source, sourceMap);
33
+ }
@@ -0,0 +1,34 @@
1
+ import octaneLoader from './loader.js';
2
+
3
+ /**
4
+ * Run the unchanged public Octane loader inside Rspack's worker pool. Rspack
5
+ * proxies resolution, ordinary file dependencies, warnings, and asynchronous
6
+ * callbacks, but omits module layer/buildInfo and addMissingDependency.
7
+ */
8
+ export default function parallelOctaneLoader(source, sourceMap) {
9
+ const missingDependencies = [];
10
+ const module = this._module;
11
+ const finalizer = this.loaders[this.loaderIndex - 1];
12
+ module.layer = finalizer?.loaderItem?.data?.octaneLayer ?? undefined;
13
+
14
+ const callback = this.callback.bind(this);
15
+ const async = this.async.bind(this);
16
+ const withWorkerMetadata = (finish) => (error, output, outputMap, metadata) => {
17
+ if (error) {
18
+ finish(error);
19
+ return;
20
+ }
21
+ finish(null, output, outputMap, {
22
+ ...(metadata ?? {}),
23
+ __octaneParallelLoader: {
24
+ buildInfo: module.buildInfo?.octane ?? null,
25
+ missingDependencies,
26
+ },
27
+ });
28
+ };
29
+
30
+ this.addMissingDependency = (dependency) => missingDependencies.push(dependency);
31
+ this.callback = withWorkerMetadata(callback);
32
+ this.async = () => withWorkerMetadata(async());
33
+ return octaneLoader.call(this, source, sourceMap);
34
+ }
package/src/plugin.js CHANGED
@@ -18,6 +18,9 @@ const PLUGIN_NAME = 'OctaneRspackPlugin';
18
18
  const PROFILE_DEFINE = '__OCTANE_PROFILE_ENABLED__';
19
19
  const PLUGIN_VERSION = createRequire(import.meta.url)('../package.json').version;
20
20
  const loaderPath = fileURLToPath(new URL('./loader.js', import.meta.url));
21
+ const finalizeLoaderPath = fileURLToPath(new URL('./finalize-loader.js', import.meta.url));
22
+ const parallelLoaderPath = fileURLToPath(new URL('./parallel-loader.js', import.meta.url));
23
+ const DEFAULT_MAX_WORKERS = 4;
21
24
  const OCTANE_RULE = /\.(?:tsrx|tsx|ts|js)$/i;
22
25
  const TYPESCRIPT_RULE = /\.(?:tsrx|tsx|ts)$/i;
23
26
 
@@ -337,7 +340,22 @@ export class OctaneRspackPlugin {
337
340
  test: OCTANE_RULE,
338
341
  type: 'javascript/auto',
339
342
  enforce: 'pre',
340
- use: [{ loader: loaderPath, options: loaderOptions }],
343
+ use:
344
+ this.options.parallel === false
345
+ ? [{ loader: loaderPath, options: loaderOptions }]
346
+ : [
347
+ { loader: finalizeLoaderPath, options: loaderOptions },
348
+ {
349
+ loader: parallelLoaderPath,
350
+ options: loaderOptions,
351
+ parallel: {
352
+ maxWorkers:
353
+ typeof this.options.parallel === 'object'
354
+ ? (this.options.parallel.maxWorkers ?? DEFAULT_MAX_WORKERS)
355
+ : DEFAULT_MAX_WORKERS,
356
+ },
357
+ },
358
+ ],
341
359
  });
342
360
  if (this.options.transpile !== false) {
343
361
  compiler.options.module.rules.push({
package/src/shared.js CHANGED
@@ -47,7 +47,7 @@ const LOADER_OPTION_KEYS = new Set([
47
47
  'universalRuntime',
48
48
  'layerSpecializations',
49
49
  ]);
50
- const PLUGIN_OPTION_KEYS = new Set([...LOADER_OPTION_KEYS, 'runtime', 'transpile']);
50
+ const PLUGIN_OPTION_KEYS = new Set([...LOADER_OPTION_KEYS, 'parallel', 'runtime', 'transpile']);
51
51
  const LAYER_SPECIALIZATION_KEYS = new Set(['runtime', 'renderers', 'universalRuntime']);
52
52
 
53
53
  function normalizeRuntimeRequest(value, label = 'runtime') {
@@ -153,6 +153,29 @@ function assertBooleanOption(options, key) {
153
153
  }
154
154
  }
155
155
 
156
+ function normalizeParallelOption(value) {
157
+ if (value === undefined || typeof value === 'boolean') return value;
158
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
159
+ throw new TypeError(
160
+ '@octanejs/rspack-plugin: `parallel` must be a boolean or an options object.',
161
+ );
162
+ }
163
+ for (const key of Object.keys(value)) {
164
+ if (key !== 'maxWorkers') {
165
+ throw new TypeError(`@octanejs/rspack-plugin: unknown \`parallel.${key}\` option.`);
166
+ }
167
+ }
168
+ if (
169
+ value.maxWorkers !== undefined &&
170
+ (!Number.isSafeInteger(value.maxWorkers) || value.maxWorkers < 1)
171
+ ) {
172
+ throw new TypeError(
173
+ '@octanejs/rspack-plugin: `parallel.maxWorkers` must be a positive integer.',
174
+ );
175
+ }
176
+ return Object.freeze(value.maxWorkers === undefined ? {} : { maxWorkers: value.maxWorkers });
177
+ }
178
+
156
179
  function normalizeOptions(value, plugin) {
157
180
  const options = value ?? {};
158
181
  if (typeof options !== 'object' || Array.isArray(options)) {
@@ -187,6 +210,7 @@ function normalizeOptions(value, plugin) {
187
210
  throw new TypeError('@octanejs/rspack-plugin: `exclude` must be an array of path strings.');
188
211
  }
189
212
  if (plugin) assertBooleanOption(options, 'transpile');
213
+ const parallel = plugin ? normalizeParallelOption(options.parallel) : undefined;
190
214
  const renderers =
191
215
  options.renderers === undefined ? undefined : normalizeRendererConfig(options.renderers);
192
216
  const universalRuntime = normalizeUniversalRuntime(options.universalRuntime);
@@ -206,6 +230,7 @@ function normalizeOptions(value, plugin) {
206
230
  ...(options.requireDirective === undefined
207
231
  ? null
208
232
  : { requireDirective: options.requireDirective }),
233
+ ...(plugin && parallel !== undefined ? { parallel } : null),
209
234
  ...(plugin && options.transpile !== undefined ? { transpile: options.transpile } : null),
210
235
  ...(plugin && options.runtime !== undefined ? { runtime: options.runtime } : null),
211
236
  };
package/types/index.d.ts CHANGED
@@ -152,6 +152,13 @@ export interface OctaneRspackLoaderOptions {
152
152
  }
153
153
 
154
154
  export interface OctaneRspackPluginOptions extends OctaneRspackLoaderOptions {
155
+ /**
156
+ * Compile Octane modules in Rspack worker threads. Enabled by default with
157
+ * at most four workers; set `false` to keep compilation on the main thread.
158
+ * Provide `maxWorkers` to request a different shared worker-pool limit.
159
+ * @default true
160
+ */
161
+ parallel?: boolean | { maxWorkers?: number };
155
162
  /**
156
163
  * Compiler and exact-runtime overrides selected by the current module's
157
164
  * Rspack layer. Unknown layers retain the top-level plugin options.
@@ -21,6 +21,7 @@ const loaderOptions: OctaneRspackLoaderOptions = {
21
21
 
22
22
  const pluginOptions: OctaneRspackPluginOptions = {
23
23
  strong: false,
24
+ parallel: { maxWorkers: 2 },
24
25
  layerSpecializations: {
25
26
  'native:main': {
26
27
  runtime: '@fixture/native-main-runtime',
@@ -29,6 +30,8 @@ const pluginOptions: OctaneRspackPluginOptions = {
29
30
  },
30
31
  };
31
32
 
33
+ const serialPluginOptions: OctaneRspackPluginOptions = { parallel: false };
34
+
32
35
  const unsupportedLoaderRuntime: OctaneRspackLoaderOptions = {
33
36
  layerSpecializations: {
34
37
  'native:main': {
@@ -38,6 +41,13 @@ const unsupportedLoaderRuntime: OctaneRspackLoaderOptions = {
38
41
  },
39
42
  };
40
43
 
44
+ const unsupportedLoaderParallel: OctaneRspackLoaderOptions = {
45
+ // @ts-expect-error Worker-pool configuration belongs to the plugin, not the standalone loader.
46
+ parallel: true,
47
+ };
48
+
41
49
  void loaderOptions;
42
50
  void pluginOptions;
51
+ void serialPluginOptions;
43
52
  void unsupportedLoaderRuntime;
53
+ void unsupportedLoaderParallel;