@octanejs/vite-plugin 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/vite-plugin",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -46,18 +46,18 @@
46
46
  }
47
47
  },
48
48
  "dependencies": {
49
- "@ripple-ts/adapter": "^0.3.101",
50
- "@octanejs/app-core": "0.0.7"
49
+ "@ripple-ts/adapter": "^0.3.108",
50
+ "@octanejs/app-core": "0.0.9"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vite": "^8.0.16",
54
- "octane": "0.1.11"
54
+ "octane": "0.1.13"
55
55
  },
56
56
  "devDependencies": {
57
- "@types/node": "^24.3.0",
58
- "playwright": "^1.61.0",
59
- "type-fest": "^5.6.0",
60
- "vite": "^8.0.16",
61
- "octane": "0.1.11"
57
+ "@types/node": "^24.13.3",
58
+ "playwright": "^1.61.1",
59
+ "type-fest": "^5.8.0",
60
+ "vite": "^8.1.5",
61
+ "octane": "0.1.13"
62
62
  }
63
63
  }
@@ -32,9 +32,14 @@ function isDeferredHydrationId(id) {
32
32
  *
33
33
  * @param {Record<string, ViteManifestEntry>} manifest
34
34
  * @param {string[]} moduleIds
35
+ * @param {Record<string, string>} [entryFiles]
35
36
  * @returns {Record<string, { js: string, css: string[] }>}
36
37
  */
37
- export function createClientAssetMap(manifest, moduleIds) {
38
+ export function createClientAssetMap(manifest, moduleIds, entryFiles = {}) {
39
+ const manifestKeysByFile = new Map(
40
+ Object.entries(manifest).map(([key, entry]) => [entry.file, key]),
41
+ );
42
+
38
43
  /**
39
44
  * @param {string} key
40
45
  * @param {boolean} deferredHydrationBranch
@@ -69,9 +74,15 @@ export function createClientAssetMap(manifest, moduleIds) {
69
74
  const assets = {};
70
75
  for (const moduleId of moduleIds) {
71
76
  // Vite manifest keys are root-relative without the leading slash.
72
- const manifestKey = moduleId.startsWith('/') ? moduleId.slice(1) : moduleId;
77
+ const sourceKey = moduleId.startsWith('/') ? moduleId.slice(1) : moduleId;
78
+ // A route module that is also statically imported can become a shared
79
+ // non-facade chunk. Vite keys that manifest entry by generated chunk name,
80
+ // so use the Rollup-observed output file to recover the graph root.
81
+ const manifestKey = manifest[sourceKey]
82
+ ? sourceKey
83
+ : manifestKeysByFile.get(entryFiles[moduleId]);
84
+ if (!manifestKey) continue;
73
85
  const entry = manifest[manifestKey];
74
- if (!entry) continue;
75
86
  assets[moduleId] = {
76
87
  js: entry.file,
77
88
  css: [...new Set(collectCss(manifestKey, false, new Set()))],
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** @import {Plugin, RenderBuiltAssetUrl, ResolvedConfig, ViteDevServer, UserConfig} from 'vite' */
2
+ /** @import {ModulePreloadOptions, Plugin, RenderBuiltAssetUrl, ResolvedConfig, ViteDevServer, UserConfig} from 'vite' */
3
3
  /** @import {LoadedOctaneConfig, OctaneConfigOptions, ResolvedOctaneConfig, RenderRoute} from '@octanejs/vite-plugin' */
4
4
 
5
5
  import fs from 'node:fs';
@@ -210,9 +210,9 @@ function collect_hydrate_module_paths(config) {
210
210
  * build is redirected to `{outDir}/client` with a manifest, the hydrate entry
211
211
  * is injected into index.html (so Vite bundles + hashes it), and closeBundle
212
212
  * runs a second, `ssr: true` build of a generated server entry to
213
- * `{outDir}/server/entry.js` — a self-contained module (app + octane bundled,
214
- * node builtins external) exporting `handler`/`nodeHandler` and auto-booting
215
- * under `node`. See server/virtual-entry.js and server/production.js.
213
+ * `{outDir}/server/entry.js`. Node-target adapters get the existing bootable
214
+ * `handler`/`nodeHandler` module; webworker-target adapters get an importable
215
+ * `createWebWorkerHandler` factory for their deployment wrapper.
216
216
  *
217
217
  * @param {{ hmr?: boolean, profile?: boolean, exclude?: string[], requireDirective?: boolean, renderers?: import('@octanejs/app-core').ExperimentalRendererConfigOptions }} [inlineOptions]
218
218
  * @returns {Plugin[]}
@@ -243,6 +243,8 @@ export function octane(inlineOptions = {}) {
243
243
  let buildOctaneConfig = null;
244
244
  /** @type {string[]} Module paths the generated client entry maps statically (build only) */
245
245
  let staticEntries = [];
246
+ /** @type {Record<string, string>} Static module path → emitted client chunk file */
247
+ let staticEntryFiles = Object.create(null);
246
248
  /** @type {Set<string>} Vite-root paths of modules containing `module server` */
247
249
  const serverModuleModules = new Set();
248
250
 
@@ -328,6 +330,25 @@ export function octane(inlineOptions = {}) {
328
330
  if (buildOctaneConfig.build.target !== undefined) {
329
331
  buildConfig.target = buildOctaneConfig.build.target;
330
332
  }
333
+ const userModulePreload = userConfig.build?.modulePreload;
334
+ if (userModulePreload === false) {
335
+ buildConfig.modulePreload = false;
336
+ } else {
337
+ /** @type {ModulePreloadOptions} */
338
+ const modulePreload =
339
+ typeof userModulePreload === 'object' ? { ...userModulePreload } : {};
340
+ const userResolveDependencies = modulePreload.resolveDependencies;
341
+ modulePreload.resolveDependencies = (filename, dependencies, context) => {
342
+ // Vite 8.1 emits entry dependency hints after the entry script.
343
+ // A script that installs <base> in between can redirect those
344
+ // root-relative requests off-origin. Static imports discover the
345
+ // same dependencies safely from the entry module URL, so omit only
346
+ // the redundant HTML hints; retain JS dynamic-import preloading.
347
+ if (context.hostType === 'html') return [];
348
+ return userResolveDependencies?.(filename, dependencies, context) ?? dependencies;
349
+ };
350
+ buildConfig.modulePreload = modulePreload;
351
+ }
331
352
  const userRenderBuiltUrl = userConfig.experimental?.renderBuiltUrl;
332
353
  /** @type {RenderBuiltAssetUrl} */
333
354
  const renderBuiltUrl = (filename, context) => {
@@ -367,6 +388,28 @@ export function octane(inlineOptions = {}) {
367
388
  if (!isBuild || isSSRBuild || !has_route_config(buildOctaneConfig)) return;
368
389
  serverModuleModules.clear();
369
390
  staticEntries = collect_hydrate_module_paths(buildOctaneConfig);
391
+ staticEntryFiles = Object.create(null);
392
+ },
393
+
394
+ /**
395
+ * Preserve the source-to-file relation that Vite's manifest cannot express
396
+ * when Rolldown promotes a dynamic route entry into a shared chunk.
397
+ */
398
+ generateBundle(_options, bundle) {
399
+ if (!isBuild || isSSRBuild || !has_route_config(buildOctaneConfig)) return;
400
+ const entryByModuleId = new Map(
401
+ staticEntries.map((moduleId) => {
402
+ const file = path.resolve(root, moduleId.startsWith('/') ? `.${moduleId}` : moduleId);
403
+ return [file.split(path.sep).join('/'), moduleId];
404
+ }),
405
+ );
406
+ for (const output of Object.values(bundle)) {
407
+ if (output.type !== 'chunk') continue;
408
+ for (const moduleId of output.moduleIds) {
409
+ const entryId = entryByModuleId.get(moduleId.split(path.sep).join('/'));
410
+ if (entryId !== undefined) staticEntryFiles[entryId] = output.fileName;
411
+ }
412
+ }
370
413
  },
371
414
 
372
415
  async configResolved(resolvedConfig) {
@@ -670,6 +713,7 @@ export function octane(inlineOptions = {}) {
670
713
  async closeBundle() {
671
714
  if (!isBuild || isSSRBuild || !has_route_config(buildOctaneConfig)) return;
672
715
  const cfg = /** @type {ResolvedOctaneConfig} */ (buildOctaneConfig);
716
+ const webWorkerServer = cfg.adapter?.serverTarget === 'webworker';
673
717
 
674
718
  console.log('[@octanejs/vite-plugin] Client build done. Building the server bundle…');
675
719
 
@@ -692,7 +736,7 @@ export function octane(inlineOptions = {}) {
692
736
  );
693
737
  }
694
738
 
695
- const clientAssetMap = createClientAssetMap(clientManifest, staticEntries);
739
+ const clientAssetMap = createClientAssetMap(clientManifest, staticEntries, staticEntryFiles);
696
740
 
697
741
  // The manifest was only needed here; leaving .vite/ in dist/client would
698
742
  // publish source file paths through the static server.
@@ -712,9 +756,11 @@ export function octane(inlineOptions = {}) {
712
756
  rootBoundary: cfg.rootBoundary,
713
757
  rpcModulePaths: [...serverModuleModules],
714
758
  clientAssetMap,
759
+ ...(webWorkerServer ? { mode: 'webworker' } : null),
715
760
  // The virtual entry has no filesystem importer, so resolve app-core
716
761
  // from this package before handing source to Vite. This also works
717
762
  // when app-core is nested under the plugin by a package manager.
763
+ configModuleId: requireFromPlugin.resolve('@octanejs/app-core/config'),
718
764
  productionModuleId: requireFromPlugin.resolve('@octanejs/app-core/production'),
719
765
  nodeModuleId: requireFromPlugin.resolve('@octanejs/app-core/node'),
720
766
  }),
@@ -739,6 +785,10 @@ export function octane(inlineOptions = {}) {
739
785
  await viteBuild({
740
786
  root,
741
787
  appType: 'custom',
788
+ // Vite deliberately preserves process.env references in SSR/library
789
+ // output. Pin build intent here so Rollup can remove Octane's complete
790
+ // development error table even when the server bundle is not minified.
791
+ define: { 'process.env.NODE_ENV': JSON.stringify('production') },
742
792
  plugins: [virtualEntryPlugin],
743
793
  resolve: {
744
794
  alias: [
@@ -760,6 +810,10 @@ export function octane(inlineOptions = {}) {
760
810
  minify: cfg.build.minify ?? false,
761
811
  rollupOptions: {
762
812
  input: VIRTUAL_SERVER_ENTRY_ID,
813
+ // Cloudflare's nodejs_compat exposes `node:` modules directly.
814
+ // Preserve application imports as well as Octane's current runtime
815
+ // dependencies instead of replacing them with browser shims.
816
+ ...(webWorkerServer ? { external: [/^node:/] } : null),
763
817
  output: {
764
818
  entryFileNames: ENTRY_FILENAME,
765
819
  format: 'esm',
@@ -767,6 +821,7 @@ export function octane(inlineOptions = {}) {
767
821
  },
768
822
  },
769
823
  ssr: {
824
+ ...(webWorkerServer ? { target: 'webworker' } : null),
770
825
  // Self-contained server bundle: everything except node builtins is
771
826
  // bundled, so dist/server deploys without node_modules. 'vite'
772
827
  // stays external as a guard — nothing should reach it (the facade
@@ -784,9 +839,11 @@ export function octane(inlineOptions = {}) {
784
839
  }
785
840
 
786
841
  console.log(`[@octanejs/vite-plugin] Server build complete: ${path.join(outDir, 'server')}`);
787
- console.log(
788
- `[@octanejs/vite-plugin] Start with: node ${outDir}/server/${ENTRY_FILENAME} (or octane-preview)`,
789
- );
842
+ if (!webWorkerServer) {
843
+ console.log(
844
+ `[@octanejs/vite-plugin] Start with: node ${outDir}/server/${ENTRY_FILENAME} (or octane-preview)`,
845
+ );
846
+ }
790
847
 
791
848
  // ------------------------------------------------------------------
792
849
  // Deploy adapter (SvelteKit-style): with both bundles on disk, let the
package/types/index.d.ts CHANGED
@@ -23,13 +23,15 @@ export interface OctanePluginOptions {
23
23
  */
24
24
  exclude?: string[];
25
25
  /**
26
- * Mixed-toolchain ownership gate: when `true`, Octane compiles only
27
- * project modules that declare `'use octane'` in their directive prologue.
28
- * Undirected project `.tsx`/`.ts`/`.js` pass through to the host
29
- * framework's own pipeline (e.g. React's JSX transform); an undirected
30
- * project `.tsrx` is a build error. Installed and linked packages keep
31
- * their Octane package-manifest decision. The directive is always
32
- * tolerated and stripped from compiled output, even when this is off.
26
+ * Mixed-toolchain ownership gate: when `true`, a project `.tsrx` is
27
+ * Octane's by extension, and a project `.tsx` (full compile) or plain
28
+ * `.ts`/`.js` (hook slotting) is Octane's only when it opens with a
29
+ * leading `@jsxImportSource octane` pragma comment (any registered
30
+ * renderer's intrinsics module also counts). A pragma naming a
31
+ * foreign source (e.g. `react`) does not claim the file. Unmarked
32
+ * project modules pass through to the host framework's own pipeline
33
+ * (e.g. React's JSX transform). Installed and linked packages keep
34
+ * their Octane package-manifest decision.
33
35
  * @default false
34
36
  */
35
37
  requireDirective?: boolean;