vite-plugin-cross-origin-storage 1.6.0 → 1.8.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/README.md CHANGED
@@ -95,26 +95,39 @@ export default defineConfig({
95
95
 
96
96
  ## How It Works
97
97
 
98
- 1. **Build Time**:
99
- - The plugin analyzes your bundle and identifies chunks matching the
100
- `include` pattern.
101
- - It generates a stable hash for each managed chunk.
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`.
107
-
108
- 2. **Runtime**:
109
- - The injected loader checks for `navigator.crossOriginStorage`.
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.
98
+ ### Build Time
99
+
100
+ 1. **Magic Externals**: For `include` patterns that resolve to npm package
101
+ names (e.g. `vendor-react` `react`), the plugin externalizes the package
102
+ from Rollup and uses esbuild to produce a single self-contained ESM bundle.
103
+ This bundle is emitted as a hashed asset (e.g. `assets/react-a1b2c3d4.js`)
104
+ and treated as a managed chunk.
105
+ 2. **Import Rewriting**: All inter-chunk imports are rewritten from relative
106
+ paths (`"./chunk.js"`) to bare specifiers (`"coschunk-assets-chunk-js"`).
107
+ This applies to both managed and unmanaged chunks, because managed chunks
108
+ run as Blob URLs and cannot resolve relative paths at runtime.
109
+ 3. **Manifest Generation**: A JSON manifest is built containing the base path,
110
+ the entry chunk filename, a map of every managed chunk filename to its
111
+ SHA-256 hash, and a list of unmanaged chunks that need network-URL entries
112
+ in the import map.
113
+ 4. **Loader Injection**: The plugin disables the default
114
+ `<script type="module">` entry tag and injects a `<script id="cos-loader">`
115
+ containing the runtime loader with the manifest inlined.
116
+
117
+ ### Runtime
118
+
119
+ 1. The loader checks for `navigator.crossOriginStorage`.
120
+ 2. For each managed chunk it calls
121
+ `navigator.crossOriginStorage.requestFileHandles([{ algorithm: 'SHA-256', value: hash }])`:
122
+ - **Cache hit**: wraps the returned `File` as a Blob URL.
123
+ - **Cache miss**: fetches the chunk from the network, stores it in COS via
124
+ `requestFileHandles(..., { create: true })`, then wraps it as a Blob URL.
125
+ 3. Unmanaged chunks receive absolute network URLs.
126
+ 4. An `<script type="importmap">` is injected into `<head>` mapping every
127
+ `coschunk-*` bare specifier to its resolved URL.
128
+ 5. The entry point is imported via its bare specifier inside a `setTimeout`
129
+ callback so the browser has time to register the import map before the
130
+ first module resolution occurs.
118
131
 
119
132
  ## Requirements
120
133
 
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var require_constants = __commonJS({
30
30
  "use strict";
31
31
  var WIN_SLASH = "\\\\/";
32
32
  var WIN_NO_SLASH = `[^${WIN_SLASH}]`;
33
+ var DEFAULT_MAX_EXTGLOB_RECURSION = 0;
33
34
  var DOT_LITERAL = "\\.";
34
35
  var PLUS_LITERAL = "\\+";
35
36
  var QMARK_LITERAL = "\\?";
@@ -80,6 +81,7 @@ var require_constants = __commonJS({
80
81
  SEP: "\\"
81
82
  };
82
83
  var POSIX_REGEX_SOURCE = {
84
+ __proto__: null,
83
85
  alnum: "a-zA-Z0-9",
84
86
  alpha: "a-zA-Z",
85
87
  ascii: "\\x00-\\x7F",
@@ -96,6 +98,7 @@ var require_constants = __commonJS({
96
98
  xdigit: "A-Fa-f0-9"
97
99
  };
98
100
  module.exports = {
101
+ DEFAULT_MAX_EXTGLOB_RECURSION,
99
102
  MAX_LENGTH: 1024 * 64,
100
103
  POSIX_REGEX_SOURCE,
101
104
  // regular expressions
@@ -646,6 +649,213 @@ var require_parse = __commonJS({
646
649
  var syntaxError = (type, char) => {
647
650
  return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
648
651
  };
652
+ var splitTopLevel = (input) => {
653
+ const parts = [];
654
+ let bracket = 0;
655
+ let paren = 0;
656
+ let quote = 0;
657
+ let value = "";
658
+ let escaped = false;
659
+ for (const ch of input) {
660
+ if (escaped === true) {
661
+ value += ch;
662
+ escaped = false;
663
+ continue;
664
+ }
665
+ if (ch === "\\") {
666
+ value += ch;
667
+ escaped = true;
668
+ continue;
669
+ }
670
+ if (ch === '"') {
671
+ quote = quote === 1 ? 0 : 1;
672
+ value += ch;
673
+ continue;
674
+ }
675
+ if (quote === 0) {
676
+ if (ch === "[") {
677
+ bracket++;
678
+ } else if (ch === "]" && bracket > 0) {
679
+ bracket--;
680
+ } else if (bracket === 0) {
681
+ if (ch === "(") {
682
+ paren++;
683
+ } else if (ch === ")" && paren > 0) {
684
+ paren--;
685
+ } else if (ch === "|" && paren === 0) {
686
+ parts.push(value);
687
+ value = "";
688
+ continue;
689
+ }
690
+ }
691
+ }
692
+ value += ch;
693
+ }
694
+ parts.push(value);
695
+ return parts;
696
+ };
697
+ var isPlainBranch = (branch) => {
698
+ let escaped = false;
699
+ for (const ch of branch) {
700
+ if (escaped === true) {
701
+ escaped = false;
702
+ continue;
703
+ }
704
+ if (ch === "\\") {
705
+ escaped = true;
706
+ continue;
707
+ }
708
+ if (/[?*+@!()[\]{}]/.test(ch)) {
709
+ return false;
710
+ }
711
+ }
712
+ return true;
713
+ };
714
+ var normalizeSimpleBranch = (branch) => {
715
+ let value = branch.trim();
716
+ let changed = true;
717
+ while (changed === true) {
718
+ changed = false;
719
+ if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
720
+ value = value.slice(2, -1);
721
+ changed = true;
722
+ }
723
+ }
724
+ if (!isPlainBranch(value)) {
725
+ return;
726
+ }
727
+ return value.replace(/\\(.)/g, "$1");
728
+ };
729
+ var hasRepeatedCharPrefixOverlap = (branches) => {
730
+ const values = branches.map(normalizeSimpleBranch).filter(Boolean);
731
+ for (let i = 0; i < values.length; i++) {
732
+ for (let j = i + 1; j < values.length; j++) {
733
+ const a = values[i];
734
+ const b = values[j];
735
+ const char = a[0];
736
+ if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
737
+ continue;
738
+ }
739
+ if (a === b || a.startsWith(b) || b.startsWith(a)) {
740
+ return true;
741
+ }
742
+ }
743
+ }
744
+ return false;
745
+ };
746
+ var parseRepeatedExtglob = (pattern, requireEnd = true) => {
747
+ if (pattern[0] !== "+" && pattern[0] !== "*" || pattern[1] !== "(") {
748
+ return;
749
+ }
750
+ let bracket = 0;
751
+ let paren = 0;
752
+ let quote = 0;
753
+ let escaped = false;
754
+ for (let i = 1; i < pattern.length; i++) {
755
+ const ch = pattern[i];
756
+ if (escaped === true) {
757
+ escaped = false;
758
+ continue;
759
+ }
760
+ if (ch === "\\") {
761
+ escaped = true;
762
+ continue;
763
+ }
764
+ if (ch === '"') {
765
+ quote = quote === 1 ? 0 : 1;
766
+ continue;
767
+ }
768
+ if (quote === 1) {
769
+ continue;
770
+ }
771
+ if (ch === "[") {
772
+ bracket++;
773
+ continue;
774
+ }
775
+ if (ch === "]" && bracket > 0) {
776
+ bracket--;
777
+ continue;
778
+ }
779
+ if (bracket > 0) {
780
+ continue;
781
+ }
782
+ if (ch === "(") {
783
+ paren++;
784
+ continue;
785
+ }
786
+ if (ch === ")") {
787
+ paren--;
788
+ if (paren === 0) {
789
+ if (requireEnd === true && i !== pattern.length - 1) {
790
+ return;
791
+ }
792
+ return {
793
+ type: pattern[0],
794
+ body: pattern.slice(2, i),
795
+ end: i
796
+ };
797
+ }
798
+ }
799
+ }
800
+ };
801
+ var getStarExtglobSequenceOutput = (pattern) => {
802
+ let index = 0;
803
+ const chars = [];
804
+ while (index < pattern.length) {
805
+ const match = parseRepeatedExtglob(pattern.slice(index), false);
806
+ if (!match || match.type !== "*") {
807
+ return;
808
+ }
809
+ const branches = splitTopLevel(match.body).map((branch2) => branch2.trim());
810
+ if (branches.length !== 1) {
811
+ return;
812
+ }
813
+ const branch = normalizeSimpleBranch(branches[0]);
814
+ if (!branch || branch.length !== 1) {
815
+ return;
816
+ }
817
+ chars.push(branch);
818
+ index += match.end + 1;
819
+ }
820
+ if (chars.length < 1) {
821
+ return;
822
+ }
823
+ const source = chars.length === 1 ? utils.escapeRegex(chars[0]) : `[${chars.map((ch) => utils.escapeRegex(ch)).join("")}]`;
824
+ return `${source}*`;
825
+ };
826
+ var repeatedExtglobRecursion = (pattern) => {
827
+ let depth = 0;
828
+ let value = pattern.trim();
829
+ let match = parseRepeatedExtglob(value);
830
+ while (match) {
831
+ depth++;
832
+ value = match.body.trim();
833
+ match = parseRepeatedExtglob(value);
834
+ }
835
+ return depth;
836
+ };
837
+ var analyzeRepeatedExtglob = (body, options) => {
838
+ if (options.maxExtglobRecursion === false) {
839
+ return { risky: false };
840
+ }
841
+ const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
842
+ const branches = splitTopLevel(body).map((branch) => branch.trim());
843
+ if (branches.length > 1) {
844
+ if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) {
845
+ return { risky: true };
846
+ }
847
+ }
848
+ for (const branch of branches) {
849
+ const safeOutput = getStarExtglobSequenceOutput(branch);
850
+ if (safeOutput) {
851
+ return { risky: true, safeOutput };
852
+ }
853
+ if (repeatedExtglobRecursion(branch) > max) {
854
+ return { risky: true };
855
+ }
856
+ }
857
+ return { risky: false };
858
+ };
649
859
  var parse = (input, options) => {
650
860
  if (typeof input !== "string") {
651
861
  throw new TypeError("Expected a string");
@@ -776,6 +986,8 @@ var require_parse = __commonJS({
776
986
  token.prev = prev;
777
987
  token.parens = state.parens;
778
988
  token.output = state.output;
989
+ token.startIndex = state.index;
990
+ token.tokensIndex = tokens.length;
779
991
  const output = (opts.capture ? "(" : "") + token.open;
780
992
  increment("parens");
781
993
  push({ type, value: value2, output: state.output ? "" : ONE_CHAR });
@@ -783,6 +995,26 @@ var require_parse = __commonJS({
783
995
  extglobs.push(token);
784
996
  };
785
997
  const extglobClose = (token) => {
998
+ const literal = input.slice(token.startIndex, state.index + 1);
999
+ const body = input.slice(token.startIndex + 2, state.index);
1000
+ const analysis = analyzeRepeatedExtglob(body, opts);
1001
+ if ((token.type === "plus" || token.type === "star") && analysis.risky) {
1002
+ const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
1003
+ const open = tokens[token.tokensIndex];
1004
+ open.type = "text";
1005
+ open.value = literal;
1006
+ open.output = safeOutput || utils.escapeRegex(literal);
1007
+ for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
1008
+ tokens[i].value = "";
1009
+ tokens[i].output = "";
1010
+ delete tokens[i].suffix;
1011
+ }
1012
+ state.output = token.output + open.output;
1013
+ state.backtrack = true;
1014
+ push({ type: "paren", extglob: true, value, output: "" });
1015
+ decrement("parens");
1016
+ return;
1017
+ }
786
1018
  let output = token.close + (opts.capture ? ")" : "");
787
1019
  let rest;
788
1020
  if (token.type === "negate") {
@@ -1626,6 +1858,22 @@ function cosPlugin(options = {}) {
1626
1858
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
1627
1859
  const loaderPath = path.resolve(__dirname, "./loader.js");
1628
1860
  let config;
1861
+ const cwdRequire = createRequire(path.join(process.cwd(), "index.js"));
1862
+ const esbuildRequire = createRequire(import.meta.url);
1863
+ const esbuild = esbuildRequire("esbuild");
1864
+ const include = options.include || ["**/*"];
1865
+ const includeArray = Array.isArray(include) ? include : [include];
1866
+ function getPackageName(item) {
1867
+ if (typeof item === "string") {
1868
+ return item.startsWith("vendor-") ? item.replace("vendor-", "") : item;
1869
+ }
1870
+ if (item instanceof RegExp) {
1871
+ const source = item.source;
1872
+ const match = source.match(/vendor-([a-zA-Z0-9-@/]+?)(?:[-._]|\/|$)/);
1873
+ return match ? match[1] : null;
1874
+ }
1875
+ return null;
1876
+ }
1629
1877
  return {
1630
1878
  name: "vite-plugin-cos",
1631
1879
  apply: "build",
@@ -1635,31 +1883,19 @@ function cosPlugin(options = {}) {
1635
1883
  config2.build.rollupOptions = config2.build.rollupOptions || {};
1636
1884
  const external = config2.build.rollupOptions.external || [];
1637
1885
  const externalArray = Array.isArray(external) ? external : typeof external === "string" ? [external] : [];
1638
- const include = options.include || ["**/*"];
1639
- const includeArray = Array.isArray(include) ? include : [include];
1640
- const require2 = createRequire(path.join(process.cwd(), "index.js"));
1641
- function getPackageName(item) {
1642
- if (typeof item === "string") {
1643
- return item.startsWith("vendor-") ? item.replace("vendor-", "") : item;
1644
- }
1645
- if (item instanceof RegExp) {
1646
- const source = item.source;
1647
- const match = source.match(/vendor-([a-zA-Z0-9-@/]+?)(?:[-._]|\/|$)/);
1648
- return match ? match[1] : null;
1649
- }
1650
- return null;
1651
- }
1652
1886
  for (const item of includeArray) {
1653
1887
  const pkgName = getPackageName(item);
1654
1888
  if (pkgName) {
1655
1889
  try {
1656
- require2.resolve(pkgName);
1657
- const pkgRegex = new RegExp(`^${pkgName}(?:/.*)?$`);
1658
- if (!externalArray.some((e) => e instanceof RegExp && e.source === pkgRegex.source)) {
1659
- console.log(
1660
- `COS Plugin: [MAGIC] Externalizing package and subpaths: ${pkgRegex} (matched from ${item})`
1661
- );
1662
- externalArray.push(pkgRegex);
1890
+ cwdRequire.resolve(pkgName);
1891
+ const externalPatterns = [pkgName, `${pkgName}/*`];
1892
+ for (const pattern of externalPatterns) {
1893
+ if (!externalArray.includes(pattern)) {
1894
+ console.log(
1895
+ `COS Plugin: [MAGIC] Externalizing pattern "${pattern}" (matched from ${item})`
1896
+ );
1897
+ externalArray.push(pattern);
1898
+ }
1663
1899
  }
1664
1900
  } catch (e) {
1665
1901
  }
@@ -1674,7 +1910,7 @@ function cosPlugin(options = {}) {
1674
1910
  order: "post",
1675
1911
  handler(html) {
1676
1912
  return html.replace(
1677
- /<script\s+[^>]*type=["']module["'][^>]*src=["'][^"']*index[^"']*["'][^>]*><\/script>/gi,
1913
+ /<script\s+[^>]*type=["']module["'][^>]*src=["'][^"']+["'][^>]*><\/script>/gi,
1678
1914
  "<!-- Entry script disabled by COS Plugin -->"
1679
1915
  );
1680
1916
  }
@@ -1682,7 +1918,7 @@ function cosPlugin(options = {}) {
1682
1918
  async generateBundle(_options, bundle) {
1683
1919
  const managedChunks = {};
1684
1920
  let mainChunk = null;
1685
- let htmlAsset = null;
1921
+ const htmlAssets = [];
1686
1922
  for (const fileName in bundle) {
1687
1923
  const chunk = bundle[fileName];
1688
1924
  if (chunk.type === "chunk") {
@@ -1698,61 +1934,45 @@ function cosPlugin(options = {}) {
1698
1934
  managedChunks[fileName] = chunk;
1699
1935
  }
1700
1936
  }
1701
- if (fileName === "index.html" && chunk.type === "asset") {
1702
- htmlAsset = chunk;
1937
+ if (fileName.endsWith(".html") && chunk.type === "asset") {
1938
+ htmlAssets.push(chunk);
1703
1939
  }
1704
1940
  }
1705
1941
  const externalToFileName = {};
1706
- const require2 = createRequire(path.join(process.cwd(), "index.js"));
1707
- const include = options.include || ["**/*"];
1708
- const includeArray = Array.isArray(include) ? include : [include];
1709
- function getPackageName(item) {
1710
- if (typeof item === "string") {
1711
- return item.startsWith("vendor-") ? item.replace("vendor-", "") : item;
1712
- }
1713
- if (item instanceof RegExp) {
1714
- const source = item.source;
1715
- const match = source.match(/vendor-([a-zA-Z0-9-@/]+?)(?:[-._]|\/|$)/);
1716
- return match ? match[1] : null;
1717
- }
1718
- return null;
1719
- }
1720
- const magicPackages = /* @__PURE__ */ new Set();
1721
1942
  for (const item of includeArray) {
1722
1943
  const pkgName = getPackageName(item);
1723
- if (pkgName) {
1944
+ if (!pkgName) continue;
1945
+ try {
1946
+ let pkgPath = "";
1724
1947
  try {
1725
- require2.resolve(pkgName);
1726
- magicPackages.add(pkgName);
1727
- } catch (e) {
1728
- }
1729
- }
1730
- }
1731
- const discoveredSpecifiers = /* @__PURE__ */ new Set();
1732
- const allChunks = Object.values(bundle).filter(
1733
- (c) => c.type === "chunk"
1734
- );
1735
- for (const chunk of allChunks) {
1736
- const allImports = [...chunk.imports, ...chunk.dynamicImports];
1737
- for (const specifier of allImports) {
1738
- for (const pkgName of magicPackages) {
1739
- if (specifier === pkgName || specifier.startsWith(`${pkgName}/`)) {
1740
- discoveredSpecifiers.add(specifier);
1741
- break;
1948
+ const mainPath = cwdRequire.resolve(pkgName);
1949
+ let currentDir = path.dirname(mainPath);
1950
+ let pkgJsonPath = "";
1951
+ while (currentDir !== path.parse(currentDir).root) {
1952
+ const candidate = path.join(currentDir, "package.json");
1953
+ if (fs.existsSync(candidate)) {
1954
+ pkgJsonPath = candidate;
1955
+ break;
1956
+ }
1957
+ currentDir = path.dirname(currentDir);
1742
1958
  }
1959
+ if (pkgJsonPath) {
1960
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
1961
+ const pkgDir = path.dirname(pkgJsonPath);
1962
+ if (pkgJson.module) {
1963
+ pkgPath = path.resolve(pkgDir, pkgJson.module);
1964
+ } else if (pkgJson.exports?.["."]?.import) {
1965
+ pkgPath = path.resolve(pkgDir, pkgJson.exports["."].import);
1966
+ } else if (pkgJson.exports?.import) {
1967
+ pkgPath = path.resolve(pkgDir, pkgJson.exports.import);
1968
+ }
1969
+ }
1970
+ if (!pkgPath) {
1971
+ pkgPath = mainPath;
1972
+ }
1973
+ } catch (e) {
1974
+ pkgPath = cwdRequire.resolve(pkgName);
1743
1975
  }
1744
- }
1745
- }
1746
- const specifierToCode = {};
1747
- for (const specifier of discoveredSpecifiers) {
1748
- try {
1749
- const pkgPath = require2.resolve(specifier);
1750
- const pkgName = Array.from(magicPackages).find((p) => specifier === p || specifier.startsWith(`${p}/`));
1751
- const otherSpecifiersFromSamePkg = Array.from(discoveredSpecifiers).filter(
1752
- (s) => s !== specifier && (s === pkgName || s.startsWith(`${pkgName}/`))
1753
- );
1754
- const esbuildRequire = createRequire(import.meta.url);
1755
- const esbuild = esbuildRequire("esbuild");
1756
1976
  const buildResult = await esbuild.build({
1757
1977
  entryPoints: [pkgPath],
1758
1978
  bundle: true,
@@ -1760,32 +1980,19 @@ function cosPlugin(options = {}) {
1760
1980
  minify: true,
1761
1981
  platform: "browser",
1762
1982
  write: false,
1983
+ // Don't write to disk
1763
1984
  target: "esnext",
1764
- // Mark other components of the same library as external to avoid duplication
1765
- external: otherSpecifiersFromSamePkg,
1985
+ // Neutralize environment
1766
1986
  define: {
1767
1987
  "process.env.NODE_ENV": '"production"'
1768
1988
  }
1769
1989
  });
1770
- let code = buildResult.outputFiles[0].text;
1771
- for (const otherSpec of otherSpecifiersFromSamePkg) {
1772
- const escapedOtherSpec = otherSpec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1773
- const bareSpecifier = `coschunk-${otherSpec.replace(/[/@]/g, "-")}`;
1774
- const staticRegex = new RegExp(`(import|export)\\b\\s*((?:(?!\\bimport\\b|\\bexport\\b)[\\s\\S])*?\\bfrom\\b\\s*)?['"]${escapedOtherSpec}['"]\\s*;?`, "g");
1775
- code = code.replace(staticRegex, (match, keyword, fromPart) => {
1776
- return `${keyword}${fromPart ? " " + fromPart : " "}"${bareSpecifier}";`;
1777
- });
1778
- const dynamicRegex = new RegExp(`import\\s*\\(\\s*['"]${escapedOtherSpec}['"]\\s*\\)`, "g");
1779
- code = code.replace(dynamicRegex, () => `import("${bareSpecifier}")`);
1780
- }
1781
- specifierToCode[specifier] = code;
1782
- const content = Buffer.from(code);
1990
+ const content = buildResult.outputFiles[0].contents;
1783
1991
  const hash = crypto.createHash("sha256").update(content).digest("hex");
1784
1992
  const ext = ".js";
1785
- const safeSpecifier = specifier.replace(/[/@]/g, "-").replace(/\.js$/, "");
1786
1993
  const fileName = path.join(
1787
1994
  config.build.assetsDir,
1788
- `${safeSpecifier}-${hash.slice(0, 8)}${ext}`
1995
+ `${pkgName}-${hash.slice(0, 8)}${ext}`
1789
1996
  );
1790
1997
  this.emitFile({
1791
1998
  type: "asset",
@@ -1795,27 +2002,21 @@ function cosPlugin(options = {}) {
1795
2002
  managedChunks[fileName] = {
1796
2003
  type: "chunk",
1797
2004
  fileName,
1798
- code,
1799
- name: specifier
2005
+ code: Buffer.from(content).toString("utf-8"),
2006
+ name: item
1800
2007
  };
1801
- externalToFileName[specifier] = fileName;
2008
+ externalToFileName[pkgName] = fileName;
1802
2009
  } catch (e) {
1803
- console.error(`COS Plugin: Failed to bundle magic specifier "${specifier}"`, e);
1804
2010
  }
1805
2011
  }
1806
2012
  if (mainChunk) {
1807
- const magicMapping = {};
1808
- for (const specifier in externalToFileName) {
1809
- const bareSpecifier = `coschunk-${specifier.replace(/[/@]/g, "-")}`;
1810
- magicMapping[bareSpecifier] = externalToFileName[specifier];
1811
- }
1812
- const allChunks2 = Object.values(bundle).filter(
2013
+ const allChunks = Object.values(bundle).filter(
1813
2014
  (c) => c.type === "chunk"
1814
2015
  );
1815
2016
  const managedChunkNames = new Set(Object.keys(managedChunks));
1816
2017
  const unmanagedDependencies = /* @__PURE__ */ new Set();
1817
2018
  const base = config.base.endsWith("/") ? config.base : config.base + "/";
1818
- for (const targetChunk of allChunks2) {
2019
+ for (const targetChunk of allChunks) {
1819
2020
  const importerDir = path.dirname(targetChunk.fileName);
1820
2021
  const deps = [...targetChunk.imports, ...targetChunk.dynamicImports];
1821
2022
  for (const depFileName of deps) {
@@ -1848,7 +2049,8 @@ function cosPlugin(options = {}) {
1848
2049
  }
1849
2050
  }
1850
2051
  for (const pkgName in externalToFileName) {
1851
- const bareSpecifier = `coschunk-${pkgName.replace(/[/@]/g, "-")}`;
2052
+ const fileName = externalToFileName[pkgName];
2053
+ const bareSpecifier = `coschunk-${fileName.replace(/\//g, "-")}`;
1852
2054
  const staticPattern = `(import|export)\\b\\s*((?:(?!\\bimport\\b|\\bexport\\b)[\\s\\S])*?\\bfrom\\b\\s*)?['"]${pkgName}['"]\\s*;?`;
1853
2055
  const staticRegex = new RegExp(staticPattern, "g");
1854
2056
  targetChunk.code = targetChunk.code.replace(
@@ -1868,8 +2070,7 @@ function cosPlugin(options = {}) {
1868
2070
  const manifest = {
1869
2071
  base,
1870
2072
  entry: mainChunk.fileName,
1871
- chunks: {},
1872
- magic: magicMapping
2073
+ chunks: {}
1873
2074
  };
1874
2075
  for (const fileName in managedChunks) {
1875
2076
  const chunk = managedChunks[fileName];
@@ -1880,13 +2081,19 @@ function cosPlugin(options = {}) {
1880
2081
  unmanagedDependencies.add(mainChunk.fileName);
1881
2082
  }
1882
2083
  manifest.unmanaged = Array.from(unmanagedDependencies);
1883
- if (htmlAsset) {
2084
+ if (htmlAssets.length > 0) {
2085
+ let loaderCode;
1884
2086
  try {
1885
- let loaderCode = fs.readFileSync(loaderPath, "utf-8");
1886
- loaderCode = loaderCode.replace(
1887
- "__COS_MANIFEST__",
1888
- JSON.stringify(manifest, null, 2)
1889
- );
2087
+ loaderCode = fs.readFileSync(loaderPath, "utf-8");
2088
+ } catch (e) {
2089
+ console.error("COS Plugin: Failed to read loader.js", e);
2090
+ return;
2091
+ }
2092
+ loaderCode = loaderCode.replace(
2093
+ "__COS_MANIFEST__",
2094
+ JSON.stringify(manifest, null, 2)
2095
+ );
2096
+ for (const htmlAsset of htmlAssets) {
1890
2097
  let htmlSource = htmlAsset.source;
1891
2098
  htmlSource = htmlSource.replace(
1892
2099
  /<link\s+[^>]*rel=["']modulepreload["'][^>]*>/gi,
@@ -1897,8 +2104,6 @@ function cosPlugin(options = {}) {
1897
2104
  () => `<head>
1898
2105
  <script id="cos-loader">${loaderCode}</script>`
1899
2106
  );
1900
- } catch (e) {
1901
- console.error("COS Plugin: Failed to read loader.js", e);
1902
2107
  }
1903
2108
  }
1904
2109
  }
package/dist/loader.js CHANGED
@@ -111,18 +111,6 @@
111
111
  // Inject Import Map
112
112
  const script = document.createElement('script');
113
113
  script.type = 'importmap';
114
-
115
- // Add magic specifier mappings (e.g. coschunk-three -> assets/three-*.js)
116
- if (manifest.magic) {
117
- for (const [specifier, fileName] of Object.entries(manifest.magic)) {
118
- const targetSpecifier = `coschunk-${fileName.replace(/\//g, '-')}`;
119
- const targetUrl = importMap.imports[targetSpecifier];
120
- if (targetUrl) {
121
- importMap.imports[specifier] = targetUrl;
122
- }
123
- }
124
- }
125
-
126
114
  script.textContent = JSON.stringify(importMap, null, 2);
127
115
  document.head.appendChild(script);
128
116
 
@@ -132,13 +120,14 @@
132
120
  // through the import map and can find other managed chunks.
133
121
  const entrySpecifier = `coschunk-${mainEntry.replace(/\//g, '-')}`;
134
122
 
135
- // Small delay to ensure the browser has fully registered the
136
- // import map before resolving the first module.
137
- setTimeout(() => {
138
- import(entrySpecifier).catch((err) => {
139
- console.error('COS Loader: Failed to start app', err);
140
- });
141
- }, 0);
123
+ // Yield to the event loop once so the browser registers the dynamically
124
+ // injected import map before the first module import.
125
+ await new Promise((resolve) => setTimeout(resolve, 0));
126
+ try {
127
+ await import(entrySpecifier);
128
+ } catch (err) {
129
+ console.error('COS Loader: Failed to start app', err);
130
+ }
142
131
  } catch (err) {
143
132
  console.error('COS Loader: Initialization failed', err);
144
133
  }
package/loader.js CHANGED
@@ -111,18 +111,6 @@
111
111
  // Inject Import Map
112
112
  const script = document.createElement('script');
113
113
  script.type = 'importmap';
114
-
115
- // Add magic specifier mappings (e.g. coschunk-three -> assets/three-*.js)
116
- if (manifest.magic) {
117
- for (const [specifier, fileName] of Object.entries(manifest.magic)) {
118
- const targetSpecifier = `coschunk-${fileName.replace(/\//g, '-')}`;
119
- const targetUrl = importMap.imports[targetSpecifier];
120
- if (targetUrl) {
121
- importMap.imports[specifier] = targetUrl;
122
- }
123
- }
124
- }
125
-
126
114
  script.textContent = JSON.stringify(importMap, null, 2);
127
115
  document.head.appendChild(script);
128
116
 
@@ -132,13 +120,14 @@
132
120
  // through the import map and can find other managed chunks.
133
121
  const entrySpecifier = `coschunk-${mainEntry.replace(/\//g, '-')}`;
134
122
 
135
- // Small delay to ensure the browser has fully registered the
136
- // import map before resolving the first module.
137
- setTimeout(() => {
138
- import(entrySpecifier).catch((err) => {
139
- console.error('COS Loader: Failed to start app', err);
140
- });
141
- }, 0);
123
+ // Yield to the event loop once so the browser registers the dynamically
124
+ // injected import map before the first module import.
125
+ await new Promise((resolve) => setTimeout(resolve, 0));
126
+ try {
127
+ await import(entrySpecifier);
128
+ } catch (err) {
129
+ console.error('COS Loader: Failed to start app', err);
130
+ }
142
131
  } catch (err) {
143
132
  console.error('COS Loader: Initialization failed', err);
144
133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-cross-origin-storage",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Vite plugin to load chunks from Cross-Origin Storage",
5
5
  "keywords": [
6
6
  "vite",
@@ -37,23 +37,34 @@
37
37
  "scripts": {
38
38
  "build": "tsup index.ts --format esm --dts --clean && cp loader.js dist/",
39
39
  "prepublishOnly": "npm run build",
40
- "test:dev": "npm run build && vite testing",
41
- "test:build": "npm run build && vite build testing",
42
- "test:preview": "vite preview testing"
40
+ "test:simple:dev": "npm run build && vite testing/simple-test",
41
+ "test:simple:build": "npm run build && vite build testing/simple-test",
42
+ "test:simple:preview": "vite preview testing/simple-test",
43
+ "test:react-hello-world:dev": "npm run build && vite testing/react-hello-world",
44
+ "test:react-hello-world:build": "npm run build && vite build testing/react-hello-world",
45
+ "test:react-hello-world:preview": "vite preview testing/react-hello-world",
46
+ "test:react-todo:dev": "npm run build && vite testing/react-todo",
47
+ "test:react-todo:build": "npm run build && vite build testing/react-todo",
48
+ "test:react-todo:preview": "vite preview --port 4174 testing/react-todo"
43
49
  },
44
50
  "peerDependencies": {
45
- "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
51
+ "vite": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
46
52
  },
47
53
  "devDependencies": {
48
54
  "@rollup/pluginutils": "^5.3.0",
49
- "@types/node": "^25.2.3",
50
- "rollup": "^4.57.1",
51
- "three": "^0.183.0",
55
+ "@types/node": "^25.6.2",
56
+ "@types/react": "^19.2.14",
57
+ "@types/react-dom": "^19.2.3",
58
+ "@vitejs/plugin-react": "^6.0.1",
59
+ "react": "^19.2.6",
60
+ "react-dom": "^19.2.6",
61
+ "rollup": "^4.60.3",
62
+ "three": "^0.184.0",
52
63
  "tsup": "^8.5.1",
53
- "typescript": "^5.9.3",
54
- "vite": "^7.3.1"
64
+ "typescript": "^6.0.3",
65
+ "vite": "^8.0.11"
55
66
  },
56
67
  "dependencies": {
57
- "esbuild": "^0.27.3"
68
+ "esbuild": "^0.28.0"
58
69
  }
59
70
  }