@weasel-js/forge 1.14.0 → 1.14.2

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.
@@ -61,4 +61,21 @@ interface WatchProbeOptions {
61
61
  /** A plugin giving any dev server the warning forge's own gives when the OS reports no file changes. */
62
62
  declare function watchProbe({ probe }?: WatchProbeOptions): Plugin;
63
63
 
64
- export { type ForgeOptions, type ForgeTestOptions, type PropControlsOptions, type WatchProbeOptions, forge, forgeTest, watchProbe };
64
+ /** Options for the `watchRecovery` vite plugin. */
65
+ interface WatchRecoveryOptions {
66
+ /**
67
+ * How long the watcher must have reported nothing before the served files are checked for changes it missed,
68
+ * and how often they are checked while it stays quiet, in milliseconds. Default 2000.
69
+ */
70
+ quietMs?: number;
71
+ }
72
+ /**
73
+ * A plugin that serves an edit the OS never reported. Each module vite serves from a file is stamped with the file's
74
+ * modification time when vite reads it. A request for a module whose file has changed since, and a check of every
75
+ * served file once the watcher has been quiet for `quietMs`, replays the missed change through vite's watcher: the
76
+ * module is transformed again and the open page gets the update, or a reload, as it would have from the event.
77
+ * Each recovery logs one line naming the file.
78
+ */
79
+ declare function watchRecovery({ quietMs }?: WatchRecoveryOptions): Plugin;
80
+
81
+ export { type ForgeOptions, type ForgeTestOptions, type PropControlsOptions, type WatchProbeOptions, type WatchRecoveryOptions, forge, forgeTest, watchProbe, watchRecovery };
@@ -3,13 +3,14 @@ import { byComponent } from '../chunk-RVJHUY7W.js';
3
3
  import { isGallery } from '../chunk-VUPN73JP.js';
4
4
  import { PROP_ARG_TYPES_EXPORT, storyNameFromExport, storyId } from '../chunk-5WS67HMU.js';
5
5
  import { readFileSync, mkdtempSync, existsSync, mkdirSync, writeFileSync, globSync, statSync, watch, rmSync } from 'fs';
6
- import { resolve, relative, sep, join, dirname, basename, matchesGlob, posix } from 'path';
6
+ import { resolve, relative, sep, isAbsolute, join, dirname, basename, matchesGlob, posix } from 'path';
7
7
  import { parse } from '@babel/parser';
8
8
  import { execFile } from 'child_process';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { createHash } from 'crypto';
11
11
  import { stableStringify } from '@weasel-js/core/json';
12
12
  import { tmpdir } from 'os';
13
+ import { normalizePath } from 'vite';
13
14
 
14
15
  // src/vite/html.ts
15
16
  var escaped = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1783,6 +1784,125 @@ ${lines.join("\n")}
1783
1784
  "capture-frame-entry.js": () => captureFrameEntry(frameStyles())
1784
1785
  };
1785
1786
  }
1787
+ var WATCH_RECOVERY = "weasel-watch-recovery";
1788
+ var QUIET_MS = 2e3;
1789
+ var mtimeOf = (file) => {
1790
+ try {
1791
+ return statSync(file).mtimeMs;
1792
+ } catch {
1793
+ return void 0;
1794
+ }
1795
+ };
1796
+ var withoutQuery = (url) => url.replace(/[?#].*$/s, "");
1797
+ function watchRecovery({ quietMs = QUIET_MS } = {}) {
1798
+ const stamps = /* @__PURE__ */ new Map();
1799
+ const read = /* @__PURE__ */ new Map();
1800
+ const fileOfUrl = /* @__PURE__ */ new Map();
1801
+ let server;
1802
+ let active = false;
1803
+ let skipped = () => true;
1804
+ let lastEvent = 0;
1805
+ let replaying = false;
1806
+ let timer;
1807
+ const restamp = (file) => {
1808
+ const modules = stamps.get(file);
1809
+ const now = mtimeOf(file);
1810
+ if (!modules || now === void 0) return;
1811
+ for (const id of modules.keys()) modules.set(id, now);
1812
+ };
1813
+ const missed = (file) => {
1814
+ const modules = stamps.get(file);
1815
+ const now = mtimeOf(file);
1816
+ if (!modules || now === void 0) return false;
1817
+ for (const stamp of modules.values()) if (stamp !== now) return true;
1818
+ return false;
1819
+ };
1820
+ const recover = (file, outcome) => {
1821
+ if (!server) return;
1822
+ restamp(file);
1823
+ for (const environment of Object.values(server.environments)) environment.moduleGraph.onFileChange(file);
1824
+ server.config.logger.warn(`[watch] ${relative(server.config.root, file)} changed with no file event; ${outcome}`, {
1825
+ timestamp: true
1826
+ });
1827
+ replaying = true;
1828
+ try {
1829
+ server.watcher.emit("change", file);
1830
+ } finally {
1831
+ replaying = false;
1832
+ }
1833
+ };
1834
+ const sweep = () => {
1835
+ if (Date.now() - lastEvent < quietMs) return;
1836
+ for (const file of stamps.keys()) if (missed(file)) recover(file, "pushing it to the page");
1837
+ };
1838
+ const self = {
1839
+ name: WATCH_RECOVERY,
1840
+ apply: "serve",
1841
+ configResolved(config) {
1842
+ active = config.server.watch !== null && config.plugins.find((p) => p.name === WATCH_RECOVERY) === self;
1843
+ const cacheDir = normalizePath(config.cacheDir) + "/";
1844
+ skipped = (file) => !isAbsolute(file) || file.includes("/node_modules/") || file.startsWith(cacheDir);
1845
+ },
1846
+ configureServer(s) {
1847
+ if (!active) return;
1848
+ server = s;
1849
+ for (const event of ["add", "change", "unlink"]) {
1850
+ s.watcher.on(event, (path) => {
1851
+ if (replaying) return;
1852
+ lastEvent = Date.now();
1853
+ const file = normalizePath(path);
1854
+ if (event === "unlink") stamps.delete(file);
1855
+ else restamp(file);
1856
+ });
1857
+ }
1858
+ const base = s.config.base;
1859
+ s.middlewares.use(function weaselWatchRecovery(req, _res, next) {
1860
+ let url = withoutQuery(req.url ?? "");
1861
+ if (base !== "/" && url.startsWith(base)) url = url.slice(base.length - 1);
1862
+ let file;
1863
+ try {
1864
+ file = fileOfUrl.get(decodeURI(url));
1865
+ } catch {
1866
+ }
1867
+ if (file && missed(file)) recover(file, "serving the new text");
1868
+ next();
1869
+ });
1870
+ timer = setInterval(sweep, quietMs);
1871
+ timer.unref();
1872
+ },
1873
+ load: {
1874
+ order: "pre",
1875
+ handler(id) {
1876
+ if (!active || this.environment.name !== "client") return;
1877
+ const file = withoutQuery(id);
1878
+ if (skipped(file)) return;
1879
+ const mtime = mtimeOf(file);
1880
+ if (mtime !== void 0) read.set(id, mtime);
1881
+ }
1882
+ },
1883
+ transform: {
1884
+ order: "pre",
1885
+ handler(_code, id) {
1886
+ const mtime = read.get(id);
1887
+ if (mtime === void 0) return;
1888
+ read.delete(id);
1889
+ if (this.environment.mode !== "dev") return;
1890
+ const mod = this.environment.moduleGraph.getModuleById(id);
1891
+ if (!mod?.file) return;
1892
+ let modules = stamps.get(mod.file);
1893
+ if (!modules) stamps.set(mod.file, modules = /* @__PURE__ */ new Map());
1894
+ modules.set(id, mtime);
1895
+ fileOfUrl.set(withoutQuery(mod.url), mod.file);
1896
+ }
1897
+ },
1898
+ buildEnd() {
1899
+ clearInterval(timer);
1900
+ }
1901
+ };
1902
+ return self;
1903
+ }
1904
+
1905
+ // src/vite/watchProbe.ts
1786
1906
  var WAIT_MS = 5e3;
1787
1907
  var REWRITE_MS = 250;
1788
1908
  var watchDir = (dir, onEvent) => {
@@ -1823,11 +1943,16 @@ function fileEventsArrive({ waitMs = WAIT_MS, watch = watchDir } = {}) {
1823
1943
  deadline.unref();
1824
1944
  });
1825
1945
  }
1826
- var SILENT = `[watch] a file written ${WAIT_MS / 1e3} s ago has not been reported by the OS file watcher, so an edit will not reach the page until file events arrive again. \`server.watch: { usePolling: true }\` serves edits without them. On macOS, look for \`fseventsd\` held at 100% CPU.`;
1946
+ var UNREPORTED = `[watch] a file written ${WAIT_MS / 1e3} s ago has not been reported by the OS file watcher, `;
1947
+ var FSEVENTSD = "On macOS, look for `fseventsd` held at 100% CPU.";
1948
+ var SILENT = UNREPORTED + "so an edit will not reach the page until file events arrive again. `server.watch: { usePolling: true }` serves edits without them, and so does the `watchRecovery()` plugin beside `watchProbe()`. " + FSEVENTSD;
1949
+ var RECOVERING = UNREPORTED + "so edits are found by their modification times instead: on the next request for the module, or within seconds once the watcher is quiet, each logged as it is served. " + FSEVENTSD;
1827
1950
  async function warnWhenWatchIsSilent(server, probe = fileEventsArrive) {
1828
1951
  const { watch } = server.config.server;
1829
1952
  if (watch === null || watch?.usePolling) return;
1830
- if (!await probe()) server.config.logger.warn(SILENT);
1953
+ if (await probe()) return;
1954
+ const recovering = server.config.plugins.some((p) => p.name === WATCH_RECOVERY);
1955
+ server.config.logger.warn(recovering ? RECOVERING : SILENT);
1831
1956
  }
1832
1957
  function watchProbe({ probe } = {}) {
1833
1958
  return {
@@ -1856,6 +1981,14 @@ function watchSources(server, { files, depGraph, reindex, redeps, logError }) {
1856
1981
  });
1857
1982
  }
1858
1983
  }
1984
+ function companions(options, storyFiles) {
1985
+ const { storybookShims: shims, propControls: controls } = options;
1986
+ return [
1987
+ watchRecovery(),
1988
+ ...shims === false ? [] : [storybookShims()],
1989
+ ...controls === false ? [] : [propControls(storyFiles, controls === true ? void 0 : controls)]
1990
+ ];
1991
+ }
1859
1992
  function forge(options) {
1860
1993
  let root = process.cwd();
1861
1994
  let base = "/";
@@ -1994,8 +2127,7 @@ function forge(options) {
1994
2127
  return true;
1995
2128
  }
1996
2129
  return [
1997
- ...options.storybookShims === false ? [] : [storybookShims()],
1998
- ...options.propControls === false ? [] : [propControls(() => [...files().keys()], options.propControls === true ? void 0 : options.propControls)],
2130
+ ...companions(options, () => [...files().keys()]),
1999
2131
  {
2000
2132
  name: "weaselforge",
2001
2133
  config(config, { command }) {
@@ -2121,6 +2253,6 @@ ${lines.join("\n")}
2121
2253
  ];
2122
2254
  }
2123
2255
 
2124
- export { forge, forgeTest, watchProbe };
2256
+ export { forge, forgeTest, watchProbe, watchRecovery };
2125
2257
  //# sourceMappingURL=index.js.map
2126
2258
  //# sourceMappingURL=index.js.map