@open-xchange/vite-plugin-ox-manifests 0.8.4 → 1.0.0-pre2

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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebFetch(domain:eslint.org)",
5
+ "WebFetch(domain:www.npmjs.com)",
6
+ "WebFetch(domain:github.com)",
7
+ "mcp__plugin_context7_context7__resolve-library-id",
8
+ "mcp__plugin_context7_context7__query-docs"
9
+ ]
10
+ }
11
+ }
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.0.0] - 2026-03-13
8
+
9
+ ### Changed
10
+
11
+ - Migrate to Vite 8 / Rolldown
12
+ - Build manifest from bundle directly (Vite 8 native manifest is inaccessible)
13
+ - Replace Rollup types with Rolldown namespace from vite
14
+ - Switch dependency from `@open-xchange/rollup-plugin-po2json` to `@open-xchange/vite-plugin-po2json`
15
+ - Add `vite ^8.0.0` as peer dependency
16
+
7
17
  ## [0.6.4] - 2024-03-01
8
18
 
9
19
  ### Changed
@@ -1,11 +1,12 @@
1
1
  import { dirname, posix } from 'node:path';
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { normalizePath } from 'vite';
4
- import { PROJECT_NAME as GETTEXT_PROJECT_NAME, parsePoFile, namespacesFrom } from '@open-xchange/rollup-plugin-po2json';
4
+ import { PROJECT_NAME as GETTEXT_PROJECT_NAME, parsePoFile, namespacesFrom } from '@open-xchange/vite-plugin-po2json';
5
5
  import { definePlugin } from './plugin.js';
6
6
  import { PROJECT_NAME, applyInputToOptions } from '../util.js';
7
7
  export default definePlugin(() => {
8
8
  const manifests = [];
9
+ const dictionaryFileNames = new Set();
9
10
  let resolvedConfig;
10
11
  return {
11
12
  name: `${PROJECT_NAME}/gettext-plugin`,
@@ -32,6 +33,11 @@ export default definePlugin(() => {
32
33
  }));
33
34
  input[defaultDictionary] = 'gettext';
34
35
  manifests.push({ namespace: 'i18n', path: defaultDictionary });
36
+ // Track expected output file names for dictionary chunks so we can
37
+ // identify them in generateBundle (chunk.modules is empty in Rolldown)
38
+ for (const namespace of [defaultDictionary, ...Object.keys(input).filter(k => k !== defaultDictionary)]) {
39
+ dictionaryFileNames.add(`${namespace}.js`);
40
+ }
35
41
  if (resolvedConfig.mode === 'production') {
36
42
  applyInputToOptions(input, options);
37
43
  }
@@ -42,19 +48,19 @@ export default definePlugin(() => {
42
48
  return manifests;
43
49
  return manifests.map(manifest => ({ ...manifest, raw: posix.join(resolvedConfig.base, `/@id/${manifest.path}.js`) }));
44
50
  },
51
+ // In Rolldown, chunk.modules is empty and getModuleInfo().meta is not
52
+ // mutable. Identify dictionary chunks by matching their output file names
53
+ // against the known dictionary namespaces, and annotate them directly.
45
54
  generateBundle(_options, bundle) {
46
55
  for (const file in bundle) {
47
56
  const chunk = bundle[file];
48
- if (chunk.type !== 'chunk' || !chunk.facadeModuleId)
57
+ if (chunk.type !== 'chunk')
49
58
  continue;
50
- const modules = Object.keys(chunk.modules);
51
- if (modules.length === 0)
52
- modules.push(chunk.facadeModuleId);
53
- for (const id of modules) {
54
- const meta = this.getModuleInfo(id)?.meta;
55
- if (meta?.gettext?.dictionary === true) {
56
- (meta.manifests ??= []).push({ namespace: 'i18n', path: '' });
57
- }
59
+ if (dictionaryFileNames.has(file)) {
60
+ const c = chunk;
61
+ const arr = (c._oxManifests ?? []);
62
+ arr.push({ namespace: 'i18n', path: '' });
63
+ c._oxManifests = arr;
58
64
  }
59
65
  }
60
66
  }
@@ -22,50 +22,87 @@ async function getManifestEntryFile(basePath, extensions) {
22
22
  }
23
23
  throw new Error(`Cannot find file "${basePath}"`);
24
24
  }
25
+ /**
26
+ * Computes the original file name for a chunk relative to the project root.
27
+ * Mirrors the logic used by Vite's internal manifest plugin.
28
+ */
29
+ function getChunkOriginalFileName(chunk, root) {
30
+ if (chunk.facadeModuleId) {
31
+ let name = chunk.facadeModuleId;
32
+ // strip query params
33
+ const queryIndex = name.indexOf('?');
34
+ if (queryIndex >= 0)
35
+ name = name.substring(0, queryIndex);
36
+ // make relative to root
37
+ if (name.startsWith(root + '/')) {
38
+ name = name.substring(root.length + 1);
39
+ }
40
+ return name;
41
+ }
42
+ return `_${path.basename(chunk.fileName)}`;
43
+ }
44
+ /**
45
+ * Builds a Vite-compatible manifest from the output bundle.
46
+ * This replaces the previous approach of intercepting Vite's internal
47
+ * manifest plugin, which is no longer possible in Vite 8 where the
48
+ * manifest is generated by a native plugin.
49
+ */
50
+ function buildManifestFromBundle(bundle, root) {
51
+ const manifest = {};
52
+ for (const file in bundle) {
53
+ const output = bundle[file];
54
+ if (output.type !== 'chunk')
55
+ continue;
56
+ const src = getChunkOriginalFileName(output, root);
57
+ const entry = { file: output.fileName };
58
+ if (output.facadeModuleId)
59
+ entry.src = src;
60
+ if (output.isEntry)
61
+ entry.isEntry = true;
62
+ if (output.isDynamicEntry)
63
+ entry.isDynamicEntry = true;
64
+ if (output.imports?.length)
65
+ entry.imports = [...output.imports];
66
+ if (output.dynamicImports?.length)
67
+ entry.dynamicImports = [...output.dynamicImports];
68
+ // CSS and assets from viteMetadata
69
+ const viteMetadata = output.viteMetadata;
70
+ if (viteMetadata?.importedCss?.size)
71
+ entry.css = [...viteMetadata.importedCss];
72
+ if (viteMetadata?.importedAssets?.size)
73
+ entry.assets = [...viteMetadata.importedAssets];
74
+ manifest[src] = entry;
75
+ }
76
+ // Also include assets
77
+ for (const file in bundle) {
78
+ const output = bundle[file];
79
+ if (output.type !== 'asset')
80
+ continue;
81
+ // Skip internal Vite assets like .vite/manifest.json
82
+ if (output.fileName.startsWith('.vite/'))
83
+ continue;
84
+ const names = output.originalFileNames?.length
85
+ ? output.originalFileNames
86
+ : (output.names?.length ? output.names : []);
87
+ for (const name of names) {
88
+ if (!manifest[name]) {
89
+ manifest[name] = { file: output.fileName };
90
+ }
91
+ }
92
+ }
93
+ return manifest;
94
+ }
25
95
  export default definePlugin(({ supportedEntryExtensions, entryPoints, manifestsAsEntryPoints }) => {
26
96
  const manifestModules = new Map();
27
97
  let resolvedConfig;
28
- let inputOptions;
29
- let viteManifestPlugin;
30
- let environment;
31
- async function buildViteManifests(options, bundle) {
32
- return new Promise((resolve, reject) => {
33
- // local async context to satisfy type checker expecting void callback for promise constructor
34
- (async function () {
35
- if (typeof viteManifestPlugin.buildStart !== 'function' || typeof viteManifestPlugin.generateBundle !== 'function') {
36
- throw new Error(`${PROJECT_NAME}: missing required callbacks in plugin 'vite:manifest'`);
37
- }
38
- // fake `PluginContext` to be passed to Vite's manifest plugin, needed to extract the asset data
39
- const context = {
40
- emitFile(file) {
41
- if (file.type === 'asset' && typeof file.source === 'string') {
42
- resolve(JSON.parse(file.source));
43
- }
44
- else {
45
- reject(new Error(`${PROJECT_NAME}: received unexpected manifest data from plugin 'vite:manifest'`));
46
- }
47
- },
48
- getFileName: () => 'Not implemented',
49
- environment
50
- };
51
- // manually invoke plugin hooks (in a local async context to satisfy type checker expecting sync promise callbacks)
52
- await viteManifestPlugin.buildStart.call(context, inputOptions);
53
- await viteManifestPlugin.generateBundle.call(context, options, bundle, false);
54
- })().catch(reject);
55
- });
56
- }
57
98
  return {
58
99
  name: `${PROJECT_NAME}/manifests-plugin`,
59
100
  config(config) {
60
- (config.build ??= {}).manifest = true;
101
+ // Disable Vite's built-in manifest we generate our own
102
+ (config.build ??= {}).manifest = false;
61
103
  },
62
104
  configResolved(config) {
63
105
  resolvedConfig = config;
64
- if (resolvedConfig.mode !== 'production')
65
- return;
66
- // extract the vite:manifest plugin from the list
67
- const index = config.plugins.findIndex(plugin => plugin.name === 'vite:manifest');
68
- viteManifestPlugin = config.plugins.splice(index, 1)[0];
69
106
  },
70
107
  async options(options) {
71
108
  const input = {};
@@ -102,10 +139,6 @@ export default definePlugin(({ supportedEntryExtensions, entryPoints, manifestsA
102
139
  }
103
140
  return options;
104
141
  },
105
- buildStart(options) {
106
- inputOptions = options;
107
- environment = this.environment;
108
- },
109
142
  // specific call to collect all manifests
110
143
  getManifests() {
111
144
  return Array.from(manifestModules, ([path, manifests]) => manifests.map(m => ({ ...m, path: basepath(path.slice(resolvedConfig.root.length + 1)) }))).flat(1);
@@ -116,12 +149,20 @@ export default definePlugin(({ supportedEntryExtensions, entryPoints, manifestsA
116
149
  const manifests = manifestModules.get(id);
117
150
  return manifests ? { meta: { manifests } } : undefined;
118
151
  },
119
- async generateBundle(options, bundle) {
120
- const viteManifests = await buildViteManifests(options, bundle);
152
+ async generateBundle(_options, bundle) {
153
+ const viteManifests = buildManifestFromBundle(bundle, resolvedConfig.root);
121
154
  for (const entry in viteManifests) {
122
155
  const chunk = bundle[viteManifests[entry].file];
123
- if (chunk.type === 'chunk' && chunk.facadeModuleId) {
124
- viteManifests[entry].meta = this.getModuleInfo(chunk.facadeModuleId)?.meta;
156
+ if (chunk?.type === 'chunk' && chunk.facadeModuleId) {
157
+ const meta = { ...this.getModuleInfo(chunk.facadeModuleId)?.meta };
158
+ // Merge manifests annotated directly on the chunk by other sub-plugins
159
+ // (e.g. gettext.ts), since Rolldown's getModuleInfo().meta doesn't
160
+ // reflect mutations or virtual module meta on facade modules.
161
+ const chunkManifests = chunk._oxManifests;
162
+ if (chunkManifests?.length) {
163
+ meta.manifests = [...(meta.manifests || []), ...chunkManifests];
164
+ }
165
+ viteManifests[entry].meta = meta;
125
166
  }
126
167
  }
127
168
  this.emitFile({
@@ -1,5 +1,5 @@
1
1
  import type { Plugin } from 'vite';
2
- import type { GettextPluginModuleMeta } from '@open-xchange/rollup-plugin-po2json';
2
+ import type { GettextPluginModuleMeta } from '@open-xchange/vite-plugin-po2json';
3
3
  import type { OxManifest } from '../util.js';
4
4
  import type { VitePluginOxManifests, VitePluginOxManifestsOptions } from '../index.js';
5
5
  /**
package/dist/util.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ResolvedConfig, ViteDevServer, Rollup } from 'vite';
1
+ import type { ResolvedConfig, ViteDevServer, Rolldown } from 'vite';
2
2
  export type Dict<T = unknown> = Record<string, T>;
3
3
  export interface OxManifest {
4
4
  namespace: string;
@@ -8,7 +8,7 @@ export interface OxManifest {
8
8
  generator?: string;
9
9
  }
10
10
  export declare const PROJECT_NAME = "@open-xchange/vite-plugin-ox-manifests";
11
- export declare function applyInputToOptions(input: Dict<string>, options: Rollup.InputOptions): void;
11
+ export declare function applyInputToOptions(input: Dict<string>, options: Rolldown.InputOptions): void;
12
12
  export declare function joinUrlPaths(...paths: string[]): string;
13
13
  export declare function basepath(src: string): string;
14
14
  export declare function deepMergeObject(a: Dict, b: Dict): Dict;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-xchange/vite-plugin-ox-manifests",
3
- "version": "0.8.4",
3
+ "version": "1.0.0-pre2",
4
4
  "description": "A vite plugin to concat and serve ox manifests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,16 +26,18 @@
26
26
  "fast-glob": "^3.3.3",
27
27
  "magic-string": "^0.30.21",
28
28
  "parseurl": "^1.3.3",
29
- "@open-xchange/rollup-plugin-po2json": "0.9.4",
30
- "@open-xchange/vite-plugin-ox-externals": "0.8.1"
29
+ "@open-xchange/vite-plugin-po2json": "1.0.0-pre1",
30
+ "@open-xchange/vite-plugin-ox-externals": "1.0.0-pre1"
31
+ },
32
+ "peerDependencies": {
33
+ "vite": "^8.0.0"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/node": "^24.10.3",
34
37
  "@types/parseurl": "^1.3.3",
35
38
  "less": "^4.4.2",
36
- "rollup": "^4.53.3",
37
39
  "typescript": "^5.9.3",
38
- "vite": "^7.2.7",
40
+ "vite": "^8.0.0",
39
41
  "vitest": "^4.0.15",
40
42
  "@open-xchange/lint": "0.2.1"
41
43
  },