vite-plugin-cross-origin-storage 1.3.11 → 1.3.13
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/README.md +43 -21
- package/dist/index.js +25 -19
- package/dist/loader.js +59 -35
- package/loader.js +59 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
# vite-plugin-cross-origin-storage
|
|
2
2
|
|
|
3
|
-
A Vite plugin to cache and load static assets (chunks) using the
|
|
3
|
+
A Vite plugin to cache and load static assets (chunks) using the
|
|
4
|
+
[Cross-Origin Storage (COS) API](https://github.com/WICG/cross-origin-storage).
|
|
4
5
|
|
|
5
|
-
This plugin progressively enhances your application by attempting to load vendor
|
|
6
|
+
This plugin progressively enhances your application by attempting to load vendor
|
|
7
|
+
chunks and other assets from a shared Cross-Origin Storage, reducing bandwidth
|
|
8
|
+
usage and improving load times across different sites that share common
|
|
9
|
+
dependencies.
|
|
6
10
|
|
|
7
11
|
## Features
|
|
8
12
|
|
|
9
|
-
- **Automatic Import Rewriting**: Rewrites static imports to use COS-loaded Blob
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
|
|
13
|
-
- **
|
|
13
|
+
- **Automatic Import Rewriting**: Rewrites static imports to use COS-loaded Blob
|
|
14
|
+
URLs when available.
|
|
15
|
+
- **Network Fallback**: Gracefully falls back to standard network requests if
|
|
16
|
+
COS is unavailable or the asset is missing.
|
|
17
|
+
- **Smart Caching**: Automatically stores fetched assets into COS for future
|
|
18
|
+
use.
|
|
19
|
+
- **Configurable**: Easily include or exclude specific chunks using glob
|
|
20
|
+
patterns.
|
|
21
|
+
- **Runtime Loader**: Injects a lightweight loader to handle COS interactions
|
|
22
|
+
transparently.
|
|
14
23
|
|
|
15
24
|
## Installation
|
|
16
25
|
|
|
@@ -38,14 +47,16 @@ export default defineConfig({
|
|
|
38
47
|
|
|
39
48
|
## Configuration
|
|
40
49
|
|
|
41
|
-
| Option
|
|
42
|
-
|
|
|
43
|
-
| `include` | `string \| RegExp \| Array` | `['**/*']`
|
|
44
|
-
| `exclude` | `string \| RegExp \| Array` | `undefined` | Pattern to exclude chunks from being managed.
|
|
50
|
+
| Option | Type | Default | Description |
|
|
51
|
+
| :-------- | :-------------------------- | :---------- | :---------------------------------------------- |
|
|
52
|
+
| `include` | `string \| RegExp \| Array` | `['**/*']` | Pattern to include chunks to be managed by COS. |
|
|
53
|
+
| `exclude` | `string \| RegExp \| Array` | `undefined` | Pattern to exclude chunks from being managed. |
|
|
45
54
|
|
|
46
55
|
## Recipe: Granular Vendor Splitting
|
|
47
56
|
|
|
48
|
-
To maximize caching benefits, it is recommended to split your `node_modules`
|
|
57
|
+
To maximize caching benefits, it is recommended to split your `node_modules`
|
|
58
|
+
dependencies into separate chunks. This ensures that updates to one package
|
|
59
|
+
(e.g., `react`) do not invalidate the cache for others (e.g., `lodash`).
|
|
49
60
|
|
|
50
61
|
Add the following `manualChunks` configuration to your `vite.config.ts`:
|
|
51
62
|
|
|
@@ -64,7 +75,9 @@ export default defineConfig({
|
|
|
64
75
|
// e.g. "node_modules/react/..." -> "vendor-react"
|
|
65
76
|
// e.g. "node_modules/@scope/pkg/..." -> "vendor-scope-pkg"
|
|
66
77
|
const parts = id.split('node_modules/')[1].split('/');
|
|
67
|
-
const packageName = parts[0].startsWith('@')
|
|
78
|
+
const packageName = parts[0].startsWith('@')
|
|
79
|
+
? `${parts[0]}/${parts[1]}`
|
|
80
|
+
: parts[0];
|
|
68
81
|
return `vendor-${packageName.replace('@', '').replace('/', '-')}`;
|
|
69
82
|
}
|
|
70
83
|
},
|
|
@@ -83,21 +96,30 @@ export default defineConfig({
|
|
|
83
96
|
## How It Works
|
|
84
97
|
|
|
85
98
|
1. **Build Time**:
|
|
86
|
-
- The plugin analyzes your bundle and identifies chunks matching the
|
|
99
|
+
- The plugin analyzes your bundle and identifies chunks matching the
|
|
100
|
+
`include` pattern.
|
|
87
101
|
- It generates a stable hash for each managed chunk.
|
|
88
|
-
- It rewrites imports in your code to look for a global variable (e.g.,
|
|
89
|
-
|
|
102
|
+
- It rewrites imports in your code to look for a global variable (e.g.,
|
|
103
|
+
`window.__COS_CHUNK_...`) containing the Blob URL of the chunk, falling
|
|
104
|
+
back to the relative network path if the variable is unset.
|
|
105
|
+
- It disables the default `<script type="module" src="...">` entry point in
|
|
106
|
+
your `index.html` and injects a custom `loader.js`.
|
|
90
107
|
|
|
91
108
|
2. **Runtime**:
|
|
92
109
|
- The injected loader checks for `navigator.crossOriginStorage`.
|
|
93
|
-
- If supported, it requests the file handle for each managed chunk using its
|
|
94
|
-
|
|
95
|
-
- **Cache
|
|
96
|
-
|
|
110
|
+
- If supported, it requests the file handle for each managed chunk using its
|
|
111
|
+
hash.
|
|
112
|
+
- **Cache Hit**: If found, it creates a Blob URL and assigns it to the
|
|
113
|
+
corresponding global variable.
|
|
114
|
+
- **Cache Miss**: If not found, it fetches the file from the network, stores
|
|
115
|
+
it in COS, and then creates the Blob URL.
|
|
116
|
+
- Finally, the loader imports your application's entry point, which now
|
|
117
|
+
seamlessly uses the cached assets.
|
|
97
118
|
|
|
98
119
|
## Requirements
|
|
99
120
|
|
|
100
|
-
- A browser with `Cross-Origin Storage` support (or a
|
|
121
|
+
- A browser with `Cross-Origin Storage` support (or a
|
|
122
|
+
[browser extension](https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih)).
|
|
101
123
|
|
|
102
124
|
## License
|
|
103
125
|
|
package/dist/index.js
CHANGED
|
@@ -1651,7 +1651,9 @@ function cosPlugin(options = {}) {
|
|
|
1651
1651
|
mainChunk = chunk;
|
|
1652
1652
|
} else {
|
|
1653
1653
|
const res = filter(fileName);
|
|
1654
|
-
console.log(
|
|
1654
|
+
console.log(
|
|
1655
|
+
`COS Plugin: [FILTER] ${fileName} -> ${res ? "INCLUDE" : "SKIP"}`
|
|
1656
|
+
);
|
|
1655
1657
|
if (res) {
|
|
1656
1658
|
managedChunks[fileName] = chunk;
|
|
1657
1659
|
}
|
|
@@ -1662,7 +1664,9 @@ function cosPlugin(options = {}) {
|
|
|
1662
1664
|
}
|
|
1663
1665
|
}
|
|
1664
1666
|
if (mainChunk) {
|
|
1665
|
-
const allChunks = Object.values(bundle).filter(
|
|
1667
|
+
const allChunks = Object.values(bundle).filter(
|
|
1668
|
+
(c) => c.type === "chunk"
|
|
1669
|
+
);
|
|
1666
1670
|
const managedChunkNames = new Set(Object.keys(managedChunks));
|
|
1667
1671
|
const managedChunkInfo = {};
|
|
1668
1672
|
for (const fileName in managedChunks) {
|
|
@@ -1682,26 +1686,25 @@ function cosPlugin(options = {}) {
|
|
|
1682
1686
|
if (isTargetManaged || isDepManaged) {
|
|
1683
1687
|
let relPath = path.relative(importerDir, depFileName);
|
|
1684
1688
|
if (!relPath.startsWith(".")) relPath = "./" + relPath;
|
|
1685
|
-
const escapedRelPath = relPath.replace(
|
|
1689
|
+
const escapedRelPath = relPath.replace(
|
|
1690
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
1691
|
+
"\\$&"
|
|
1692
|
+
);
|
|
1686
1693
|
const bareSpecifier = depFileName;
|
|
1687
|
-
const staticPattern = `import\\s*(?:(
|
|
1694
|
+
const staticPattern = `(import|export)\\b\\s*((?:(?!\\bimport\\b|\\bexport\\b)[\\s\\S])*?\\bfrom\\b\\s*)?['"]${escapedRelPath}['"]\\s*;?`;
|
|
1688
1695
|
const staticRegex = new RegExp(staticPattern, "g");
|
|
1689
|
-
targetChunk.code = targetChunk.code.replace(
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1696
|
+
targetChunk.code = targetChunk.code.replace(
|
|
1697
|
+
staticRegex,
|
|
1698
|
+
(match, keyword, fromPart) => {
|
|
1699
|
+
return `${keyword}${fromPart ? " " + fromPart : " "}"${bareSpecifier}";`;
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1695
1702
|
const dynamicPattern = `import\\s*\\(\\s*['"]${escapedRelPath}['"]\\s*\\)`;
|
|
1696
1703
|
const dynamicRegex = new RegExp(dynamicPattern, "g");
|
|
1697
|
-
targetChunk.code = targetChunk.code.replace(
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
if (named) return `export {${named}} from "${bareSpecifier}";`;
|
|
1702
|
-
if (namespace) return `export * as ${namespace} from "${bareSpecifier}";`;
|
|
1703
|
-
return `export * from "${bareSpecifier}";`;
|
|
1704
|
-
});
|
|
1704
|
+
targetChunk.code = targetChunk.code.replace(
|
|
1705
|
+
dynamicRegex,
|
|
1706
|
+
() => `import("${bareSpecifier}")`
|
|
1707
|
+
);
|
|
1705
1708
|
}
|
|
1706
1709
|
}
|
|
1707
1710
|
}
|
|
@@ -1725,7 +1728,10 @@ function cosPlugin(options = {}) {
|
|
|
1725
1728
|
if (htmlAsset) {
|
|
1726
1729
|
try {
|
|
1727
1730
|
let loaderCode = fs.readFileSync(loaderPath, "utf-8");
|
|
1728
|
-
loaderCode = loaderCode.replace(
|
|
1731
|
+
loaderCode = loaderCode.replace(
|
|
1732
|
+
"__COS_MANIFEST__",
|
|
1733
|
+
JSON.stringify(manifest)
|
|
1734
|
+
);
|
|
1729
1735
|
let htmlSource = htmlAsset.source;
|
|
1730
1736
|
htmlSource = htmlSource.replace(
|
|
1731
1737
|
/<link\s+[^>]*rel=["']modulepreload["'][^>]*>/gi,
|
package/dist/loader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
(async function () {
|
|
2
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3
3
|
|
|
4
4
|
const isCOSAvailable = 'crossOriginStorage' in navigator;
|
|
5
5
|
console.log('COS Loader: isCOSAvailable =', isCOSAvailable);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Identify managed chunks (anything with a hash)
|
|
18
|
-
const chunksToLoad = Object.values(manifest).filter(item => item.hash);
|
|
18
|
+
const chunksToLoad = Object.values(manifest).filter((item) => item.hash);
|
|
19
19
|
|
|
20
20
|
async function getBlobFromCOS(hash) {
|
|
21
21
|
if (!isCOSAvailable) return null;
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
return await handles[0].getFile();
|
|
28
28
|
}
|
|
29
29
|
} catch (err) {
|
|
30
|
-
if (err.name !== 'NotFoundError')
|
|
30
|
+
if (err.name !== 'NotFoundError')
|
|
31
|
+
console.error('COS Loader: Error checking COS', err);
|
|
31
32
|
}
|
|
32
33
|
return null;
|
|
33
34
|
}
|
|
@@ -58,48 +59,69 @@
|
|
|
58
59
|
// Longest prefix wins in Import Maps, so specific managed entries below take precedence.
|
|
59
60
|
// We assume all chunks are in the same relative directory as the managed ones.
|
|
60
61
|
const firstChunk = chunksToLoad[0];
|
|
61
|
-
const assetsDir = firstChunk.fileName.substring(
|
|
62
|
-
|
|
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
|
+
);
|
|
63
70
|
if (assetsDir && assetsUrl) {
|
|
64
71
|
importMap.imports[assetsDir] = assetsUrl;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
await Promise.all(
|
|
68
|
-
|
|
74
|
+
await Promise.all(
|
|
75
|
+
chunksToLoad.map(async (chunk) => {
|
|
76
|
+
let url = null;
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
);
|
|
85
105
|
}
|
|
86
|
-
} catch (e) {
|
|
87
|
-
console.error(`COS Loader: Network fetch failed for ${chunk.file}`, e);
|
|
88
106
|
}
|
|
89
|
-
}
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
importMap.imports[chunk.fileName] = shimUrl;
|
|
116
|
+
importMap.imports[chunk.file] = shimUrl;
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
// Also set global if anyone still needs it (legacy)
|
|
119
|
+
if (chunk.globalVar) {
|
|
120
|
+
window[chunk.globalVar] = url;
|
|
121
|
+
}
|
|
100
122
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
123
|
+
})
|
|
124
|
+
);
|
|
103
125
|
|
|
104
126
|
// Inject the importmap
|
|
105
127
|
if (Object.keys(importMap.imports).length > 0) {
|
|
@@ -113,6 +135,8 @@
|
|
|
113
135
|
// Start App
|
|
114
136
|
try {
|
|
115
137
|
console.log('COS Loader: Starting app...');
|
|
138
|
+
// Ensure the importmap is registered before importing
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
116
140
|
await import(mainEntry.file);
|
|
117
141
|
} catch (err) {
|
|
118
142
|
console.error('COS Loader: Failed to start app', err);
|
package/loader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
(async function () {
|
|
2
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3
3
|
|
|
4
4
|
const isCOSAvailable = 'crossOriginStorage' in navigator;
|
|
5
5
|
console.log('COS Loader: isCOSAvailable =', isCOSAvailable);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// Identify managed chunks (anything with a hash)
|
|
18
|
-
const chunksToLoad = Object.values(manifest).filter(item => item.hash);
|
|
18
|
+
const chunksToLoad = Object.values(manifest).filter((item) => item.hash);
|
|
19
19
|
|
|
20
20
|
async function getBlobFromCOS(hash) {
|
|
21
21
|
if (!isCOSAvailable) return null;
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
return await handles[0].getFile();
|
|
28
28
|
}
|
|
29
29
|
} catch (err) {
|
|
30
|
-
if (err.name !== 'NotFoundError')
|
|
30
|
+
if (err.name !== 'NotFoundError')
|
|
31
|
+
console.error('COS Loader: Error checking COS', err);
|
|
31
32
|
}
|
|
32
33
|
return null;
|
|
33
34
|
}
|
|
@@ -58,48 +59,69 @@
|
|
|
58
59
|
// Longest prefix wins in Import Maps, so specific managed entries below take precedence.
|
|
59
60
|
// We assume all chunks are in the same relative directory as the managed ones.
|
|
60
61
|
const firstChunk = chunksToLoad[0];
|
|
61
|
-
const assetsDir = firstChunk.fileName.substring(
|
|
62
|
-
|
|
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
|
+
);
|
|
63
70
|
if (assetsDir && assetsUrl) {
|
|
64
71
|
importMap.imports[assetsDir] = assetsUrl;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
await Promise.all(
|
|
68
|
-
|
|
74
|
+
await Promise.all(
|
|
75
|
+
chunksToLoad.map(async (chunk) => {
|
|
76
|
+
let url = null;
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
);
|
|
85
105
|
}
|
|
86
|
-
} catch (e) {
|
|
87
|
-
console.error(`COS Loader: Network fetch failed for ${chunk.file}`, e);
|
|
88
106
|
}
|
|
89
|
-
}
|
|
90
107
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
importMap.imports[chunk.fileName] = shimUrl;
|
|
116
|
+
importMap.imports[chunk.file] = shimUrl;
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
// Also set global if anyone still needs it (legacy)
|
|
119
|
+
if (chunk.globalVar) {
|
|
120
|
+
window[chunk.globalVar] = url;
|
|
121
|
+
}
|
|
100
122
|
}
|
|
101
|
-
}
|
|
102
|
-
|
|
123
|
+
})
|
|
124
|
+
);
|
|
103
125
|
|
|
104
126
|
// Inject the importmap
|
|
105
127
|
if (Object.keys(importMap.imports).length > 0) {
|
|
@@ -113,6 +135,8 @@
|
|
|
113
135
|
// Start App
|
|
114
136
|
try {
|
|
115
137
|
console.log('COS Loader: Starting app...');
|
|
138
|
+
// Ensure the importmap is registered before importing
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
116
140
|
await import(mainEntry.file);
|
|
117
141
|
} catch (err) {
|
|
118
142
|
console.error('COS Loader: Failed to start app', err);
|