vite-plugin-html-pages 1.2.3 → 1.2.4

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/dist/index.js CHANGED
@@ -1,16 +1,82 @@
1
1
  // src/plugin.ts
2
2
  import pLimit from "p-limit";
3
3
 
4
+ // src/static-assets.ts
5
+ import fs from "fs/promises";
6
+ import path from "path";
7
+ import fg from "fast-glob";
8
+ import { transformWithEsbuild } from "vite";
9
+ function normalizeSlashes(value) {
10
+ return value.replace(/\\/g, "/");
11
+ }
12
+ function hasAnySuffix(value, suffixes) {
13
+ return suffixes.some((suffix) => value.endsWith(suffix));
14
+ }
15
+ function toOutputFileName(relativePathFromSrc) {
16
+ if (relativePathFromSrc.endsWith(".ts")) {
17
+ return relativePathFromSrc.slice(0, -3) + ".js";
18
+ }
19
+ return relativePathFromSrc;
20
+ }
21
+ async function collectStaticAssets(args) {
22
+ const { root, pagesDir, pageExtensions } = args;
23
+ const srcDir = path.join(root, pagesDir);
24
+ const entries = await fg("**/*", {
25
+ cwd: srcDir,
26
+ onlyFiles: true,
27
+ dot: false,
28
+ absolute: false
29
+ });
30
+ const assets = [];
31
+ for (const entry of entries) {
32
+ const rel = normalizeSlashes(entry);
33
+ if (hasAnySuffix(rel, pageExtensions)) {
34
+ continue;
35
+ }
36
+ const absolutePath = path.join(srcDir, rel);
37
+ if (rel.endsWith(".ts")) {
38
+ assets.push({
39
+ absolutePath,
40
+ relativePathFromSrc: rel,
41
+ outputFileName: normalizeSlashes(toOutputFileName(rel)),
42
+ kind: "ts"
43
+ });
44
+ continue;
45
+ }
46
+ assets.push({
47
+ absolutePath,
48
+ relativePathFromSrc: rel,
49
+ outputFileName: normalizeSlashes(rel),
50
+ kind: "copy"
51
+ });
52
+ }
53
+ return assets;
54
+ }
55
+ async function buildStaticAssetSource(asset) {
56
+ if (asset.kind === "copy") {
57
+ return fs.readFile(asset.absolutePath);
58
+ }
59
+ const source = await fs.readFile(asset.absolutePath, "utf8");
60
+ const result = await transformWithEsbuild(source, asset.absolutePath, {
61
+ loader: "ts",
62
+ format: "esm",
63
+ target: "es2020",
64
+ sourcemap: false,
65
+ minify: false
66
+ });
67
+ return result.code;
68
+ }
69
+
4
70
  // src/discover.ts
5
- import path2 from "path";
71
+ import path3 from "path";
6
72
 
7
73
  // src/path-utils.ts
8
- import path from "path";
74
+ import path2 from "path";
9
75
  function toPosix(value) {
10
76
  return value.replace(/\\/g, "/");
11
77
  }
12
78
  function normalizeFsPath(value) {
13
- return path.normalize(value);
79
+ return path2.normalize(value);
14
80
  }
15
81
  function normalizeRoutePath(value) {
16
82
  const normalized = toPosix(value).replace(/\/+/g, "/");
@@ -126,21 +192,21 @@ function buildDefaultIncludeGlobs(pagesDir, pageExtensions) {
126
192
  }
127
193
  async function discoverEntryPages(root, options) {
128
194
  const fgModule = await import("fast-glob");
129
- const fg = fgModule.default ?? fgModule;
195
+ const fg2 = fgModule.default ?? fgModule;
130
196
  const pagesDir = options.pagesDir ?? "src";
131
197
  const pageExtensions = options.pageExtensions?.length ? options.pageExtensions : [".ht.js", ".html.js", ".ht.ts", ".html.ts"];
132
198
  const include = Array.isArray(options.include) ? options.include : options.include ? [options.include] : buildDefaultIncludeGlobs(pagesDir, pageExtensions);
133
199
  const exclude = Array.isArray(options.exclude) ? options.exclude : options.exclude ? [options.exclude] : [];
134
- const pagesRoot = normalizeFsPath(path2.join(root, pagesDir));
135
- const files = await fg.glob(include, {
200
+ const pagesRoot = normalizeFsPath(path3.join(root, pagesDir));
201
+ const files = await fg2.glob(include, {
136
202
  cwd: root,
137
203
  ignore: exclude,
138
204
  absolute: true
139
205
  });
140
206
  return files.sort().map((absolutePath) => {
141
207
  const entryPath = normalizeFsPath(absolutePath);
142
- const relativePath = toPosix(path2.relative(root, entryPath));
143
- const relativeFromPagesDir = toPosix(path2.relative(pagesRoot, entryPath));
208
+ const relativePath = toPosix(path3.relative(root, entryPath));
209
+ const relativeFromPagesDir = toPosix(path3.relative(pagesRoot, entryPath));
144
210
  if (relativeFromPagesDir.startsWith("../") || relativeFromPagesDir === "..") {
145
211
  throw new Error(
146
212
  `[${PLUGIN_NAME}] Page is outside pagesDir: ${entryPath} (pagesDir: ${pagesDir})`
@@ -164,8 +230,8 @@ async function discoverEntryPages(root, options) {
164
230
  }
165
231
 
166
232
  // src/dev-server.ts
167
- import fs from "fs";
168
- import path4 from "path";
233
+ import fs2 from "fs";
234
+ import path5 from "path";
169
235
 
170
236
  // src/errors.ts
171
237
  function invalidHtmlReturn(page, value) {
@@ -218,7 +284,7 @@ async function renderPage(page, mod, dev = false) {
218
284
  }
219
285
 
220
286
  // src/module-loader.ts
221
- import path3 from "path";
287
+ import path4 from "path";
222
288
  import { createServer } from "vite";
223
289
  var buildServer = null;
224
290
  async function createPageModuleLoader(args) {
@@ -245,7 +311,7 @@ async function createPageModuleLoader(args) {
245
311
  buildServer = await createServer(config);
246
312
  }
247
313
  return async (entryPath) => {
248
- const relativePath = "/" + path3.relative(root, entryPath).replace(/\\/g, "/");
314
+ const relativePath = "/" + path4.relative(root, entryPath).replace(/\\/g, "/");
249
315
  const mod = await buildServer.ssrLoadModule(relativePath);
250
316
  return mod;
251
317
  };
@@ -269,8 +335,8 @@ function tryRewriteRootAssetToSrc(server, url) {
269
335
  if (!isStaticAssetRequest(url)) return null;
270
336
  if (url.startsWith("/src/")) return null;
271
337
  const root = server.config.root;
272
- const candidate = path4.join(root, "src", url.slice(1));
273
- if (fs.existsSync(candidate)) {
338
+ const candidate = path5.join(root, "src", url.slice(1));
339
+ if (fs2.existsSync(candidate)) {
274
340
  return `/src/${url.slice(1)}`;
275
341
  }
276
342
  return null;
@@ -368,149 +434,6 @@ async function buildPageIndex(args) {
368
434
  return pages;
369
435
  }
370
436
 
371
- // src/assets.ts
372
- import fs2 from "fs";
373
- import path5 from "path";
374
- var EXTERNAL_URL_RE = /^(?:[a-z]+:)?\/\//i;
375
- function isLocalAssetUrl(url) {
376
- return !!url && !url.startsWith("data:") && !url.startsWith("mailto:") && !url.startsWith("tel:") && !url.startsWith("#") && !EXTERNAL_URL_RE.test(url);
377
- }
378
- function stripQueryAndHash(url) {
379
- return url.split("#")[0].split("?")[0];
380
- }
381
- function extractHtmlAssets(html) {
382
- const assets = [];
383
- for (const match of html.matchAll(
384
- /<link\b[^>]*\brel=["']stylesheet["'][^>]*\bhref=["']([^"']+)["'][^>]*>/gi
385
- )) {
386
- assets.push({ kind: "css", url: match[1] });
387
- }
388
- for (const match of html.matchAll(
389
- /<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi
390
- )) {
391
- assets.push({ kind: "js", url: match[1] });
392
- }
393
- return dedupeExtractedAssets(assets);
394
- }
395
- function dedupeExtractedAssets(assets) {
396
- const seen = /* @__PURE__ */ new Set();
397
- const out = [];
398
- for (const asset of assets) {
399
- const key = `${asset.kind}:${asset.url}`;
400
- if (seen.has(key)) continue;
401
- seen.add(key);
402
- out.push(asset);
403
- }
404
- return out;
405
- }
406
- function resolveLocalAssetPath(args) {
407
- const { root, pagesDir, pageDir, url } = args;
408
- if (!isLocalAssetUrl(url)) return null;
409
- const cleanUrl = stripQueryAndHash(url);
410
- let abs;
411
- if (cleanUrl.startsWith("/")) {
412
- abs = path5.join(root, pagesDir, cleanUrl.slice(1));
413
- } else if (cleanUrl.startsWith(`${pagesDir}/`)) {
414
- abs = path5.join(root, cleanUrl);
415
- } else {
416
- const baseDir = pageDir ?? path5.join(root, pagesDir);
417
- abs = path5.resolve(baseDir, cleanUrl);
418
- }
419
- return fs2.existsSync(abs) ? abs : null;
420
- }
421
- function emitHtmlAsset(args) {
422
- const { ctx, kind, absolutePath } = args;
423
- if (kind === "css" || kind === "js") {
424
- return ctx.emitFile({
425
- type: "chunk",
426
- id: absolutePath,
427
- name: path5.basename(absolutePath, path5.extname(absolutePath))
428
- });
429
- }
430
- throw new Error(`[vite-plugin-html-pages] Unsupported asset kind: ${kind}`);
431
- }
432
- function replaceAllLiteral(input, search, replacement) {
433
- return input.split(search).join(replacement);
434
- }
435
- function rewriteHtmlAssetUrls(html, replacements) {
436
- let out = html;
437
- for (const [originalUrl, builtUrl] of replacements) {
438
- out = replaceAllLiteral(
439
- out,
440
- `href="${originalUrl}"`,
441
- `href="${builtUrl}"`
442
- );
443
- out = replaceAllLiteral(
444
- out,
445
- `href='${originalUrl}'`,
446
- `href='${builtUrl}'`
447
- );
448
- out = replaceAllLiteral(
449
- out,
450
- `src="${originalUrl}"`,
451
- `src="${builtUrl}"`
452
- );
453
- out = replaceAllLiteral(
454
- out,
455
- `src='${originalUrl}'`,
456
- `src='${builtUrl}'`
457
- );
458
- }
459
- return out;
460
- }
461
- async function collectHtmlAssetRefs(args) {
462
- const { ctx, root, pagesDir, htmlByPageKey } = args;
463
- const refs = /* @__PURE__ */ new Map();
464
- for (const { html, pageDir } of htmlByPageKey.values()) {
465
- const assets = extractHtmlAssets(html);
466
- for (const asset of assets) {
467
- const abs = resolveLocalAssetPath({
468
- root,
469
- pagesDir,
470
- pageDir,
471
- url: asset.url
472
- });
473
- if (!abs) continue;
474
- const key = `${asset.kind}:${asset.url}`;
475
- if (refs.has(key)) continue;
476
- const refId = emitHtmlAsset({
477
- ctx,
478
- kind: asset.kind,
479
- absolutePath: abs
480
- });
481
- refs.set(key, {
482
- kind: asset.kind,
483
- originalUrl: asset.url,
484
- absolutePath: abs,
485
- refId
486
- });
487
- }
488
- }
489
- return refs;
490
- }
491
- function buildHtmlAssetReplacementMap(args) {
492
- const { ctx, refs, bundle } = args;
493
- const replacements = /* @__PURE__ */ new Map();
494
- for (const ref of refs.values()) {
495
- if (ref.kind === "js") {
496
- const fileName = ctx.getFileName(ref.refId);
497
- replacements.set(ref.originalUrl, `/${fileName}`);
498
- continue;
499
- }
500
- if (ref.kind === "css") {
501
- const jsEntryFile = ctx.getFileName(ref.refId);
502
- const jsChunk = bundle[jsEntryFile];
503
- if (jsChunk && jsChunk.type === "chunk" && "viteMetadata" in jsChunk && jsChunk.viteMetadata?.importedCss && jsChunk.viteMetadata.importedCss.size > 0) {
504
- const cssFile = [...jsChunk.viteMetadata.importedCss][0];
505
- replacements.set(ref.originalUrl, `/${cssFile}`);
506
- continue;
507
- }
508
- replacements.set(ref.originalUrl, `/${jsEntryFile}`);
509
- }
510
- }
511
- return replacements;
512
- }
513
-
514
437
  // src/plugin.ts
515
438
  import fs3 from "fs";
516
439
  import path6 from "path";
@@ -539,9 +462,9 @@ function htPages(options = {}) {
539
462
  let root = process.cwd();
540
463
  let server = null;
541
464
  let devPages = [];
542
- let htmlAssetRefs = /* @__PURE__ */ new Map();
543
465
  const cleanUrls = options.cleanUrls ?? true;
544
466
  const pagesDir = options.pagesDir ?? "src";
467
+ const pageExtensions = options.pageExtensions?.length ? options.pageExtensions : [".ht.js", ".html.js", ".ht.ts", ".html.ts"];
545
468
  function logDebug(enabled, ...args) {
546
469
  if (!enabled) return;
547
470
  console.log(`[${PLUGIN_NAME}]`, ...args);
@@ -630,38 +553,21 @@ function htPages(options = {}) {
630
553
  for (const entry of entries) {
631
554
  this.addWatchFile(entry.entryPath);
632
555
  }
633
- if (server) {
634
- return;
635
- }
636
- htmlAssetRefs.clear();
637
- const { modulesByEntry, pages } = await buildPagesPipeline();
638
- const htmlByPageKey = /* @__PURE__ */ new Map();
639
- for (const page of pages) {
640
- const mod = modulesByEntry.get(page.entryPath);
641
- if (!mod) {
642
- throw new Error(
643
- `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`
644
- );
645
- }
646
- const html = await renderPage(page, mod, false);
647
- htmlByPageKey.set(page.entryPath, {
648
- html,
649
- pageDir: path6.dirname(page.absolutePath)
650
- });
651
- }
652
- htmlAssetRefs = await collectHtmlAssetRefs({
653
- ctx: this,
556
+ const staticAssets = await collectStaticAssets({
654
557
  root,
655
558
  pagesDir,
656
- htmlByPageKey
559
+ pageExtensions
657
560
  });
561
+ for (const asset of staticAssets) {
562
+ this.addWatchFile(asset.absolutePath);
563
+ }
658
564
  logDebug(
659
565
  options.debug,
660
- "collected html assets",
661
- [...htmlAssetRefs.values()].map((ref) => ({
662
- kind: ref.kind,
663
- originalUrl: ref.originalUrl,
664
- absolutePath: ref.absolutePath
566
+ "static assets",
567
+ staticAssets.map((asset) => ({
568
+ kind: asset.kind,
569
+ input: asset.relativePathFromSrc,
570
+ output: asset.outputFileName
665
571
  }))
666
572
  );
667
573
  },
@@ -690,23 +596,31 @@ function htPages(options = {}) {
690
596
  async generateBundle(_, bundle) {
691
597
  try {
692
598
  const { modulesByEntry, pages } = await buildPagesPipeline();
693
- const assetReplacements = buildHtmlAssetReplacementMap({
694
- ctx: this,
695
- refs: htmlAssetRefs,
696
- bundle
599
+ const staticAssets = await collectStaticAssets({
600
+ root,
601
+ pagesDir,
602
+ pageExtensions
697
603
  });
698
604
  logDebug(
699
605
  options.debug,
700
- "asset replacements",
701
- [...assetReplacements.entries()]
606
+ "emitting pages",
607
+ pages.map((p) => p.fileName)
702
608
  );
703
609
  logDebug(
704
610
  options.debug,
705
- "emitting pages",
706
- pages.map((p) => p.fileName)
611
+ "emitting static assets",
612
+ staticAssets.map((asset) => asset.outputFileName)
707
613
  );
708
614
  const limit = pLimit(options.renderConcurrency ?? 8);
709
615
  const batchSize = options.renderBatchSize ?? Math.max(options.renderConcurrency ?? 8, 32);
616
+ for (const asset of staticAssets) {
617
+ const source = await buildStaticAssetSource(asset);
618
+ this.emitFile({
619
+ type: "asset",
620
+ fileName: asset.outputFileName,
621
+ source
622
+ });
623
+ }
710
624
  for (const batch of chunkArray(pages, batchSize)) {
711
625
  await Promise.all(
712
626
  batch.map(
@@ -717,8 +631,7 @@ function htPages(options = {}) {
717
631
  `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`
718
632
  );
719
633
  }
720
- let html = await renderPage(page, mod, false);
721
- html = rewriteHtmlAssetUrls(html, assetReplacements);
634
+ const html = await renderPage(page, mod, false);
722
635
  this.emitFile({
723
636
  type: "asset",
724
637
  fileName: options.mapOutputPath?.(page) ?? page.fileName,
@@ -736,8 +649,7 @@ function htPages(options = {}) {
736
649
  `[${PLUGIN_NAME}] Missing module for 404 page entry: ${notFoundPage.entryPath}`
737
650
  );
738
651
  }
739
- let html = await renderPage(notFoundPage, mod, false);
740
- html = rewriteHtmlAssetUrls(html, assetReplacements);
652
+ const html = await renderPage(notFoundPage, mod, false);
741
653
  this.emitFile({
742
654
  type: "asset",
743
655
  fileName: "404.html",
@@ -746,49 +658,49 @@ function htPages(options = {}) {
746
658
  logDebug(options.debug, "generated 404.html from user page");
747
659
  } else {
748
660
  const default404 = `<!doctype html>
749
- <html lang="en">
750
- <head>
751
- <meta charset="UTF-8" />
752
- <meta name="viewport" content="width=device-width, initial-scale=1" />
753
- <title>404 - Page Not Found</title>
754
- <style>
755
- :root {
756
- color-scheme: light dark;
757
- }
758
- body {
759
- margin: 0;
760
- font-family: system-ui, sans-serif;
761
- min-height: 100vh;
762
- display: grid;
763
- place-items: center;
764
- padding: 2rem;
765
- }
766
- main {
767
- max-width: 40rem;
768
- text-align: center;
769
- }
770
- h1 {
771
- font-size: 3rem;
772
- margin: 0 0 1rem;
773
- }
774
- p {
775
- margin: 0.5rem 0;
776
- line-height: 1.5;
777
- }
778
- a {
779
- color: inherit;
780
- }
781
- </style>
782
- </head>
783
- <body>
784
- <main>
785
- <h1>404</h1>
786
- <p>Page not found.</p>
787
- <p><a href="/">Go back home</a></p>
788
- </main>
789
- </body>
790
- </html>
791
- `;
661
+ <html lang="en">
662
+ <head>
663
+ <meta charset="UTF-8" />
664
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
665
+ <title>404 - Page Not Found</title>
666
+ <style>
667
+ :root {
668
+ color-scheme: light dark;
669
+ }
670
+ body {
671
+ margin: 0;
672
+ font-family: system-ui, sans-serif;
673
+ min-height: 100vh;
674
+ display: grid;
675
+ place-items: center;
676
+ padding: 2rem;
677
+ }
678
+ main {
679
+ max-width: 40rem;
680
+ text-align: center;
681
+ }
682
+ h1 {
683
+ font-size: 3rem;
684
+ margin: 0 0 1rem;
685
+ }
686
+ p {
687
+ margin: 0.5rem 0;
688
+ line-height: 1.5;
689
+ }
690
+ a {
691
+ color: inherit;
692
+ }
693
+ </style>
694
+ </head>
695
+ <body>
696
+ <main>
697
+ <h1>404</h1>
698
+ <p>Page not found.</p>
699
+ <p><a href="/">Go back home</a></p>
700
+ </main>
701
+ </body>
702
+ </html>
703
+ `;
792
704
  this.emitFile({
793
705
  type: "asset",
794
706
  fileName: "404.html",