@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.
- package/.claude/settings.local.json +11 -0
- package/CHANGELOG.md +10 -0
- package/dist/plugins/gettext.js +16 -10
- package/dist/plugins/manifests.js +84 -43
- package/dist/plugins/plugin.d.ts +1 -1
- package/dist/util.d.ts +2 -2
- package/package.json +7 -5
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
|
package/dist/plugins/gettext.js
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/
|
|
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'
|
|
57
|
+
if (chunk.type !== 'chunk')
|
|
49
58
|
continue;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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(
|
|
120
|
-
const viteManifests =
|
|
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
|
|
124
|
-
|
|
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({
|
package/dist/plugins/plugin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
-
import type { GettextPluginModuleMeta } from '@open-xchange/
|
|
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,
|
|
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:
|
|
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.
|
|
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/
|
|
30
|
-
"@open-xchange/vite-plugin-ox-externals": "0.
|
|
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": "^
|
|
40
|
+
"vite": "^8.0.0",
|
|
39
41
|
"vitest": "^4.0.15",
|
|
40
42
|
"@open-xchange/lint": "0.2.1"
|
|
41
43
|
},
|