vite-plugin-cross-origin-storage 1.0.0 → 1.1.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/package.json CHANGED
@@ -1,17 +1,34 @@
1
1
  {
2
2
  "name": "vite-plugin-cross-origin-storage",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Vite plugin to load chunks from Cross-Origin Storage",
5
5
  "type": "module",
6
- "main": "index.ts",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
7
16
  "files": [
8
- "index.ts",
17
+ "dist",
9
18
  "loader.js"
10
19
  ],
20
+ "scripts": {
21
+ "build": "tsup index.ts --format esm --dts --clean"
22
+ },
11
23
  "peerDependencies": {
12
24
  "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
13
25
  },
14
- "dependencies": {
15
- "@rollup/pluginutils": "^5.3.0"
26
+ "devDependencies": {
27
+ "@rollup/pluginutils": "^5.3.0",
28
+ "@types/node": "^25.0.9",
29
+ "rollup": "^4.55.2",
30
+ "tsup": "^8.5.1",
31
+ "typescript": "^5.9.3",
32
+ "vite": "^7.3.1"
16
33
  }
17
34
  }
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
- }