vite-plugin-cross-origin-storage 1.3.17 → 1.3.19

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.js CHANGED
@@ -1668,6 +1668,7 @@ function cosPlugin(options = {}) {
1668
1668
  (c) => c.type === "chunk"
1669
1669
  );
1670
1670
  const managedChunkNames = new Set(Object.keys(managedChunks));
1671
+ const base = config.base.endsWith("/") ? config.base : config.base + "/";
1671
1672
  const managedChunkInfo = {};
1672
1673
  for (const fileName in managedChunks) {
1673
1674
  const nameHash = crypto.createHash("sha256").update(fileName).digest("hex").substring(0, 8);
@@ -1690,7 +1691,7 @@ function cosPlugin(options = {}) {
1690
1691
  /[.*+?^${}()|[\]\\]/g,
1691
1692
  "\\$&"
1692
1693
  );
1693
- const bareSpecifier = `cos-id_${depFileName}`;
1694
+ const bareSpecifier = `./${depFileName}`;
1694
1695
  const staticPattern = `(import|export)\\b\\s*((?:(?!\\bimport\\b|\\bexport\\b)[\\s\\S])*?\\bfrom\\b\\s*)?['"]${escapedRelPath}['"]\\s*;?`;
1695
1696
  const staticRegex = new RegExp(staticPattern, "g");
1696
1697
  targetChunk.code = targetChunk.code.replace(
@@ -1709,7 +1710,6 @@ function cosPlugin(options = {}) {
1709
1710
  }
1710
1711
  }
1711
1712
  const manifest = {};
1712
- const base = config.base.endsWith("/") ? config.base : config.base + "/";
1713
1713
  for (const fileName in managedChunkInfo) {
1714
1714
  const { chunk, globalVar } = managedChunkInfo[fileName];
1715
1715
  const finalHash = crypto.createHash("sha256").update(chunk.code).digest("hex");
package/dist/loader.js CHANGED
@@ -16,6 +16,8 @@
16
16
 
17
17
  // Identify managed chunks (anything with a hash)
18
18
  const chunksToLoad = Object.values(manifest).filter((item) => item.hash);
19
+ const chunksByFileName = {};
20
+ chunksToLoad.forEach(c => chunksByFileName[c.fileName] = c);
19
21
 
20
22
  async function getBlobFromCOS(hash) {
21
23
  if (!isCOSAvailable) return null;
@@ -51,93 +53,114 @@
51
53
  }
52
54
  }
53
55
 
54
- // Load all managed chunks in parallel
55
- if (chunksToLoad.length > 0) {
56
- const importMap = { imports: {} };
57
-
58
- // Prefix mapping: Handles all unmanaged chunks automatically.
59
- // Longest prefix wins in Import Maps, so specific managed entries below take precedence.
60
- // We assume all chunks are in the same relative directory as the managed ones.
61
- const firstChunk = chunksToLoad[0];
62
- const assetsDir = firstChunk.fileName.substring(
63
- 0,
64
- firstChunk.fileName.lastIndexOf('/') + 1
65
- );
66
- const assetsUrl = firstChunk.file.substring(
67
- 0,
68
- firstChunk.file.lastIndexOf('/') + 1
69
- );
70
- if (assetsDir && assetsUrl) {
71
- importMap.imports[assetsDir] = assetsUrl;
56
+ // Cache for resolved URLs
57
+ const resolvedUrls = {}; // fileName -> { blobUrl, shimUrl }
58
+ const processing = new Set();
59
+
60
+ // Cache for raw text content
61
+ const rawContent = {}; // fileName -> string
62
+
63
+ async function loadRawContent(chunk) {
64
+ if (rawContent[chunk.fileName]) return rawContent[chunk.fileName];
65
+
66
+ let blob = await getBlobFromCOS(chunk.hash);
67
+ if (!blob) {
68
+ console.log(`COS Loader: ${chunk.file} not in COS, fetching...`);
69
+ try {
70
+ const resp = await fetch(chunk.file);
71
+ if (!resp.ok) throw new Error(`Status ${resp.status}`);
72
+ blob = await resp.blob();
73
+ storeBlobInCOS(blob, chunk.hash);
74
+ } catch (e) {
75
+ console.error(`COS Loader: Failed to fetch ${chunk.file}`, e);
76
+ return null;
77
+ }
72
78
  }
79
+ const text = await blob.text();
80
+ rawContent[chunk.fileName] = text;
81
+ return text;
82
+ }
73
83
 
74
- await Promise.all(
75
- chunksToLoad.map(async (chunk) => {
76
- let url = null;
77
-
78
- const cosBlob = await getBlobFromCOS(chunk.hash);
79
- if (cosBlob) {
80
- console.log(`COS Loader: Loaded ${chunk.file} from COS!`);
81
- url = URL.createObjectURL(
82
- new Blob([cosBlob], { type: 'text/javascript' })
83
- );
84
- } else {
85
- console.log(`COS Loader: ${chunk.file} not in COS, fetching...`);
86
- try {
87
- const response = await fetch(chunk.file);
88
- if (response.ok) {
89
- const blob = await response.blob();
90
- url = URL.createObjectURL(
91
- new Blob([blob], { type: 'text/javascript' })
92
- );
93
- // Store in COS for next time
94
- storeBlobInCOS(blob, chunk.hash);
95
- } else {
96
- console.error(
97
- `COS Loader: Fetch failed with status ${response.status}`
98
- );
99
- }
100
- } catch (e) {
101
- console.error(
102
- `COS Loader: Network fetch failed for ${chunk.file}`,
103
- e
104
- );
105
- }
106
- }
107
-
108
- if (url) {
109
- // Use a Data URL shim to decouple the import graph.
110
- // This ensures that the circular dependency between React and React-DOM
111
- // doesn't cause a lockout/deadlock during the module graph instantiation.
112
- const shim = `export * from "${url}";${chunk.hasDefault ? `export { default } from "${url}";` : ''}`;
113
- const shimUrl = `data:text/javascript;base64,${btoa(shim)}`;
114
-
115
- // Map the virtual bare specifier to the shim
116
- importMap.imports[`cos-id_${chunk.fileName}`] = shimUrl;
117
-
118
- // Also set global if anyone still needs it (legacy)
119
- if (chunk.globalVar) {
120
- window[chunk.globalVar] = url;
121
- }
122
- }
123
- })
124
- );
125
-
126
- // Inject the importmap
127
- if (Object.keys(importMap.imports).length > 0) {
128
- const imScript = document.createElement('script');
129
- imScript.type = 'importmap';
130
- imScript.textContent = JSON.stringify(importMap);
131
- document.head.appendChild(imScript);
84
+ async function resolveChunk(fileName) {
85
+ if (resolvedUrls[fileName]) return resolvedUrls[fileName];
86
+ if (processing.has(fileName)) {
87
+ console.warn(`COS Loader: Circular dependency detected for ${fileName}. Breaking cycle with placeholder (may fail).`);
88
+ // We cannot solve cycles with Direct Data Injection easily.
89
+ // Returning null might cause failure, but we hope merging chunks avoided this.
90
+ return null;
132
91
  }
92
+
93
+ processing.add(fileName);
94
+ const chunk = chunksByFileName[fileName];
95
+ if (!chunk) {
96
+ // Unmanaged dependency? Should have been handled by absolute/base logic but we reverted to ./
97
+ // If it's ./unmanaged.js, we don't have it in manifest.
98
+ // We can't inject it.
99
+ console.warn(`COS Loader: Unknown dependency ${fileName}`);
100
+ return null;
101
+ }
102
+
103
+ let code = await loadRawContent(chunk);
104
+ if (!code) return null;
105
+
106
+ // Find dependencies in the code: import ... from "./dep.js"
107
+ // Regex matches the one used in build: `from "./..."`
108
+ const depRegex = /from\s+['"]\.\/([^'"]+)['"]/g;
109
+ let match;
110
+ const deps = new Set();
111
+ while ((match = depRegex.exec(code)) !== null) {
112
+ deps.add(match[1]); // The filename relative to current
113
+ }
114
+
115
+ // Resolve dependencies recursively
116
+ const replacements = [];
117
+ for (const depName of deps) {
118
+ const res = await resolveChunk(depName);
119
+ if (res && res.shimUrl) {
120
+ replacements.push({ depName, url: res.shimUrl });
121
+ }
122
+ }
123
+
124
+ // Replace in code
125
+ for (const { depName, url } of replacements) {
126
+ // Replace ALL occurrences
127
+ // We need to be careful with regex replacement safer:
128
+ // Replace `from "./depName"` with `from "url"`
129
+ code = code.split(`from "./${depName}"`).join(`from "${url}"`);
130
+ code = code.split(`from './${depName}'`).join(`from "${url}"`);
131
+
132
+ // Also dynamic imports: import("./depName")
133
+ code = code.split(`import("./${depName}")`).join(`import("${url}")`);
134
+ code = code.split(`import('./${depName}')`).join(`import("${url}")`);
135
+ }
136
+
137
+ const blob = new Blob([code], { type: 'text/javascript' });
138
+ const blobUrl = URL.createObjectURL(blob);
139
+
140
+ // Create Shim
141
+ const shim = `export * from "${blobUrl}";${chunk.hasDefault ? `export { default } from "${blobUrl}";` : ''}`;
142
+ const shimUrl = `data:text/javascript;base64,${btoa(shim)}`;
143
+
144
+ resolvedUrls[fileName] = { blobUrl, shimUrl };
145
+ processing.delete(fileName);
146
+ return resolvedUrls[fileName];
133
147
  }
134
148
 
135
- // Start App
149
+ // Initialize
136
150
  try {
137
151
  console.log('COS Loader: Starting app...');
138
- // Ensure the importmap is registered before importing
139
- await new Promise((resolve) => setTimeout(resolve, 0));
140
- await import(mainEntry.file);
152
+ // Resolve main entry
153
+ const entryFileName = mainEntry.fileName;
154
+ // We assume mainEntry doesn't need to be shimmed for ITSELF, but its deps do.
155
+ // Actually resolveChunk returns the shimUrl.
156
+ const res = await resolveChunk(entryFileName);
157
+
158
+ if (res) {
159
+ // Import the SHIM of the main entry
160
+ await import(res.shimUrl);
161
+ } else {
162
+ console.error('COS Loader: Failed to resolve main entry');
163
+ }
141
164
  } catch (err) {
142
165
  console.error('COS Loader: Failed to start app', err);
143
166
  }
package/loader.js CHANGED
@@ -16,6 +16,8 @@
16
16
 
17
17
  // Identify managed chunks (anything with a hash)
18
18
  const chunksToLoad = Object.values(manifest).filter((item) => item.hash);
19
+ const chunksByFileName = {};
20
+ chunksToLoad.forEach(c => chunksByFileName[c.fileName] = c);
19
21
 
20
22
  async function getBlobFromCOS(hash) {
21
23
  if (!isCOSAvailable) return null;
@@ -51,93 +53,114 @@
51
53
  }
52
54
  }
53
55
 
54
- // Load all managed chunks in parallel
55
- if (chunksToLoad.length > 0) {
56
- const importMap = { imports: {} };
57
-
58
- // Prefix mapping: Handles all unmanaged chunks automatically.
59
- // Longest prefix wins in Import Maps, so specific managed entries below take precedence.
60
- // We assume all chunks are in the same relative directory as the managed ones.
61
- const firstChunk = chunksToLoad[0];
62
- const assetsDir = firstChunk.fileName.substring(
63
- 0,
64
- firstChunk.fileName.lastIndexOf('/') + 1
65
- );
66
- const assetsUrl = firstChunk.file.substring(
67
- 0,
68
- firstChunk.file.lastIndexOf('/') + 1
69
- );
70
- if (assetsDir && assetsUrl) {
71
- importMap.imports[assetsDir] = assetsUrl;
56
+ // Cache for resolved URLs
57
+ const resolvedUrls = {}; // fileName -> { blobUrl, shimUrl }
58
+ const processing = new Set();
59
+
60
+ // Cache for raw text content
61
+ const rawContent = {}; // fileName -> string
62
+
63
+ async function loadRawContent(chunk) {
64
+ if (rawContent[chunk.fileName]) return rawContent[chunk.fileName];
65
+
66
+ let blob = await getBlobFromCOS(chunk.hash);
67
+ if (!blob) {
68
+ console.log(`COS Loader: ${chunk.file} not in COS, fetching...`);
69
+ try {
70
+ const resp = await fetch(chunk.file);
71
+ if (!resp.ok) throw new Error(`Status ${resp.status}`);
72
+ blob = await resp.blob();
73
+ storeBlobInCOS(blob, chunk.hash);
74
+ } catch (e) {
75
+ console.error(`COS Loader: Failed to fetch ${chunk.file}`, e);
76
+ return null;
77
+ }
72
78
  }
79
+ const text = await blob.text();
80
+ rawContent[chunk.fileName] = text;
81
+ return text;
82
+ }
73
83
 
74
- await Promise.all(
75
- chunksToLoad.map(async (chunk) => {
76
- let url = null;
77
-
78
- const cosBlob = await getBlobFromCOS(chunk.hash);
79
- if (cosBlob) {
80
- console.log(`COS Loader: Loaded ${chunk.file} from COS!`);
81
- url = URL.createObjectURL(
82
- new Blob([cosBlob], { type: 'text/javascript' })
83
- );
84
- } else {
85
- console.log(`COS Loader: ${chunk.file} not in COS, fetching...`);
86
- try {
87
- const response = await fetch(chunk.file);
88
- if (response.ok) {
89
- const blob = await response.blob();
90
- url = URL.createObjectURL(
91
- new Blob([blob], { type: 'text/javascript' })
92
- );
93
- // Store in COS for next time
94
- storeBlobInCOS(blob, chunk.hash);
95
- } else {
96
- console.error(
97
- `COS Loader: Fetch failed with status ${response.status}`
98
- );
99
- }
100
- } catch (e) {
101
- console.error(
102
- `COS Loader: Network fetch failed for ${chunk.file}`,
103
- e
104
- );
105
- }
106
- }
107
-
108
- if (url) {
109
- // Use a Data URL shim to decouple the import graph.
110
- // This ensures that the circular dependency between React and React-DOM
111
- // doesn't cause a lockout/deadlock during the module graph instantiation.
112
- const shim = `export * from "${url}";${chunk.hasDefault ? `export { default } from "${url}";` : ''}`;
113
- const shimUrl = `data:text/javascript;base64,${btoa(shim)}`;
114
-
115
- // Map the virtual bare specifier to the shim
116
- importMap.imports[`cos-id_${chunk.fileName}`] = shimUrl;
117
-
118
- // Also set global if anyone still needs it (legacy)
119
- if (chunk.globalVar) {
120
- window[chunk.globalVar] = url;
121
- }
122
- }
123
- })
124
- );
125
-
126
- // Inject the importmap
127
- if (Object.keys(importMap.imports).length > 0) {
128
- const imScript = document.createElement('script');
129
- imScript.type = 'importmap';
130
- imScript.textContent = JSON.stringify(importMap);
131
- document.head.appendChild(imScript);
84
+ async function resolveChunk(fileName) {
85
+ if (resolvedUrls[fileName]) return resolvedUrls[fileName];
86
+ if (processing.has(fileName)) {
87
+ console.warn(`COS Loader: Circular dependency detected for ${fileName}. Breaking cycle with placeholder (may fail).`);
88
+ // We cannot solve cycles with Direct Data Injection easily.
89
+ // Returning null might cause failure, but we hope merging chunks avoided this.
90
+ return null;
132
91
  }
92
+
93
+ processing.add(fileName);
94
+ const chunk = chunksByFileName[fileName];
95
+ if (!chunk) {
96
+ // Unmanaged dependency? Should have been handled by absolute/base logic but we reverted to ./
97
+ // If it's ./unmanaged.js, we don't have it in manifest.
98
+ // We can't inject it.
99
+ console.warn(`COS Loader: Unknown dependency ${fileName}`);
100
+ return null;
101
+ }
102
+
103
+ let code = await loadRawContent(chunk);
104
+ if (!code) return null;
105
+
106
+ // Find dependencies in the code: import ... from "./dep.js"
107
+ // Regex matches the one used in build: `from "./..."`
108
+ const depRegex = /from\s+['"]\.\/([^'"]+)['"]/g;
109
+ let match;
110
+ const deps = new Set();
111
+ while ((match = depRegex.exec(code)) !== null) {
112
+ deps.add(match[1]); // The filename relative to current
113
+ }
114
+
115
+ // Resolve dependencies recursively
116
+ const replacements = [];
117
+ for (const depName of deps) {
118
+ const res = await resolveChunk(depName);
119
+ if (res && res.shimUrl) {
120
+ replacements.push({ depName, url: res.shimUrl });
121
+ }
122
+ }
123
+
124
+ // Replace in code
125
+ for (const { depName, url } of replacements) {
126
+ // Replace ALL occurrences
127
+ // We need to be careful with regex replacement safer:
128
+ // Replace `from "./depName"` with `from "url"`
129
+ code = code.split(`from "./${depName}"`).join(`from "${url}"`);
130
+ code = code.split(`from './${depName}'`).join(`from "${url}"`);
131
+
132
+ // Also dynamic imports: import("./depName")
133
+ code = code.split(`import("./${depName}")`).join(`import("${url}")`);
134
+ code = code.split(`import('./${depName}')`).join(`import("${url}")`);
135
+ }
136
+
137
+ const blob = new Blob([code], { type: 'text/javascript' });
138
+ const blobUrl = URL.createObjectURL(blob);
139
+
140
+ // Create Shim
141
+ const shim = `export * from "${blobUrl}";${chunk.hasDefault ? `export { default } from "${blobUrl}";` : ''}`;
142
+ const shimUrl = `data:text/javascript;base64,${btoa(shim)}`;
143
+
144
+ resolvedUrls[fileName] = { blobUrl, shimUrl };
145
+ processing.delete(fileName);
146
+ return resolvedUrls[fileName];
133
147
  }
134
148
 
135
- // Start App
149
+ // Initialize
136
150
  try {
137
151
  console.log('COS Loader: Starting app...');
138
- // Ensure the importmap is registered before importing
139
- await new Promise((resolve) => setTimeout(resolve, 0));
140
- await import(mainEntry.file);
152
+ // Resolve main entry
153
+ const entryFileName = mainEntry.fileName;
154
+ // We assume mainEntry doesn't need to be shimmed for ITSELF, but its deps do.
155
+ // Actually resolveChunk returns the shimUrl.
156
+ const res = await resolveChunk(entryFileName);
157
+
158
+ if (res) {
159
+ // Import the SHIM of the main entry
160
+ await import(res.shimUrl);
161
+ } else {
162
+ console.error('COS Loader: Failed to resolve main entry');
163
+ }
141
164
  } catch (err) {
142
165
  console.error('COS Loader: Failed to start app', err);
143
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-cross-origin-storage",
3
- "version": "1.3.17",
3
+ "version": "1.3.19",
4
4
  "description": "Vite plugin to load chunks from Cross-Origin Storage",
5
5
  "keywords": [
6
6
  "vite",