@rohal12/twee-ts 1.5.0 → 1.6.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.
@@ -262,8 +262,21 @@ function quote(s) {
262
262
  }
263
263
 
264
264
  // src/story.ts
265
+ var passageIndex = /* @__PURE__ */ new WeakMap();
266
+ function getIndex(story) {
267
+ let index = passageIndex.get(story);
268
+ if (!index) {
269
+ index = /* @__PURE__ */ new Map();
270
+ for (let i = 0; i < story.passages.length; i++) {
271
+ const passage = story.passages[i];
272
+ if (passage) index.set(passage.name, i);
273
+ }
274
+ passageIndex.set(story, index);
275
+ }
276
+ return index;
277
+ }
265
278
  function createStory() {
266
- return {
279
+ const story = {
267
280
  name: "",
268
281
  ifid: "",
269
282
  passages: [],
@@ -279,16 +292,17 @@ function createStory() {
279
292
  zoom: 1
280
293
  }
281
294
  };
295
+ passageIndex.set(story, /* @__PURE__ */ new Map());
296
+ return story;
282
297
  }
283
298
  function storyHas(story, name) {
284
- return story.passages.some((p) => p.name === name);
285
- }
286
- function storyIndex(story, name) {
287
- return story.passages.findIndex((p) => p.name === name);
299
+ return getIndex(story).has(name);
288
300
  }
289
301
  function storyAppend(story, p, diagnostics) {
290
- const i = storyIndex(story, p.name);
302
+ const index = getIndex(story);
303
+ const i = index.get(p.name) ?? -1;
291
304
  if (i === -1) {
305
+ index.set(p.name, story.passages.length);
292
306
  story.passages.push(p);
293
307
  } else {
294
308
  diagnostics.push({
@@ -299,9 +313,15 @@ function storyAppend(story, p, diagnostics) {
299
313
  }
300
314
  }
301
315
  function storyPrepend(story, p, diagnostics) {
302
- const i = storyIndex(story, p.name);
316
+ const index = getIndex(story);
317
+ const i = index.get(p.name) ?? -1;
303
318
  if (i === -1) {
304
319
  story.passages.unshift(p);
320
+ index.clear();
321
+ for (let j = 0; j < story.passages.length; j++) {
322
+ const passage = story.passages[j];
323
+ if (passage) index.set(passage.name, j);
324
+ }
305
325
  } else {
306
326
  diagnostics.push({
307
327
  level: "warning",
@@ -591,11 +611,15 @@ function watchFilesystem(pathnames, outFilename, callback) {
591
611
  const watchers = [];
592
612
  let buildTimer = null;
593
613
  const BUILD_DEBOUNCE = 500;
594
- function scheduleBuild() {
614
+ const pendingFiles = /* @__PURE__ */ new Set();
615
+ function scheduleBuild(changedFile) {
616
+ if (changedFile) pendingFiles.add(changedFile);
595
617
  if (buildTimer) clearTimeout(buildTimer);
596
618
  buildTimer = setTimeout(() => {
597
619
  buildTimer = null;
598
- callback();
620
+ const files = pendingFiles.size > 0 ? new Set(pendingFiles) : void 0;
621
+ pendingFiles.clear();
622
+ callback(files);
599
623
  }, BUILD_DEBOUNCE);
600
624
  }
601
625
  for (const pathname of pathnames) {
@@ -605,7 +629,8 @@ function watchFilesystem(pathnames, outFilename, callback) {
605
629
  const abs = resolve(pathname, filename);
606
630
  if (abs === absOutFile) return;
607
631
  if (isKnownFileType(filename)) {
608
- scheduleBuild();
632
+ const rel = relative(process.cwd(), abs);
633
+ scheduleBuild(rel || abs);
609
634
  }
610
635
  });
611
636
  watchers.push(watcher);
@@ -864,6 +889,7 @@ function readFormatSource(format) {
864
889
 
865
890
  // src/loader.ts
866
891
  import { basename } from "path";
892
+ import { statSync as statSync3 } from "fs";
867
893
 
868
894
  // src/lexer.ts
869
895
  var EOF = -1;
@@ -1371,54 +1397,182 @@ function loadInlineSources(story, sources, opts, diagnostics) {
1371
1397
  }
1372
1398
  }
1373
1399
  }
1374
- function loadTwee(story, filename, opts, diagnostics) {
1400
+ function parseTweeFile(filename, opts) {
1375
1401
  const source = readUTF8(filename);
1376
1402
  const result = parseTwee(source, {
1377
1403
  filename,
1378
1404
  trim: opts.trim ?? true,
1379
1405
  twee2Compat: opts.twee2Compat ?? false
1380
1406
  });
1381
- diagnostics.push(...result.diagnostics);
1382
- for (const p of result.passages) {
1383
- storyAdd(story, p, diagnostics);
1384
- }
1407
+ return { passages: result.passages, diagnostics: [...result.diagnostics] };
1385
1408
  }
1386
- function loadTagged(story, tag, filename, diagnostics) {
1409
+ function parseTaggedFile(tag, filename) {
1387
1410
  const source = readUTF8(filename);
1388
- storyAdd(story, { name: basename(filename), tags: [tag], text: source }, diagnostics);
1411
+ return { passages: [{ name: basename(filename), tags: [tag], text: source }], diagnostics: [] };
1389
1412
  }
1390
- function loadMedia(story, tag, filename, diagnostics) {
1413
+ function parseMediaFile(tag, filename) {
1391
1414
  const source = readBase64(filename);
1392
1415
  const name = baseNameWithoutExt(filename);
1393
- storyAdd(
1394
- story,
1395
- {
1396
- name,
1397
- tags: [tag],
1398
- text: `data:${mediaTypeFromFilename(filename)};base64,${source}`
1399
- },
1400
- diagnostics
1401
- );
1416
+ return {
1417
+ passages: [{ name, tags: [tag], text: `data:${mediaTypeFromFilename(filename)};base64,${source}` }],
1418
+ diagnostics: []
1419
+ };
1402
1420
  }
1403
- function loadFont(story, filename, diagnostics) {
1421
+ function parseFontFile(filename) {
1404
1422
  const source = readBase64(filename);
1405
1423
  const name = basename(filename);
1406
1424
  const family = baseNameWithoutExt(filename);
1407
1425
  const ext = normalizedFileExt(filename);
1408
1426
  const mediaType = mediaTypeFromExt(ext);
1409
1427
  const hint = fontFormatHint(ext);
1410
- storyAdd(
1411
- story,
1412
- {
1413
- name,
1414
- tags: ["stylesheet"],
1415
- text: `@font-face {
1428
+ return {
1429
+ passages: [
1430
+ {
1431
+ name,
1432
+ tags: ["stylesheet"],
1433
+ text: `@font-face {
1416
1434
  font-family: "${family}";
1417
1435
  src: url("data:${mediaType};base64,${source}") format("${hint}");
1418
1436
  }`
1419
- },
1420
- diagnostics
1421
- );
1437
+ }
1438
+ ],
1439
+ diagnostics: []
1440
+ };
1441
+ }
1442
+ function parseFile(filename, opts) {
1443
+ const ext = normalizedFileExt(filename);
1444
+ switch (ext) {
1445
+ case "tw":
1446
+ case "twee":
1447
+ return parseTweeFile(filename, opts);
1448
+ case "tw2":
1449
+ case "twee2":
1450
+ return parseTweeFile(filename, { ...opts, twee2Compat: true });
1451
+ case "css":
1452
+ return parseTaggedFile("stylesheet", filename);
1453
+ case "js":
1454
+ return parseTaggedFile("script", filename);
1455
+ case "otf":
1456
+ case "ttf":
1457
+ case "woff":
1458
+ case "woff2":
1459
+ return parseFontFile(filename);
1460
+ case "gif":
1461
+ case "jpeg":
1462
+ case "jpg":
1463
+ case "png":
1464
+ case "svg":
1465
+ case "tif":
1466
+ case "tiff":
1467
+ case "webp":
1468
+ return parseMediaFile("Twine.image", filename);
1469
+ case "aac":
1470
+ case "flac":
1471
+ case "m4a":
1472
+ case "mp3":
1473
+ case "oga":
1474
+ case "ogg":
1475
+ case "opus":
1476
+ case "wav":
1477
+ case "wave":
1478
+ case "weba":
1479
+ return parseMediaFile("Twine.audio", filename);
1480
+ case "mp4":
1481
+ case "ogv":
1482
+ case "webm":
1483
+ return parseMediaFile("Twine.video", filename);
1484
+ case "vtt":
1485
+ return parseMediaFile("Twine.vtt", filename);
1486
+ default:
1487
+ return void 0;
1488
+ }
1489
+ }
1490
+ function loadTwee(story, filename, opts, diagnostics) {
1491
+ const result = parseTweeFile(filename, opts);
1492
+ diagnostics.push(...result.diagnostics);
1493
+ for (const p of result.passages) {
1494
+ storyAdd(story, p, diagnostics);
1495
+ }
1496
+ }
1497
+ function loadTagged(story, tag, filename, diagnostics) {
1498
+ const result = parseTaggedFile(tag, filename);
1499
+ for (const p of result.passages) {
1500
+ storyAdd(story, p, diagnostics);
1501
+ }
1502
+ }
1503
+ function loadMedia(story, tag, filename, diagnostics) {
1504
+ const result = parseMediaFile(tag, filename);
1505
+ for (const p of result.passages) {
1506
+ storyAdd(story, p, diagnostics);
1507
+ }
1508
+ }
1509
+ function loadFont(story, filename, diagnostics) {
1510
+ const result = parseFontFile(filename);
1511
+ for (const p of result.passages) {
1512
+ storyAdd(story, p, diagnostics);
1513
+ }
1514
+ }
1515
+ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles, cache, changedFiles) {
1516
+ const currentFiles = /* @__PURE__ */ new Set();
1517
+ for (const filename of filenames) {
1518
+ if (processedFiles.has(filename)) {
1519
+ diagnostics.push({ level: "warning", message: `load ${filename}: Skipping duplicate.` });
1520
+ continue;
1521
+ }
1522
+ currentFiles.add(filename);
1523
+ const cached = cache.get(filename);
1524
+ if (changedFiles && cached && !changedFiles.has(filename)) {
1525
+ diagnostics.push(...cached.diagnostics);
1526
+ for (const p of cached.passages) {
1527
+ storyAdd(story, p, diagnostics);
1528
+ }
1529
+ processedFiles.add(filename);
1530
+ continue;
1531
+ }
1532
+ let mtimeMs;
1533
+ try {
1534
+ mtimeMs = statSync3(filename).mtimeMs;
1535
+ } catch {
1536
+ continue;
1537
+ }
1538
+ if (cached && cached.mtimeMs === mtimeMs) {
1539
+ diagnostics.push(...cached.diagnostics);
1540
+ for (const p of cached.passages) {
1541
+ storyAdd(story, p, diagnostics);
1542
+ }
1543
+ processedFiles.add(filename);
1544
+ continue;
1545
+ }
1546
+ try {
1547
+ const result = parseFile(filename, opts);
1548
+ if (!result) continue;
1549
+ cache.set(filename, {
1550
+ mtimeMs,
1551
+ passages: result.passages,
1552
+ diagnostics: result.diagnostics
1553
+ });
1554
+ diagnostics.push(...result.diagnostics);
1555
+ for (const p of result.passages) {
1556
+ storyAdd(story, p, diagnostics);
1557
+ }
1558
+ } catch (e) {
1559
+ diagnostics.push({
1560
+ level: "error",
1561
+ message: `load ${filename}: ${e instanceof Error ? e.message : String(e)}`,
1562
+ file: filename
1563
+ });
1564
+ continue;
1565
+ }
1566
+ processedFiles.add(filename);
1567
+ }
1568
+ for (const key of cache.keys()) {
1569
+ if (!currentFiles.has(key)) {
1570
+ cache.delete(key);
1571
+ }
1572
+ }
1573
+ if (story.name !== "" && !story.passages.some((p) => p.name === "StoryTitle")) {
1574
+ storyPrepend(story, { name: "StoryTitle", tags: [], text: story.name }, diagnostics);
1575
+ }
1422
1576
  }
1423
1577
 
1424
1578
  // src/version.ts
@@ -1687,7 +1841,7 @@ function modifyHead(html, modulePaths, headFile, diagnostics) {
1687
1841
  }
1688
1842
 
1689
1843
  // src/remote-formats.ts
1690
- import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1844
+ import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
1691
1845
  import { join as join4 } from "path";
1692
1846
  import { homedir as homedir2 } from "os";
1693
1847
  var DEFAULT_SFA_INDICES = [
@@ -1899,7 +2053,7 @@ function discoverCachedFormats() {
1899
2053
  for (const name of names) {
1900
2054
  const nameDir = join4(cacheDir, name);
1901
2055
  try {
1902
- if (!statSync3(nameDir).isDirectory()) continue;
2056
+ if (!statSync4(nameDir).isDirectory()) continue;
1903
2057
  } catch {
1904
2058
  continue;
1905
2059
  }
@@ -1940,11 +2094,12 @@ async function compileToFile(options) {
1940
2094
  }
1941
2095
  async function watch(options) {
1942
2096
  const controller = new AbortController();
2097
+ const cache = /* @__PURE__ */ new Map();
1943
2098
  const filePaths = options.sources.filter((s) => typeof s === "string");
1944
2099
  const modulePaths = options.modules ?? [];
1945
2100
  const allPaths = [...filePaths, ...modulePaths];
1946
- const handle = watchFilesystem(allPaths, options.outFile, () => {
1947
- buildOutput(options).then((result) => {
2101
+ const handle = watchFilesystem(allPaths, options.outFile, (changedFiles) => {
2102
+ buildOutput(options, cache, changedFiles).then((result) => {
1948
2103
  writeFileSync2(options.outFile, result.output, "utf-8");
1949
2104
  options.onBuild?.(result);
1950
2105
  }).catch((e) => {
@@ -1954,7 +2109,7 @@ async function watch(options) {
1954
2109
  controller.signal.addEventListener("abort", () => handle.close());
1955
2110
  return controller;
1956
2111
  }
1957
- async function buildOutput(options) {
2112
+ async function buildOutput(options, cache, changedFiles) {
1958
2113
  const diagnostics = [];
1959
2114
  const outputMode = options.outputMode ?? "html";
1960
2115
  const trim = options.trim ?? true;
@@ -1975,7 +2130,11 @@ async function buildOutput(options) {
1975
2130
  const sourceFilenames = getFilenames(filePaths);
1976
2131
  const story = createStory();
1977
2132
  const processedFiles = /* @__PURE__ */ new Set();
1978
- loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
2133
+ if (cache) {
2134
+ loadSourcesCached(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles, cache, changedFiles);
2135
+ } else {
2136
+ loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
2137
+ }
1979
2138
  loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
1980
2139
  if (options.tagAliases) {
1981
2140
  story.passages = applyTagAliases(story.passages, options.tagAliases);