vite-plugin-cross-origin-storage 1.0.0 → 1.2.0
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/dist/index.d.ts +17 -0
- package/dist/index.js +1747 -0
- package/package.json +39 -5
- package/index.ts +0 -202
package/package.json
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-cross-origin-storage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Vite plugin to load chunks from Cross-Origin Storage",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vite",
|
|
7
|
+
"plugin",
|
|
8
|
+
"cross-origin-storage",
|
|
9
|
+
"cos",
|
|
10
|
+
"loader",
|
|
11
|
+
"performance"
|
|
12
|
+
],
|
|
13
|
+
"author": "Thomas Steiner",
|
|
14
|
+
"license": "Apache-2.0",
|
|
15
|
+
"homepage": "https://github.com/tomayac/vite-plugin-cross-origin-storage#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/tomayac/vite-plugin-cross-origin-storage.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/tomayac/vite-plugin-cross-origin-storage/issues"
|
|
22
|
+
},
|
|
5
23
|
"type": "module",
|
|
6
|
-
"main": "index.
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
7
33
|
"files": [
|
|
8
|
-
"
|
|
34
|
+
"dist",
|
|
9
35
|
"loader.js"
|
|
10
36
|
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup index.ts --format esm --dts --clean"
|
|
39
|
+
},
|
|
11
40
|
"peerDependencies": {
|
|
12
41
|
"vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
13
42
|
},
|
|
14
|
-
"
|
|
15
|
-
"@rollup/pluginutils": "^5.3.0"
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@rollup/pluginutils": "^5.3.0",
|
|
45
|
+
"@types/node": "^25.0.9",
|
|
46
|
+
"rollup": "^4.55.2",
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vite": "^7.3.1"
|
|
16
50
|
}
|
|
17
51
|
}
|
package/index.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from 'vite';
|
|
2
|
-
import crypto from 'crypto';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
import { createFilter } from '@rollup/pluginutils';
|
|
7
|
-
|
|
8
|
-
export interface CosPluginOptions {
|
|
9
|
-
/**
|
|
10
|
-
* Pattern to include chunks to be managed by COS.
|
|
11
|
-
* Matches against the output filename (e.g. `assets/vendor-*.js`).
|
|
12
|
-
* Default: `['**\/*']` (all chunks, except the entry implementation detail)
|
|
13
|
-
*/
|
|
14
|
-
include?: string | RegExp | (string | RegExp)[];
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Pattern to exclude chunks from being managed by COS.
|
|
18
|
-
*/
|
|
19
|
-
exclude?: string | RegExp | (string | RegExp)[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export default function cosPlugin(options: CosPluginOptions = {}): Plugin {
|
|
23
|
-
const filter = createFilter(options.include || ['**/*'], options.exclude);
|
|
24
|
-
|
|
25
|
-
// Resolve loader path relative to this file
|
|
26
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
-
const loaderPath = path.resolve(__dirname, 'loader.js');
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
name: 'vite-plugin-cos',
|
|
31
|
-
apply: 'build',
|
|
32
|
-
enforce: 'post',
|
|
33
|
-
|
|
34
|
-
transformIndexHtml: {
|
|
35
|
-
order: 'post',
|
|
36
|
-
handler(html) {
|
|
37
|
-
// Disable standard entry script to let the COS loader handle it
|
|
38
|
-
return html.replace(
|
|
39
|
-
/<script\s+[^>]*type=["']module["'][^>]*src=["'][^"']*index[^"']*["'][^>]*><\/script>/gi,
|
|
40
|
-
'<!-- Entry script disabled by COS Plugin -->'
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
async generateBundle(_options, bundle) {
|
|
46
|
-
const managedChunks: Record<string, any> = {};
|
|
47
|
-
let mainChunk: any = null;
|
|
48
|
-
let htmlAsset: any = null;
|
|
49
|
-
|
|
50
|
-
for (const fileName in bundle) {
|
|
51
|
-
const chunk = bundle[fileName];
|
|
52
|
-
if (chunk.type === 'chunk') {
|
|
53
|
-
if (chunk.isEntry) {
|
|
54
|
-
mainChunk = chunk;
|
|
55
|
-
} else {
|
|
56
|
-
// Apply filter to determine if this chunk should be managed by COS
|
|
57
|
-
if (filter(fileName)) {
|
|
58
|
-
managedChunks[fileName] = chunk;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (fileName === 'index.html' && chunk.type === 'asset') {
|
|
63
|
-
htmlAsset = chunk;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (mainChunk) {
|
|
68
|
-
// Step 1: Assign stable global variables to managed chunks
|
|
69
|
-
// We do this BEFORE calculating hashes because the global variable names
|
|
70
|
-
// are needed for import rewriting, which changes the code, which changes the hash.
|
|
71
|
-
const chunkInfo: Record<string, { globalVar: string, chunk: any }> = {};
|
|
72
|
-
|
|
73
|
-
for (const fileName in managedChunks) {
|
|
74
|
-
// We use a hash of the filename to ensure it's a valid JS identifier and relatively short
|
|
75
|
-
// detailed: using filename hash creates a stable identifier for the lifetime of the file path
|
|
76
|
-
const nameHash = crypto.createHash('sha256').update(fileName).digest('hex').substring(0, 8);
|
|
77
|
-
chunkInfo[fileName] = {
|
|
78
|
-
globalVar: `__COS_CHUNK_${nameHash}__`,
|
|
79
|
-
chunk: managedChunks[fileName]
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Collect ALL chunks to rewrite imports in them
|
|
84
|
-
const allChunks = Object.values(bundle).filter(c => c.type === 'chunk');
|
|
85
|
-
|
|
86
|
-
// Step 2: Rewrite imports TO managed chunks in ALL chunks
|
|
87
|
-
// This modifies the importers to look for the global variable (Blob URL)
|
|
88
|
-
for (const targetChunk of allChunks) {
|
|
89
|
-
for (const fileName in chunkInfo) {
|
|
90
|
-
// Avoid self-reference
|
|
91
|
-
if (targetChunk.fileName === fileName) continue;
|
|
92
|
-
|
|
93
|
-
const { globalVar } = chunkInfo[fileName];
|
|
94
|
-
const chunkBasename = fileName.split('/').pop()!;
|
|
95
|
-
const escapedName = chunkBasename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
96
|
-
// Regex to find static imports of the managed chunk
|
|
97
|
-
const pattern = `import\\s*\\{([^}]+)\\}\\s*from\\s*['"]\\.\\/${escapedName}['"];?`;
|
|
98
|
-
const importRegex = new RegExp(pattern, 'g');
|
|
99
|
-
|
|
100
|
-
if (importRegex.test(targetChunk.code)) {
|
|
101
|
-
// Calculate relative path for fallback (in case COS loading fails or is unavailable)
|
|
102
|
-
// targetChunk.fileName is the importer (e.g. "assets/index.js")
|
|
103
|
-
// fileName is the importee (e.g. "assets/vendor.js")
|
|
104
|
-
// We need "./vendor.js", not "./assets/vendor.js"
|
|
105
|
-
let relativePath = path.relative(path.dirname(targetChunk.fileName), fileName);
|
|
106
|
-
if (!relativePath.startsWith('.')) {
|
|
107
|
-
relativePath = `./${relativePath}`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
targetChunk.code = targetChunk.code.replace(importRegex, (_match: string, bindings: string) => {
|
|
111
|
-
const destructuringPattern = bindings.split(',').map(b => {
|
|
112
|
-
const parts = b.trim().split(/\s+as\s+/);
|
|
113
|
-
return parts.length === 2 ? `${parts[0]}:${parts[1]}` : parts[0];
|
|
114
|
-
}).join(',');
|
|
115
|
-
return `const {${destructuringPattern}}=await import(window.${globalVar}||"${relativePath}");`;
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Step 3: Rewrite imports to UNMANAGED chunks in MANAGED chunks.
|
|
122
|
-
// Managed chunks are loaded via Blob URLs. Relative imports fail in Blob URLs.
|
|
123
|
-
// Absolute paths (e.g. "/assets/foo.js") work but depend on the app being at domain root.
|
|
124
|
-
// To support subdirectories (and typical "base: './'" configs), we usage dynamic imports
|
|
125
|
-
// with runtime URL resolution against `document.baseURI`.
|
|
126
|
-
for (const fileName in managedChunks) {
|
|
127
|
-
const chunk = managedChunks[fileName];
|
|
128
|
-
// Check all imports designated by Rollup
|
|
129
|
-
chunk.imports.forEach((importedFile: string) => {
|
|
130
|
-
// If the importedFile is NOT in chunkInfo, it is unmanaged.
|
|
131
|
-
if (!chunkInfo[importedFile]) {
|
|
132
|
-
const importedBasename = importedFile.split('/').pop()!;
|
|
133
|
-
const escapedName = importedBasename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
134
|
-
|
|
135
|
-
// We need to match the full import statement to rewrite it to a destructuring assignment
|
|
136
|
-
// Pattern: import { a as b, c } from "./foo.js";
|
|
137
|
-
const pattern = `import\\s*\\{([^}]+)\\}\\s*from\\s*['"]\\.\\/${escapedName}['"];?`;
|
|
138
|
-
const importRegex = new RegExp(pattern, 'g');
|
|
139
|
-
|
|
140
|
-
if (importRegex.test(chunk.code)) {
|
|
141
|
-
chunk.code = chunk.code.replace(importRegex, (_match: string, bindings: string) => {
|
|
142
|
-
const destructuringPattern = bindings.split(',').map(b => {
|
|
143
|
-
const parts = b.trim().split(/\s+as\s+/);
|
|
144
|
-
return parts.length === 2 ? `${parts[0]}:${parts[1]}` : parts[0];
|
|
145
|
-
}).join(',');
|
|
146
|
-
// Rewrite to dynamic import using document.baseURI to resolve the path correctly relative to the page
|
|
147
|
-
return `const {${destructuringPattern}}=await import(new URL("${importedFile}", document.baseURI).href);`;
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Also handle side-effect imports if any? (Likely not for vendor chunks, but good to be safe?)
|
|
152
|
-
// For now, focusing on named imports as that's what Rollup outputs for code splitting.
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Step 4: Calculate final hashes and build manifest
|
|
158
|
-
// Now that code is modified (rewritten), we calculate the hash of the ACTUAL content that will be on disk.
|
|
159
|
-
// This ensures that if the rewrite logic changes (e.g. absolute paths), the hash changes, busting the COS cache.
|
|
160
|
-
const manifest: Record<string, any> = {};
|
|
161
|
-
for (const fileName in chunkInfo) {
|
|
162
|
-
const { chunk, globalVar } = chunkInfo[fileName];
|
|
163
|
-
const finalHash = crypto.createHash('sha256').update(chunk.code).digest('hex');
|
|
164
|
-
|
|
165
|
-
manifest[fileName] = {
|
|
166
|
-
file: `/${fileName}`,
|
|
167
|
-
hash: finalHash,
|
|
168
|
-
globalVar: globalVar
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
manifest['index'] = {
|
|
173
|
-
file: `/${mainChunk.fileName}`
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// Inject loader and inlined manifest into index.html
|
|
177
|
-
if (htmlAsset) {
|
|
178
|
-
try {
|
|
179
|
-
let loaderCode = fs.readFileSync(loaderPath, 'utf-8');
|
|
180
|
-
loaderCode = loaderCode.replace('__COS_MANIFEST__', JSON.stringify(manifest));
|
|
181
|
-
|
|
182
|
-
let htmlSource = htmlAsset.source as string;
|
|
183
|
-
|
|
184
|
-
// Remove modulepreload links to avoid double fetching keys we manage
|
|
185
|
-
htmlSource = htmlSource.replace(
|
|
186
|
-
/<link\s+[^>]*rel=["']modulepreload["'][^>]*>/gi,
|
|
187
|
-
'<!-- modulepreload disabled by COS Plugin -->'
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
// Inject into head
|
|
191
|
-
htmlAsset.source = htmlSource.replace(
|
|
192
|
-
'<head>',
|
|
193
|
-
() => `<head>\n<script id="cos-loader">${loaderCode}</script>`
|
|
194
|
-
);
|
|
195
|
-
} catch (e) {
|
|
196
|
-
console.error('COS Plugin: Failed to read loader.js', e);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
}
|