@rohal12/twee-ts 1.17.1 → 1.18.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.
@@ -1,4 +1,4 @@
1
- import { t as CompileOptions } from "../types-dR5vTVu2.js";
1
+ import { t as CompileOptions } from "../types-D8Xfe0l8.js";
2
2
  //#region src/plugins/diagnostics.d.ts
3
3
  /** An error carrying the file and line the bundler reports (and Vite shows in its overlay). */
4
4
  interface LocatedError extends Error {
@@ -30,12 +30,14 @@ export interface TweeTsRollupPluginOptions {
30
30
  }
31
31
  export declare function tweeTsPlugin(options: TweeTsRollupPluginOptions): {
32
32
  name: string;
33
+ options(inputOptions: object): undefined;
33
34
  buildStart(this: {
34
35
  addWatchFile: (id: string) => void;
35
36
  meta: {
36
37
  watchMode: boolean;
37
38
  };
38
39
  }): void;
40
+ renderStart(outputOptions: OutputLocation): void;
39
41
  generateBundle(this: {
40
42
  emitFile: (opts: {
41
43
  type: 'asset';
@@ -44,9 +46,6 @@ export declare function tweeTsPlugin(options: TweeTsRollupPluginOptions): {
44
46
  }) => void;
45
47
  error: (error: LocatedError) => never;
46
48
  warn: (message: string) => void;
47
- meta: {
48
- watchMode: boolean;
49
- };
50
49
  }, outputOptions: OutputLocation, bundle: Readonly<Record<string, unknown>>): Promise<void>;
51
50
  };
52
51
  //#endregion
@@ -1,6 +1,6 @@
1
- import { a as fatalError, c as compileForOutputFile, o as formatDiagnostic, s as splitDiagnostics, t as emittedFilePath } from "../paths-w4ulhCFb.js";
2
- import { readdirSync } from "node:fs";
3
- import { join, resolve, sep } from "node:path";
1
+ import { c as splitDiagnostics, f as outputPaths, i as outputLocations, l as compileForOutputFile, m as walkedEntry, o as fatalError, p as realPathOf, s as formatDiagnostic, t as createOutputRecord } from "../paths-DgO9oo6G.js";
2
+ import { readdirSync, statSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
4
  //#region src/plugins/rollup.ts
5
5
  /**
6
6
  * Ready-made Rollup plugin for twee-ts.
@@ -8,52 +8,80 @@ import { join, resolve, sep } from "node:path";
8
8
  * and emit nothing; warnings go through Rollup's warnings.
9
9
  */
10
10
  /**
11
- * What `rollup --watch` watches for the input at the absolute `path`: the path
12
- * itself, which Rollup watches recursively when it is a folder, unless it holds
13
- * one of the files the build wrote (`written`). Such a folder is replaced by what
14
- * it contains, and the written files are left out, so writing them starts no
15
- * build. A file added straight to that folder later is found at the next build.
11
+ * What `rollup --watch` watches for the input at `path`, whose real path is
12
+ * `real`. `isRoot`: the input itself, which is followed wherever it leads.
13
+ *
14
+ * Rollup watches a folder recursively and follows the links in it. So a folder
15
+ * is registered whole only when nothing under it is left out of the story: no
16
+ * file or folder the build writes (`outputs`), and no link to a folder, which
17
+ * source discovery doesn't follow and which may lead to the output. Any other
18
+ * folder is replaced by what it contains, minus those, so writing the outputs
19
+ * starts no build. A file added straight to such a folder is found at the next
20
+ * build. A path the build writes to is never registered itself: Rollup fails a
21
+ * watch build whose watched files include its `output.dir` or `output.file`.
16
22
  */
17
- function watchTargets(path, written) {
18
- if (written.includes(path)) return [];
19
- if (!written.some((file) => file.startsWith(path + sep))) return [path];
20
- let entries;
23
+ function watchTargets(path, real, outputs, isRoot) {
24
+ if (outputs.isFile(real) || !isRoot && outputs.isDir(real)) return [];
25
+ let entry;
21
26
  try {
22
- entries = readdirSync(path);
27
+ entry = isRoot ? {
28
+ stat: statSync(path),
29
+ real
30
+ } : walkedEntry(path, real);
23
31
  } catch {
24
- return [path];
32
+ return isRoot && !outputs.isDir(real) ? [path] : [];
25
33
  }
26
- return entries.flatMap((entry) => watchTargets(join(path, entry), written));
34
+ if (entry === void 0) return [];
35
+ if (outputs.isFile(entry.real)) return [];
36
+ if (!entry.stat.isDirectory()) return [path];
37
+ let names;
38
+ try {
39
+ names = readdirSync(path);
40
+ } catch {
41
+ return isRoot && !outputs.isDir(real) ? [path] : [];
42
+ }
43
+ const children = names.map((name) => {
44
+ const child = join(path, name);
45
+ return {
46
+ path: child,
47
+ targets: watchTargets(child, join(entry.real, name), outputs, false)
48
+ };
49
+ });
50
+ return !outputs.holds(entry.real) && children.every(({ path: child, targets }) => targets.length === 1 && targets[0] === child) ? [path] : children.flatMap(({ targets }) => targets);
27
51
  }
28
52
  function tweeTsPlugin(options) {
29
53
  const outputFilename = options.outputFilename ?? "index.html";
30
- let written = [];
31
- let writing = [];
54
+ const record = createOutputRecord(outputFilename);
32
55
  return {
33
56
  name: "twee-ts",
57
+ options(inputOptions) {
58
+ const { output } = inputOptions;
59
+ for (const location of outputLocations(output)) record.addLocation(location);
60
+ },
34
61
  buildStart() {
35
62
  if (!this.meta.watchMode) return;
36
- if (writing.length > 0) {
37
- written = writing;
38
- writing = [];
39
- }
63
+ const outputs = outputPaths(record.outputs());
40
64
  const extra = options.compileOptions;
41
65
  const inputs = [
42
66
  ...options.sources,
43
67
  ...extra?.headFile ? [extra.headFile] : [],
44
68
  ...extra?.modules ?? []
45
69
  ];
46
- for (const input of inputs) for (const target of watchTargets(resolve(input), written)) this.addWatchFile(target);
70
+ for (const input of inputs) for (const target of watchTargets(resolve(input), realPathOf(input), outputs, true)) this.addWatchFile(target);
71
+ },
72
+ renderStart(outputOptions) {
73
+ record.addLocation(outputOptions);
47
74
  },
48
75
  async generateBundle(outputOptions, bundle) {
49
- const outFile = emittedFilePath(outputOptions, outputFilename);
76
+ record.addLocation(outputOptions);
77
+ record.addFiles(outputOptions, Object.keys(bundle));
50
78
  let result;
51
79
  try {
52
80
  result = await compileForOutputFile({
53
81
  sources: options.sources,
54
82
  formatId: options.format,
55
83
  ...options.compileOptions
56
- }, outFile);
84
+ }, record.outputs());
57
85
  } catch (e) {
58
86
  return this.error(fatalError(e));
59
87
  }
@@ -69,10 +97,6 @@ function tweeTsPlugin(options) {
69
97
  fileName: outputFilename,
70
98
  source: result.output
71
99
  });
72
- if (this.meta.watchMode) for (const fileName of /* @__PURE__ */ new Set([...Object.keys(bundle), outputFilename])) {
73
- const path = emittedFilePath(outputOptions, fileName);
74
- if (path !== void 0) writing.push(path);
75
- }
76
100
  }
77
101
  };
78
102
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rollup.js","names":[],"sources":["../../src/plugins/rollup.ts"],"sourcesContent":["/**\n * Ready-made Rollup plugin for twee-ts.\n * Compiles .tw files and emits HTML as an asset. Compile errors fail the build\n * and emit nothing; warnings go through Rollup's warnings.\n */\nimport { readdirSync } from 'node:fs';\nimport { join, resolve, sep } from 'node:path';\nimport type { CompileOptions, CompileResult, Diagnostic } from '../types.js';\nimport { compileForOutputFile } from '../compiler.js';\nimport { fatalError, formatDiagnostic, splitDiagnostics } from './diagnostics.js';\nimport type { LocatedError } from './diagnostics.js';\nimport { emittedFilePath } from './paths.js';\nimport type { OutputLocation } from './paths.js';\n\nexport interface TweeTsRollupPluginOptions {\n /** Source directories/files to compile. */\n sources: string[];\n /** Story format ID. */\n format?: string;\n /** Output filename. Default: 'index.html'. */\n outputFilename?: string;\n /** Additional compile options. */\n compileOptions?: Partial<CompileOptions>;\n}\n\n/**\n * What `rollup --watch` watches for the input at the absolute `path`: the path\n * itself, which Rollup watches recursively when it is a folder, unless it holds\n * one of the files the build wrote (`written`). Such a folder is replaced by what\n * it contains, and the written files are left out, so writing them starts no\n * build. A file added straight to that folder later is found at the next build.\n */\nfunction watchTargets(path: string, written: readonly string[]): string[] {\n if (written.includes(path)) return [];\n if (!written.some((file) => file.startsWith(path + sep))) return [path];\n let entries: string[];\n try {\n entries = readdirSync(path);\n } catch {\n return [path]; // Unreadable: Rollup's watcher reports it.\n }\n return entries.flatMap((entry) => watchTargets(join(path, entry), written));\n}\n\nexport function tweeTsPlugin(options: TweeTsRollupPluginOptions) {\n const outputFilename = options.outputFilename ?? 'index.html';\n // In watch mode, the files the last build wrote and those the current one writes\n // (absolute paths). They may sit inside a source folder, which must not rebuild for them.\n let written: readonly string[] = [];\n let writing: string[] = [];\n\n return {\n name: 'twee-ts',\n\n // `rollup --watch` rebuilds for the files the build registers. The story's\n // inputs are read in generateBundle, outside the module graph, so they are\n // registered here. Rollup's watcher watches a folder recursively, files added\n // to it included.\n buildStart(this: { addWatchFile: (id: string) => void; meta: { watchMode: boolean } }) {\n if (!this.meta.watchMode) return;\n // A build that failed before writing leaves the last build's files as they were.\n if (writing.length > 0) {\n written = writing;\n writing = [];\n }\n const extra = options.compileOptions;\n const inputs = [...options.sources, ...(extra?.headFile ? [extra.headFile] : []), ...(extra?.modules ?? [])];\n for (const input of inputs) {\n for (const target of watchTargets(resolve(input), written)) this.addWatchFile(target);\n }\n },\n\n async generateBundle(\n this: {\n emitFile: (opts: { type: 'asset'; fileName: string; source: string }) => void;\n error: (error: LocatedError) => never;\n warn: (message: string) => void;\n meta: { watchMode: boolean };\n },\n outputOptions: OutputLocation,\n bundle: Readonly<Record<string, unknown>>,\n ): Promise<void> {\n // Where the story is written: a source folder may hold it, and its last\n // build must not be loaded back as a source.\n const outFile = emittedFilePath(outputOptions, outputFilename);\n let result: CompileResult;\n try {\n result = await compileForOutputFile(\n { sources: options.sources, formatId: options.format, ...options.compileOptions },\n outFile,\n );\n } catch (e) {\n return this.error(fatalError(e));\n }\n let warnings: Diagnostic[];\n try {\n warnings = splitDiagnostics(result);\n } catch (e) {\n return this.error(e as LocatedError);\n }\n for (const w of warnings) this.warn(formatDiagnostic(w));\n this.emitFile({ type: 'asset', fileName: outputFilename, source: result.output });\n if (this.meta.watchMode) {\n for (const fileName of new Set([...Object.keys(bundle), outputFilename])) {\n const path = emittedFilePath(outputOptions, fileName);\n if (path !== undefined) writing.push(path);\n }\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgCA,SAAS,aAAa,MAAc,SAAsC;CACxE,IAAI,QAAQ,SAAS,IAAI,GAAG,OAAO,CAAC;CACpC,IAAI,CAAC,QAAQ,MAAM,SAAS,KAAK,WAAW,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI;CACtE,IAAI;CACJ,IAAI;EACF,UAAU,YAAY,IAAI;CAC5B,QAAQ;EACN,OAAO,CAAC,IAAI;CACd;CACA,OAAO,QAAQ,SAAS,UAAU,aAAa,KAAK,MAAM,KAAK,GAAG,OAAO,CAAC;AAC5E;AAEA,SAAgB,aAAa,SAAoC;CAC/D,MAAM,iBAAiB,QAAQ,kBAAkB;CAGjD,IAAI,UAA6B,CAAC;CAClC,IAAI,UAAoB,CAAC;CAEzB,OAAO;EACL,MAAM;EAMN,aAAuF;GACrF,IAAI,CAAC,KAAK,KAAK,WAAW;GAE1B,IAAI,QAAQ,SAAS,GAAG;IACtB,UAAU;IACV,UAAU,CAAC;GACb;GACA,MAAM,QAAQ,QAAQ;GACtB,MAAM,SAAS;IAAC,GAAG,QAAQ;IAAS,GAAI,OAAO,WAAW,CAAC,MAAM,QAAQ,IAAI,CAAC;IAAI,GAAI,OAAO,WAAW,CAAC;GAAE;GAC3G,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,UAAU,aAAa,QAAQ,KAAK,GAAG,OAAO,GAAG,KAAK,aAAa,MAAM;EAExF;EAEA,MAAM,eAOJ,eACA,QACe;GAGf,MAAM,UAAU,gBAAgB,eAAe,cAAc;GAC7D,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,qBACb;KAAE,SAAS,QAAQ;KAAS,UAAU,QAAQ;KAAQ,GAAG,QAAQ;IAAe,GAChF,OACF;GACF,SAAS,GAAG;IACV,OAAO,KAAK,MAAM,WAAW,CAAC,CAAC;GACjC;GACA,IAAI;GACJ,IAAI;IACF,WAAW,iBAAiB,MAAM;GACpC,SAAS,GAAG;IACV,OAAO,KAAK,MAAM,CAAiB;GACrC;GACA,KAAK,MAAM,KAAK,UAAU,KAAK,KAAK,iBAAiB,CAAC,CAAC;GACvD,KAAK,SAAS;IAAE,MAAM;IAAS,UAAU;IAAgB,QAAQ,OAAO;GAAO,CAAC;GAChF,IAAI,KAAK,KAAK,WACZ,KAAK,MAAM,4BAAY,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,MAAM,GAAG,cAAc,CAAC,GAAG;IACxE,MAAM,OAAO,gBAAgB,eAAe,QAAQ;IACpD,IAAI,SAAS,KAAA,GAAW,QAAQ,KAAK,IAAI;GAC3C;EAEJ;CACF;AACF"}
1
+ {"version":3,"file":"rollup.js","names":[],"sources":["../../src/plugins/rollup.ts"],"sourcesContent":["/**\n * Ready-made Rollup plugin for twee-ts.\n * Compiles .tw files and emits HTML as an asset. Compile errors fail the build\n * and emit nothing; warnings go through Rollup's warnings.\n */\nimport { readdirSync, statSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { CompileOptions, CompileResult, Diagnostic } from '../types.js';\nimport { compileForOutputFile } from '../compiler.js';\nimport { outputPaths, realPathOf, walkedEntry } from '../filesystem.js';\nimport type { OutputPaths } from '../filesystem.js';\nimport { fatalError, formatDiagnostic, splitDiagnostics } from './diagnostics.js';\nimport type { LocatedError } from './diagnostics.js';\nimport { createOutputRecord, outputLocations } from './paths.js';\nimport type { OutputLocation } from './paths.js';\n\nexport interface TweeTsRollupPluginOptions {\n /** Source directories/files to compile. */\n sources: string[];\n /** Story format ID. */\n format?: string;\n /** Output filename. Default: 'index.html'. */\n outputFilename?: string;\n /** Additional compile options. */\n compileOptions?: Partial<CompileOptions>;\n}\n\n/**\n * What `rollup --watch` watches for the input at `path`, whose real path is\n * `real`. `isRoot`: the input itself, which is followed wherever it leads.\n *\n * Rollup watches a folder recursively and follows the links in it. So a folder\n * is registered whole only when nothing under it is left out of the story: no\n * file or folder the build writes (`outputs`), and no link to a folder, which\n * source discovery doesn't follow and which may lead to the output. Any other\n * folder is replaced by what it contains, minus those, so writing the outputs\n * starts no build. A file added straight to such a folder is found at the next\n * build. A path the build writes to is never registered itself: Rollup fails a\n * watch build whose watched files include its `output.dir` or `output.file`.\n */\nfunction watchTargets(path: string, real: string, outputs: OutputPaths, isRoot: boolean): string[] {\n if (outputs.isFile(real) || (!isRoot && outputs.isDir(real))) return [];\n let entry;\n try {\n entry = isRoot ? { stat: statSync(path), real } : walkedEntry(path, real);\n } catch {\n // An input Rollup's watcher reports, unless it is an output location. Below an\n // input, what source discovery can't read (a link to nothing, say) is skipped.\n return isRoot && !outputs.isDir(real) ? [path] : [];\n }\n if (entry === undefined) return []; // A link to a folder, not followed.\n if (outputs.isFile(entry.real)) return [];\n if (!entry.stat.isDirectory()) return [path];\n let names: string[];\n try {\n names = readdirSync(path);\n } catch {\n return isRoot && !outputs.isDir(real) ? [path] : [];\n }\n const children = names.map((name) => {\n const child = join(path, name);\n return { path: child, targets: watchTargets(child, join(entry.real, name), outputs, false) };\n });\n const whole =\n !outputs.holds(entry.real) &&\n children.every(({ path: child, targets }) => targets.length === 1 && targets[0] === child);\n return whole ? [path] : children.flatMap(({ targets }) => targets);\n}\n\nexport function tweeTsPlugin(options: TweeTsRollupPluginOptions) {\n const outputFilename = options.outputFilename ?? 'index.html';\n // Every path this plugin's builds write: each output's folder or file, its story,\n // and the files of each bundle. A source folder may hold any of them, and none\n // may be read back as a source or rebuild under watch.\n const record = createOutputRecord(outputFilename);\n\n return {\n name: 'twee-ts',\n\n // Rollup's watch mode and CLI pass the whole config here, outputs included, so\n // they are known before the first build starts and before any is written.\n // Typed `object`: a type with only optional properties would share none with\n // Rollup's InputOptions, and the plugin would no longer be assignable to Plugin.\n options(inputOptions: object): undefined {\n const { output } = inputOptions as { readonly output?: unknown };\n for (const location of outputLocations(output)) record.addLocation(location);\n return undefined;\n },\n\n // `rollup --watch` rebuilds for the files the build registers. The story's\n // inputs are read in generateBundle, outside the module graph, so they are\n // registered here. Rollup's watcher watches a folder recursively, files added\n // to it included.\n buildStart(this: { addWatchFile: (id: string) => void; meta: { watchMode: boolean } }) {\n if (!this.meta.watchMode) return;\n const outputs = outputPaths(record.outputs());\n const extra = options.compileOptions;\n const inputs = [...options.sources, ...(extra?.headFile ? [extra.headFile] : []), ...(extra?.modules ?? [])];\n for (const input of inputs) {\n for (const target of watchTargets(resolve(input), realPathOf(input), outputs, true)) this.addWatchFile(target);\n }\n },\n\n // Runs for every output before any of them generates its bundle, when the\n // outputs are written together (as the CLI and watch mode write them).\n renderStart(outputOptions: OutputLocation): void {\n record.addLocation(outputOptions);\n },\n\n async generateBundle(\n this: {\n emitFile: (opts: { type: 'asset'; fileName: string; source: string }) => void;\n error: (error: LocatedError) => never;\n warn: (message: string) => void;\n },\n outputOptions: OutputLocation,\n bundle: Readonly<Record<string, unknown>>,\n ): Promise<void> {\n // Where this output writes the story and the bundle. A source folder may hold\n // them, or another output's, and no last build of them may be read back.\n record.addLocation(outputOptions);\n record.addFiles(outputOptions, Object.keys(bundle));\n let result: CompileResult;\n try {\n result = await compileForOutputFile(\n { sources: options.sources, formatId: options.format, ...options.compileOptions },\n record.outputs(),\n );\n } catch (e) {\n return this.error(fatalError(e));\n }\n let warnings: Diagnostic[];\n try {\n warnings = splitDiagnostics(result);\n } catch (e) {\n return this.error(e as LocatedError);\n }\n for (const w of warnings) this.warn(formatDiagnostic(w));\n this.emitFile({ type: 'asset', fileName: outputFilename, source: result.output });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAS,aAAa,MAAc,MAAc,SAAsB,QAA2B;CACjG,IAAI,QAAQ,OAAO,IAAI,KAAM,CAAC,UAAU,QAAQ,MAAM,IAAI,GAAI,OAAO,CAAC;CACtE,IAAI;CACJ,IAAI;EACF,QAAQ,SAAS;GAAE,MAAM,SAAS,IAAI;GAAG;EAAK,IAAI,YAAY,MAAM,IAAI;CAC1E,QAAQ;EAGN,OAAO,UAAU,CAAC,QAAQ,MAAM,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC;CACpD;CACA,IAAI,UAAU,KAAA,GAAW,OAAO,CAAC;CACjC,IAAI,QAAQ,OAAO,MAAM,IAAI,GAAG,OAAO,CAAC;CACxC,IAAI,CAAC,MAAM,KAAK,YAAY,GAAG,OAAO,CAAC,IAAI;CAC3C,IAAI;CACJ,IAAI;EACF,QAAQ,YAAY,IAAI;CAC1B,QAAQ;EACN,OAAO,UAAU,CAAC,QAAQ,MAAM,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC;CACpD;CACA,MAAM,WAAW,MAAM,KAAK,SAAS;EACnC,MAAM,QAAQ,KAAK,MAAM,IAAI;EAC7B,OAAO;GAAE,MAAM;GAAO,SAAS,aAAa,OAAO,KAAK,MAAM,MAAM,IAAI,GAAG,SAAS,KAAK;EAAE;CAC7F,CAAC;CAID,OAFE,CAAC,QAAQ,MAAM,MAAM,IAAI,KACzB,SAAS,OAAO,EAAE,MAAM,OAAO,cAAc,QAAQ,WAAW,KAAK,QAAQ,OAAO,KAAK,IAC5E,CAAC,IAAI,IAAI,SAAS,SAAS,EAAE,cAAc,OAAO;AACnE;AAEA,SAAgB,aAAa,SAAoC;CAC/D,MAAM,iBAAiB,QAAQ,kBAAkB;CAIjD,MAAM,SAAS,mBAAmB,cAAc;CAEhD,OAAO;EACL,MAAM;EAMN,QAAQ,cAAiC;GACvC,MAAM,EAAE,WAAW;GACnB,KAAK,MAAM,YAAY,gBAAgB,MAAM,GAAG,OAAO,YAAY,QAAQ;EAE7E;EAMA,aAAuF;GACrF,IAAI,CAAC,KAAK,KAAK,WAAW;GAC1B,MAAM,UAAU,YAAY,OAAO,QAAQ,CAAC;GAC5C,MAAM,QAAQ,QAAQ;GACtB,MAAM,SAAS;IAAC,GAAG,QAAQ;IAAS,GAAI,OAAO,WAAW,CAAC,MAAM,QAAQ,IAAI,CAAC;IAAI,GAAI,OAAO,WAAW,CAAC;GAAE;GAC3G,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,UAAU,aAAa,QAAQ,KAAK,GAAG,WAAW,KAAK,GAAG,SAAS,IAAI,GAAG,KAAK,aAAa,MAAM;EAEjH;EAIA,YAAY,eAAqC;GAC/C,OAAO,YAAY,aAAa;EAClC;EAEA,MAAM,eAMJ,eACA,QACe;GAGf,OAAO,YAAY,aAAa;GAChC,OAAO,SAAS,eAAe,OAAO,KAAK,MAAM,CAAC;GAClD,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,qBACb;KAAE,SAAS,QAAQ;KAAS,UAAU,QAAQ;KAAQ,GAAG,QAAQ;IAAe,GAChF,OAAO,QAAQ,CACjB;GACF,SAAS,GAAG;IACV,OAAO,KAAK,MAAM,WAAW,CAAC,CAAC;GACjC;GACA,IAAI;GACJ,IAAI;IACF,WAAW,iBAAiB,MAAM;GACpC,SAAS,GAAG;IACV,OAAO,KAAK,MAAM,CAAiB;GACrC;GACA,KAAK,MAAM,KAAK,UAAU,KAAK,KAAK,iBAAiB,CAAC,CAAC;GACvD,KAAK,SAAS;IAAE,MAAM;IAAS,UAAU;IAAgB,QAAQ,OAAO;GAAO,CAAC;EAClF;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { t as CompileOptions } from "../types-dR5vTVu2.js";
1
+ import { t as CompileOptions } from "../types-D8Xfe0l8.js";
2
2
  import { Plugin } from "vite";
3
3
  //#region src/plugins/vite.d.ts
4
4
  export interface TweeTsVitePluginOptions {
@@ -1,6 +1,6 @@
1
- import { a as fatalError, c as compileForOutputFile, d as mediaTypeFromFilename, i as toPosix, l as getFilenames, n as isInside, o as formatDiagnostic, r as isViteConfigTemp, s as splitDiagnostics, t as emittedFilePath, u as isExcluded } from "../paths-w4ulhCFb.js";
1
+ import { a as toPosix, c as splitDiagnostics, d as isExcluded, f as outputPaths, h as mediaTypeFromFilename, i as outputLocations, l as compileForOutputFile, m as walkedEntry, n as isInside, o as fatalError, p as realPathOf, r as isViteConfigTemp, s as formatDiagnostic, t as createOutputRecord, u as getFilenames } from "../paths-DgO9oo6G.js";
2
2
  import { readdirSync, statSync } from "node:fs";
3
- import { resolve } from "node:path";
3
+ import { join, relative, resolve } from "node:path";
4
4
  import { stripVTControlCharacters } from "node:util";
5
5
  import { build, version } from "vite";
6
6
  //#region src/plugins/vite.ts
@@ -119,23 +119,52 @@ function headInputs(options) {
119
119
  return [...extra?.headFile ? [extra.headFile] : [], ...extra?.modules ?? []].map((p) => toPosix(resolve(p)));
120
120
  }
121
121
  /**
122
- * Whether a file (forward-slash path) is an input the compile leaves out: the
123
- * story HTML a build writes (`builtStory`, an absolute path), and the sources
124
- * `exclude` matches. `exclude` never applies to the head file and modules.
122
+ * Whether a file (forward-slash path) is a source `exclude` matches. `exclude`
123
+ * never applies to the head file and modules.
125
124
  */
126
- function excludedInput(options, builtStory) {
125
+ function excludedByGlob(options) {
127
126
  const exclude = options.compileOptions?.exclude ?? [];
128
127
  const notExcludable = headInputs(options);
129
- const output = builtStory === void 0 ? void 0 : toPosix(builtStory);
130
- return (file) => file === output || isExcluded(file, exclude) && !isInside(file, notExcludable);
128
+ return (file) => isExcluded(file, exclude) && !isInside(file, notExcludable);
131
129
  }
132
130
  /**
133
- * Where a build writes the story HTML with this config: `build.outDir`, against
134
- * the root. It may sit inside a source folder, where neither a compile nor the
135
- * watchers may take it for a source.
131
+ * Where a build with this config writes: each output's folder, the bundler's
132
+ * `output.dir` or else `build.outDir`, or the bundler's `output.file`. Vite
133
+ * resolves an `output.dir` against the root when it empties the folder and
134
+ * copies the public files into it, while the bundler resolves it against the
135
+ * working directory when it writes; both count.
136
136
  */
137
- function builtStoryPath(config, outputFilename) {
138
- return resolve(config.root, config.build.outDir, outputFilename);
137
+ function configOutputLocations(config) {
138
+ const outDir = resolve(config.root, config.build.outDir);
139
+ const option = config.build[viteMajor >= 8 ? "rolldownOptions" : "rollupOptions"]?.output;
140
+ return (option === void 0 ? [{}] : Array.isArray(option) ? option : [option]).flatMap((output) => {
141
+ const [location] = outputLocations(output);
142
+ if (location === void 0) return [{ dir: outDir }];
143
+ if (location.dir === void 0) return [location];
144
+ return [{ dir: resolve(location.dir) }, { dir: resolve(config.root, location.dir) }];
145
+ });
146
+ }
147
+ /**
148
+ * Every path a build with this config writes, as far as the config tells: each
149
+ * output's folder (see configOutputLocations), the story in it, and the copies
150
+ * of the public files Vite puts there. The plugin adds the files each bundle
151
+ * writes as it builds (see createOutputRecord).
152
+ */
153
+ function configOutputs(config, outputFilename) {
154
+ const record = createOutputRecord(outputFilename);
155
+ const publicFiles = config.build.copyPublicDir && config.publicDir ? getFilenames([config.publicDir]).filenames.map((file) => relative(config.publicDir, resolve(file))) : [];
156
+ for (const location of configOutputLocations(config)) {
157
+ record.addLocation(location);
158
+ record.addFiles(location, publicFiles);
159
+ }
160
+ return record.outputs();
161
+ }
162
+ /** Both builds' outputs together. */
163
+ function mergeOutputs(a, b) {
164
+ return {
165
+ files: [...a.files, ...b.files],
166
+ dirs: [...a.dirs, ...b.dirs]
167
+ };
139
168
  }
140
169
  /** Absolute forward-slash paths whose changes recompile the story: sources, head file, modules. */
141
170
  function watchedInputs(options) {
@@ -143,43 +172,50 @@ function watchedInputs(options) {
143
172
  }
144
173
  /**
145
174
  * What `vite build --watch` watches for the story: every file under `inputs`
146
- * (forward-slash paths) but those `skip` returns true for, and every folder
147
- * under them. The bundler watches each path on its own, not recursively, so a
148
- * folder is listed for the files added to it or deleted from it.
175
+ * (forward-slash paths) that source discovery reads, but those `skip` returns
176
+ * true for, and every folder under them. The bundler watches each path on its
177
+ * own, not recursively, so a folder is listed for the files added to it or
178
+ * deleted from it. As in source discovery, a link to a folder is not followed.
149
179
  *
150
180
  * A watched folder may still report changes in its subfolders (Rolldown's
151
- * watcher on Linux does), so no folder that holds the story the build writes
152
- * (`story`, a forward-slash path) is listed: writing the story would start the
153
- * next build. A file added straight to such a folder is found when another
154
- * change starts a build.
181
+ * watcher on Linux does), so no path the build writes (`outputs`) is listed, nor
182
+ * a folder that holds one: writing the outputs would start the next build. A file
183
+ * added straight to such a folder is found when another change starts a build.
155
184
  */
156
- function buildWatchTargets(inputs, skip, story) {
185
+ function buildWatchTargets(inputs, skip, outputs) {
157
186
  const targets = [];
158
- const walk = (path) => {
159
- let entries;
187
+ const walk = (path, real, isRoot) => {
188
+ if (outputs.isFile(real) || !isRoot && outputs.isDir(real)) return;
189
+ let entry;
190
+ let names;
160
191
  try {
161
- if (!statSync(path).isDirectory()) {
192
+ entry = isRoot ? {
193
+ stat: statSync(path),
194
+ real
195
+ } : walkedEntry(path, real);
196
+ if (entry === void 0 || outputs.isFile(entry.real)) return;
197
+ if (!entry.stat.isDirectory()) {
162
198
  if (!skip(path)) targets.push(path);
163
199
  return;
164
200
  }
165
- entries = readdirSync(path);
201
+ names = readdirSync(path);
166
202
  } catch {
167
203
  return;
168
204
  }
169
- if (story === void 0 || !story.startsWith(`${path}/`)) targets.push(path);
170
- for (const entry of entries) walk(`${path}/${entry}`);
205
+ if (!outputs.holds(entry.real)) targets.push(path);
206
+ for (const name of names) walk(`${path}/${name}`, join(entry.real, name), false);
171
207
  };
172
- for (const input of inputs) walk(input);
208
+ for (const input of inputs) walk(input, realPathOf(input), true);
173
209
  return targets;
174
210
  }
175
211
  /**
176
- * Every file under `inputs` (forward-slash paths) but those `skip` returns true
177
- * for, with what a change to it alters: modification time, size and inode (a
178
- * file replaced by a new one).
212
+ * Every file under `inputs` (forward-slash paths) that source discovery reads,
213
+ * leaving out `outputs`, but those `skip` returns true for, with what a change to
214
+ * it alters: modification time, size and inode (a file replaced by a new one).
179
215
  */
180
- function inputFiles(inputs, skip) {
216
+ function inputFiles(inputs, skip, outputs) {
181
217
  const files = /* @__PURE__ */ new Map();
182
- for (const filename of getFilenames([...inputs]).filenames) {
218
+ for (const filename of getFilenames(inputs, outputs).filenames) {
183
219
  const file = toPosix(resolve(filename));
184
220
  if (skip(file)) continue;
185
221
  try {
@@ -373,8 +409,9 @@ function tweeTsPlugin(options) {
373
409
  const cache = /* @__PURE__ */ new Map();
374
410
  let innerBuild = false;
375
411
  let building = false;
376
- let builtStory;
377
- let stopDev;
412
+ let buildConfig;
413
+ const record = createOutputRecord(outputFilename);
414
+ const allOutputs = (config) => mergeOutputs(configOutputs(config, outputFilename), record.outputs());
378
415
  if (options.entry && viteMajor < 8) throw new Error(`twee-ts: the entry option needs Vite 8 or newer (found ${version}).`);
379
416
  return {
380
417
  name: "twee-ts",
@@ -388,15 +425,15 @@ function tweeTsPlugin(options) {
388
425
  },
389
426
  configResolved(config) {
390
427
  if (innerBuild) return;
391
- builtStory = builtStoryPath(config, outputFilename);
428
+ if (building) buildConfig = config;
392
429
  if (!options.entry) return;
393
430
  const sources = options.sources.map((p) => toPosix(resolve(p)));
394
431
  if (isInside(toPosix(resolve(options.entry)), sources)) config.logger.warn(`[twee-ts] The entry ${options.entry} is inside the story sources (${options.sources.join(", ")}). twee-ts also loads the .js and .css files it finds in source folders, unbundled, as Story JavaScript and Story Stylesheet; keep the entry's folder out of \`sources\`.`);
395
432
  },
396
433
  buildStart() {
397
- if (innerBuild || !building || !this.meta.watchMode) return;
398
- const story = builtStory === void 0 ? void 0 : toPosix(builtStory);
399
- for (const target of buildWatchTargets(watchedInputs(options), excludedInput(options, builtStory), story)) this.addWatchFile(target);
434
+ if (innerBuild || !building || !this.meta.watchMode || buildConfig === void 0) return;
435
+ const outputs = outputPaths(allOutputs(buildConfig));
436
+ for (const target of buildWatchTargets(watchedInputs(options), excludedByGlob(options), outputs)) this.addWatchFile(target);
400
437
  },
401
438
  watchChange(id) {
402
439
  if (innerBuild) return;
@@ -414,11 +451,13 @@ function tweeTsPlugin(options) {
414
451
  async handler(outputOptions, bundle) {
415
452
  if (innerBuild) return;
416
453
  for (const [fileName, item] of Object.entries(bundle)) if (item.type === "chunk" && item.facadeModuleId === RESOLVED_EMPTY_INPUT) delete bundle[fileName];
454
+ record.addLocation(outputOptions);
455
+ record.addFiles(outputOptions, Object.keys(bundle));
456
+ const outputs = buildConfig === void 0 ? record.outputs() : allOutputs(buildConfig);
417
457
  const entry = options.entry ? takeEntryFromBundle(bundle) : void 0;
418
- const outFile = emittedFilePath(outputOptions, outputFilename) ?? builtStory;
419
458
  let result;
420
459
  try {
421
- result = await compileForOutputFile(buildCompileOptions(options, entry), outFile, cache);
460
+ result = await compileForOutputFile(buildCompileOptions(options, entry), outputs, cache);
422
461
  } catch (e) {
423
462
  return this.error(fatalError(e));
424
463
  }
@@ -441,8 +480,14 @@ function tweeTsPlugin(options) {
441
480
  const base = server.config.base;
442
481
  const servePaths = outputFilename === "index.html" ? [base, `${base}index.html`] : [`${base}${outputFilename}`];
443
482
  const inputs = watchedInputs(options);
444
- const buildOutput = builtStoryPath(server.config, outputFilename);
445
- const excluded = excludedInput(options, buildOutput);
483
+ let outputs = allOutputs(server.config);
484
+ let output = outputPaths(outputs);
485
+ const refreshOutputs = () => {
486
+ outputs = allOutputs(server.config);
487
+ output = outputPaths(outputs);
488
+ };
489
+ const excludedGlob = excludedByGlob(options);
490
+ const excluded = (file) => excludedGlob(file) || output.isOutput(file, inputs);
446
491
  const entryPath = options.entry ? resolve(options.entry) : void 0;
447
492
  const root = toPosix(server.config.root);
448
493
  server.watcher.add(inputs);
@@ -456,15 +501,18 @@ function tweeTsPlugin(options) {
456
501
  let closed = false;
457
502
  let compiledInputs = /* @__PURE__ */ new Map();
458
503
  let catchingUp;
459
- stopDev = () => {
504
+ const closeWatcher = server.watcher.close.bind(server.watcher);
505
+ server.watcher.close = async () => {
460
506
  closed = true;
461
507
  clearTimeout(timer);
462
508
  pending.clear();
509
+ return closeWatcher();
463
510
  };
464
511
  const touchesEntry = (file) => entryPath !== void 0 && (entryStale ? isInside(file, [root]) : entry?.files.has(file) ?? false);
465
512
  const rebuild = async (changed, initial = false) => {
466
513
  if (closed) return;
467
- compiledInputs = inputFiles(inputs, excluded);
514
+ refreshOutputs();
515
+ compiledInputs = inputFiles(inputs, excludedGlob, outputs);
468
516
  for (const key of [...cache.keys()]) if (changed.has(toPosix(resolve(key)))) cache.delete(key);
469
517
  try {
470
518
  if (entryPath && (entryStale || [...changed].some(touchesEntry))) {
@@ -472,7 +520,7 @@ function tweeTsPlugin(options) {
472
520
  entry = await bundleEntryForDev(server.config, entryPath);
473
521
  entryStale = false;
474
522
  }
475
- const result = await compileForOutputFile(buildCompileOptions(options, entry), buildOutput, cache);
523
+ const result = await compileForOutputFile(buildCompileOptions(options, entry), outputs, cache);
476
524
  for (const w of splitDiagnostics(result)) server.config.logger.warn(`[twee-ts] ${formatDiagnostic(w)}`);
477
525
  html = injectViteClient(result.output, base);
478
526
  lastError = void 0;
@@ -508,7 +556,7 @@ function tweeTsPlugin(options) {
508
556
  const catchUp = () => {
509
557
  catchingUp ??= (async () => {
510
558
  await queue;
511
- const changed = filesChanged(compiledInputs, inputFiles(inputs, excluded));
559
+ const changed = filesChanged(compiledInputs, inputFiles(inputs, excludedGlob, outputs));
512
560
  if (changed.size === 0) return;
513
561
  for (const file of pending) changed.add(file);
514
562
  pending = /* @__PURE__ */ new Set();
@@ -545,9 +593,6 @@ function tweeTsPlugin(options) {
545
593
  }
546
594
  next();
547
595
  });
548
- },
549
- closeBundle() {
550
- stopDev?.();
551
596
  }
552
597
  };
553
598
  }