@bamboocss/vite 1.55.1 → 1.55.3

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.
Files changed (3) hide show
  1. package/dist/index.cjs +68 -14
  2. package/dist/index.mjs +68 -14
  3. package/package.json +10 -10
package/dist/index.cjs CHANGED
@@ -304,6 +304,22 @@ const queryOf = (id) => {
304
304
  const at = id.indexOf("?");
305
305
  return at === -1 ? "" : id.slice(at);
306
306
  };
307
+ /** `?url`, tested the way Vite's asset plugin tests it — the plugin a dev server leaves it to. */
308
+ const URL_QUERY = /(?:\?|&)url(?:&|$)/;
309
+ /**
310
+ * Where a dev server serves the stylesheet, spelled the way Vite writes the URL of any module with
311
+ * no file behind it: `/@id/`, then the resolved id with its NUL as `__x00__`, behind the server's
312
+ * `origin` and base.
313
+ *
314
+ * The base is taken decoded, so `encodeURI` applies its escapes exactly once, as Vite does for an
315
+ * asset's URL. Vite 6 and up carry it as `decodedBase`, which is missing from the published types,
316
+ * hence the cast; Vite 5 has only `base`, decoded here instead.
317
+ */
318
+ const devStylesheetUrl = (config) => {
319
+ const join = (a, b) => a && b ? `${a.replace(/\/$/, "")}/${b.replace(/^\//, "")}` : a || b;
320
+ const base = config?.decodedBase ?? decodeURI(config?.base ?? "/");
321
+ return encodeURI(join(join(config?.server.origin ?? "", base), `@id/__x00__${VIRTUAL_CSS_ID}`));
322
+ };
307
323
  /**
308
324
  * A thrown value Vite can actually report.
309
325
  *
@@ -532,7 +548,7 @@ const bamboocssCss = (options) => {
532
548
  * for the client graph and once for SSR — and each load used to run a complete extraction
533
549
  * and optimization pass to produce byte-identical CSS. The sheet is a function of the source
534
550
  * files alone, and the watcher below is the single point every event that can reach it
535
- * passes through — Vite's own propagation only arrives via the watch edges `load` registers,
551
+ * passes through — Vite's own propagation only arrives via the watch edges `transform` registers,
536
552
  * over the same extracted files the watcher checks. A monotonic counter bumped there is
537
553
  * therefore enough to know whether a build already reflects the world a load is asking about.
538
554
  *
@@ -744,14 +760,12 @@ const bamboocssCss = (options) => {
744
760
  const query = queryOf(id);
745
761
  if (id.slice(0, id.length - query.length) !== RESOLVED_ID) return null;
746
762
  session.cssLoaded = true;
763
+ if (command === "serve" && URL_QUERY.test(query)) return `export default ${JSON.stringify(devStylesheetUrl(server?.config))}`;
747
764
  const generationAtStart = changeGeneration;
748
- if (command === "serve" && servedCss?.generation === generationAtStart) {
749
- if (this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
750
- return servedCss.map ? {
751
- code: servedCss.css,
752
- map: servedCss.map
753
- } : servedCss.css;
754
- }
765
+ if (command === "serve" && servedCss?.generation === generationAtStart) return servedCss.map ? {
766
+ code: servedCss.css,
767
+ map: servedCss.map
768
+ } : servedCss.css;
755
769
  let css;
756
770
  let map;
757
771
  try {
@@ -770,18 +784,53 @@ const bamboocssCss = (options) => {
770
784
  css,
771
785
  map
772
786
  };
773
- if (this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
787
+ if (command === "build" && this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
774
788
  return map ? {
775
789
  code: css,
776
790
  map
777
791
  } : css;
778
792
  },
793
+ /**
794
+ * Register every extracted file against the stylesheet, in the graph that just asked for it.
795
+ *
796
+ * In dev this has to happen here rather than in `load`. Vite attaches what `load` registers to
797
+ * the module's node in the graph, and a request for a module the graph has no node for yet runs
798
+ * `load` first and creates the node afterwards, so everything `load` registered is dropped
799
+ * without a word. `vite:css-analysis` then records no importer edges, Vite's own propagation
800
+ * never reaches the sheet in that environment, and the watcher below does not force a reload
801
+ * either, because the edited file does have a module there. The component repaints with a class
802
+ * whose rule never arrives, and stays that way until a restart.
803
+ *
804
+ * A browser's import never meets it, since the importer's analysis creates the node before the
805
+ * sheet is requested. A `transformRequest` that reaches the sheet first does. TanStack Start
806
+ * sends one on every page load when `__root.tsx` imports the stylesheet: its dev-only SSR style
807
+ * collection transforms the sheet in the client environment on the server, before the browser's
808
+ * import has put a node there.
809
+ *
810
+ * By `transform` the node exists, and the context is the one `vite:css-analysis` reads after
811
+ * every plugin has run, so the edges are recorded however the request arrived. From the
812
+ * session's set rather than `extractedSourceFiles()`, which re-globs the include patterns per
813
+ * call; every pass assigns the set from that same expression.
814
+ *
815
+ * Filtered by id, so a bundler that honours hook filters never calls in for any other module.
816
+ * The exact check stays for one that does not.
817
+ */
818
+ transform: {
819
+ filter: { id: /virtual:bamboo\.css/ },
820
+ handler(_code, id) {
821
+ if (command !== "serve") return;
822
+ const query = queryOf(id);
823
+ if (id.slice(0, id.length - query.length) !== RESOLVED_ID) return;
824
+ if (URL_QUERY.test(query)) return;
825
+ if (this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
826
+ }
827
+ },
779
828
  configureServer(devServer) {
780
829
  server = devServer;
781
830
  /**
782
831
  * The graph the stylesheet's own module lives in, which is the one that has to reach it.
783
832
  *
784
- * `load` registers every extracted file with `addWatchFile`, and `vite:css-analysis`
833
+ * `transform` registers every extracted file with `addWatchFile`, and `vite:css-analysis`
785
834
  * turns those into real importer edges — the virtual module ends up a direct importer of
786
835
  * each file the extractor read. So an edit to any of them propagates to the stylesheet on
787
836
  * Vite's own pass, in whichever environment holds that edge.
@@ -803,11 +852,16 @@ const bamboocssCss = (options) => {
803
852
  host.noteSourceChange(absoluteFile, event, { needsConfigReload: changesConfigMembership });
804
853
  changeGeneration++;
805
854
  prebuilt = void 0;
806
- const mod = server?.moduleGraph.getModuleById(RESOLVED_ID);
807
- if (!mod) return;
855
+ const sheets = [RESOLVED_ID, `${RESOLVED_ID}?direct`].flatMap((sheetId) => {
856
+ const sheet = server?.moduleGraph.getModuleById(sheetId);
857
+ return sheet ? [sheet] : [];
858
+ });
859
+ if (!sheets.length) return;
808
860
  if (wasExtracted && clientGraph.getModulesByFile(absoluteFile)?.size) return;
809
- server?.moduleGraph.invalidateModule(mod);
810
- server?.reloadModule(mod);
861
+ for (const sheet of sheets) {
862
+ server?.moduleGraph.invalidateModule(sheet);
863
+ server?.reloadModule(sheet);
864
+ }
811
865
  _bamboocss_logger.logger.debug("vite", `styles invalidated by ${absoluteFile}`);
812
866
  };
813
867
  devServer.watcher.on("change", (file) => invalidate(file, "update"));
package/dist/index.mjs CHANGED
@@ -299,6 +299,22 @@ const queryOf = (id) => {
299
299
  const at = id.indexOf("?");
300
300
  return at === -1 ? "" : id.slice(at);
301
301
  };
302
+ /** `?url`, tested the way Vite's asset plugin tests it — the plugin a dev server leaves it to. */
303
+ const URL_QUERY = /(?:\?|&)url(?:&|$)/;
304
+ /**
305
+ * Where a dev server serves the stylesheet, spelled the way Vite writes the URL of any module with
306
+ * no file behind it: `/@id/`, then the resolved id with its NUL as `__x00__`, behind the server's
307
+ * `origin` and base.
308
+ *
309
+ * The base is taken decoded, so `encodeURI` applies its escapes exactly once, as Vite does for an
310
+ * asset's URL. Vite 6 and up carry it as `decodedBase`, which is missing from the published types,
311
+ * hence the cast; Vite 5 has only `base`, decoded here instead.
312
+ */
313
+ const devStylesheetUrl = (config) => {
314
+ const join = (a, b) => a && b ? `${a.replace(/\/$/, "")}/${b.replace(/^\//, "")}` : a || b;
315
+ const base = config?.decodedBase ?? decodeURI(config?.base ?? "/");
316
+ return encodeURI(join(join(config?.server.origin ?? "", base), `@id/__x00__${VIRTUAL_CSS_ID}`));
317
+ };
302
318
  /**
303
319
  * A thrown value Vite can actually report.
304
320
  *
@@ -527,7 +543,7 @@ const bamboocssCss = (options) => {
527
543
  * for the client graph and once for SSR — and each load used to run a complete extraction
528
544
  * and optimization pass to produce byte-identical CSS. The sheet is a function of the source
529
545
  * files alone, and the watcher below is the single point every event that can reach it
530
- * passes through — Vite's own propagation only arrives via the watch edges `load` registers,
546
+ * passes through — Vite's own propagation only arrives via the watch edges `transform` registers,
531
547
  * over the same extracted files the watcher checks. A monotonic counter bumped there is
532
548
  * therefore enough to know whether a build already reflects the world a load is asking about.
533
549
  *
@@ -739,14 +755,12 @@ const bamboocssCss = (options) => {
739
755
  const query = queryOf(id);
740
756
  if (id.slice(0, id.length - query.length) !== RESOLVED_ID) return null;
741
757
  session.cssLoaded = true;
758
+ if (command === "serve" && URL_QUERY.test(query)) return `export default ${JSON.stringify(devStylesheetUrl(server?.config))}`;
742
759
  const generationAtStart = changeGeneration;
743
- if (command === "serve" && servedCss?.generation === generationAtStart) {
744
- if (this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
745
- return servedCss.map ? {
746
- code: servedCss.css,
747
- map: servedCss.map
748
- } : servedCss.css;
749
- }
760
+ if (command === "serve" && servedCss?.generation === generationAtStart) return servedCss.map ? {
761
+ code: servedCss.css,
762
+ map: servedCss.map
763
+ } : servedCss.css;
750
764
  let css;
751
765
  let map;
752
766
  try {
@@ -765,18 +779,53 @@ const bamboocssCss = (options) => {
765
779
  css,
766
780
  map
767
781
  };
768
- if (this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
782
+ if (command === "build" && this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
769
783
  return map ? {
770
784
  code: css,
771
785
  map
772
786
  } : css;
773
787
  },
788
+ /**
789
+ * Register every extracted file against the stylesheet, in the graph that just asked for it.
790
+ *
791
+ * In dev this has to happen here rather than in `load`. Vite attaches what `load` registers to
792
+ * the module's node in the graph, and a request for a module the graph has no node for yet runs
793
+ * `load` first and creates the node afterwards, so everything `load` registered is dropped
794
+ * without a word. `vite:css-analysis` then records no importer edges, Vite's own propagation
795
+ * never reaches the sheet in that environment, and the watcher below does not force a reload
796
+ * either, because the edited file does have a module there. The component repaints with a class
797
+ * whose rule never arrives, and stays that way until a restart.
798
+ *
799
+ * A browser's import never meets it, since the importer's analysis creates the node before the
800
+ * sheet is requested. A `transformRequest` that reaches the sheet first does. TanStack Start
801
+ * sends one on every page load when `__root.tsx` imports the stylesheet: its dev-only SSR style
802
+ * collection transforms the sheet in the client environment on the server, before the browser's
803
+ * import has put a node there.
804
+ *
805
+ * By `transform` the node exists, and the context is the one `vite:css-analysis` reads after
806
+ * every plugin has run, so the edges are recorded however the request arrived. From the
807
+ * session's set rather than `extractedSourceFiles()`, which re-globs the include patterns per
808
+ * call; every pass assigns the set from that same expression.
809
+ *
810
+ * Filtered by id, so a bundler that honours hook filters never calls in for any other module.
811
+ * The exact check stays for one that does not.
812
+ */
813
+ transform: {
814
+ filter: { id: /virtual:bamboo\.css/ },
815
+ handler(_code, id) {
816
+ if (command !== "serve") return;
817
+ const query = queryOf(id);
818
+ if (id.slice(0, id.length - query.length) !== RESOLVED_ID) return;
819
+ if (URL_QUERY.test(query)) return;
820
+ if (this.addWatchFile) for (const file of session.extractedFiles) this.addWatchFile(file);
821
+ }
822
+ },
774
823
  configureServer(devServer) {
775
824
  server = devServer;
776
825
  /**
777
826
  * The graph the stylesheet's own module lives in, which is the one that has to reach it.
778
827
  *
779
- * `load` registers every extracted file with `addWatchFile`, and `vite:css-analysis`
828
+ * `transform` registers every extracted file with `addWatchFile`, and `vite:css-analysis`
780
829
  * turns those into real importer edges — the virtual module ends up a direct importer of
781
830
  * each file the extractor read. So an edit to any of them propagates to the stylesheet on
782
831
  * Vite's own pass, in whichever environment holds that edge.
@@ -798,11 +847,16 @@ const bamboocssCss = (options) => {
798
847
  host.noteSourceChange(absoluteFile, event, { needsConfigReload: changesConfigMembership });
799
848
  changeGeneration++;
800
849
  prebuilt = void 0;
801
- const mod = server?.moduleGraph.getModuleById(RESOLVED_ID);
802
- if (!mod) return;
850
+ const sheets = [RESOLVED_ID, `${RESOLVED_ID}?direct`].flatMap((sheetId) => {
851
+ const sheet = server?.moduleGraph.getModuleById(sheetId);
852
+ return sheet ? [sheet] : [];
853
+ });
854
+ if (!sheets.length) return;
803
855
  if (wasExtracted && clientGraph.getModulesByFile(absoluteFile)?.size) return;
804
- server?.moduleGraph.invalidateModule(mod);
805
- server?.reloadModule(mod);
856
+ for (const sheet of sheets) {
857
+ server?.moduleGraph.invalidateModule(sheet);
858
+ server?.reloadModule(sheet);
859
+ }
806
860
  logger.debug("vite", `styles invalidated by ${absoluteFile}`);
807
861
  };
808
862
  devServer.watcher.on("change", (file) => invalidate(file, "update"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.55.1",
3
+ "version": "1.55.3",
4
4
  "description": "Vite integration for Bamboo CSS",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -42,20 +42,20 @@
42
42
  "magic-string": "0.30.21",
43
43
  "postcss": "8.5.26",
44
44
  "postcss-selector-parser": "7.1.5",
45
- "@bamboocss/config": "1.55.1",
46
- "@bamboocss/core": "1.55.1",
47
- "@bamboocss/extractor": "1.55.1",
48
- "@bamboocss/logger": "1.55.1",
49
- "@bamboocss/node": "1.55.1",
50
- "@bamboocss/ts-ast": "1.55.1",
51
- "@bamboocss/shared": "1.55.1",
52
- "@bamboocss/types": "1.55.1"
45
+ "@bamboocss/config": "1.55.3",
46
+ "@bamboocss/extractor": "1.55.3",
47
+ "@bamboocss/logger": "1.55.3",
48
+ "@bamboocss/node": "1.55.3",
49
+ "@bamboocss/core": "1.55.3",
50
+ "@bamboocss/shared": "1.55.3",
51
+ "@bamboocss/types": "1.55.3",
52
+ "@bamboocss/ts-ast": "1.55.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@jridgewell/trace-mapping": "^0.3.31",
56
56
  "@typescript/api": "npm:typescript@7.1.0-dev.20260826.1",
57
57
  "vite": "7.2.6",
58
- "@bamboocss/fixture": "1.55.1"
58
+ "@bamboocss/fixture": "1.55.3"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "vite": ">=5"