@moku-labs/web 0.5.5 → 0.5.6
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.cjs +78 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.mjs +78 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1335,6 +1335,23 @@ function calculateReadingTime(text) {
|
|
|
1335
1335
|
function articleToUrl(locale, slug) {
|
|
1336
1336
|
return `/${locale}/${slug}/`;
|
|
1337
1337
|
}
|
|
1338
|
+
/** Matches an `<img>` `src` that points at the co-located `images/` dir (relative or root-relative). */
|
|
1339
|
+
const RELATIVE_IMAGE_SRC = /(<img\b[^>]*?\bsrc=")(?:\.?\/)?images\//g;
|
|
1340
|
+
/**
|
|
1341
|
+
* Rewrite relative co-located image URLs (`./images/x.webp`) in rendered article HTML to the shared
|
|
1342
|
+
* absolute path the build copies them to (`/<slug>/images/...`), so they resolve from any locale page.
|
|
1343
|
+
*
|
|
1344
|
+
* @param html - The rendered article HTML.
|
|
1345
|
+
* @param slug - Article directory name.
|
|
1346
|
+
* @returns The HTML with image `src`s rewritten to absolute paths.
|
|
1347
|
+
* @example
|
|
1348
|
+
* ```ts
|
|
1349
|
+
* rewriteImageUrls('<img src="./images/a.webp">', "post"); // '<img src="/post/images/a.webp">'
|
|
1350
|
+
* ```
|
|
1351
|
+
*/
|
|
1352
|
+
function rewriteImageUrls(html, slug) {
|
|
1353
|
+
return html.replaceAll(RELATIVE_IMAGE_SRC, `$1/${slug}/images/`);
|
|
1354
|
+
}
|
|
1338
1355
|
/**
|
|
1339
1356
|
* Build the canonical "article not found" error for {@link createContentApi.load}.
|
|
1340
1357
|
* Centralised so the null-resolve path and the production draft-suppression path
|
|
@@ -1450,7 +1467,7 @@ async function readArticle(ctx, slug, fileLocale, outLocale, isFallback) {
|
|
|
1450
1467
|
ctx.state.dirtyPaths.delete(filePath);
|
|
1451
1468
|
const { frontmatter, body } = parseFrontmatter(raw, ctx.config);
|
|
1452
1469
|
const processor = ensureProcessor(ctx.state, ctx.config);
|
|
1453
|
-
const html = String(await processor.process(body));
|
|
1470
|
+
const html = rewriteImageUrls(String(await processor.process(body)), slug);
|
|
1454
1471
|
const { readingTime, wordCount } = calculateReadingTime(body);
|
|
1455
1472
|
return {
|
|
1456
1473
|
frontmatter,
|
|
@@ -1662,6 +1679,18 @@ function createContentApi(ctx) {
|
|
|
1662
1679
|
*/
|
|
1663
1680
|
articleToCard(article) {
|
|
1664
1681
|
return toCard(article);
|
|
1682
|
+
},
|
|
1683
|
+
/**
|
|
1684
|
+
* The configured content source directory (e.g. `"./content"`).
|
|
1685
|
+
*
|
|
1686
|
+
* @returns The content directory path from config.
|
|
1687
|
+
* @example
|
|
1688
|
+
* ```ts
|
|
1689
|
+
* api.contentDir(); // "./content"
|
|
1690
|
+
* ```
|
|
1691
|
+
*/
|
|
1692
|
+
contentDir() {
|
|
1693
|
+
return ctx.config.contentDir;
|
|
1665
1694
|
}
|
|
1666
1695
|
};
|
|
1667
1696
|
}
|
|
@@ -3445,6 +3474,52 @@ function readCachedContent(ctx) {
|
|
|
3445
3474
|
return cached instanceof Map ? cached : /* @__PURE__ */ new Map();
|
|
3446
3475
|
}
|
|
3447
3476
|
//#endregion
|
|
3477
|
+
//#region src/plugins/build/phases/content-images.ts
|
|
3478
|
+
/**
|
|
3479
|
+
* @file build phase — content-images. Copies each article's co-located image directory
|
|
3480
|
+
* (`<contentDir>/<slug>/images/`) to a single shared output dir (`<outDir>/<slug>/images/`) reused by
|
|
3481
|
+
* every locale, matching the absolute `/<slug>/images/...` URLs the content renderer emits. Gated by
|
|
3482
|
+
* `config.images`.
|
|
3483
|
+
*/
|
|
3484
|
+
/** Conventional per-article image subdirectory name (alongside `<slug>/<locale>.md`). */
|
|
3485
|
+
const ARTICLE_IMAGE_DIR = "images";
|
|
3486
|
+
/**
|
|
3487
|
+
* Copy every article's co-located `images/` directory to `<outDir>/<slug>/images/`. No-op when
|
|
3488
|
+
* `config.images` is false or the content directory does not exist.
|
|
3489
|
+
*
|
|
3490
|
+
* @param ctx - Plugin context (provides `config`, `log`, `require`).
|
|
3491
|
+
* @returns The number of directories copied (one per article that has an `images/` dir).
|
|
3492
|
+
* @example
|
|
3493
|
+
* ```ts
|
|
3494
|
+
* const copied = await copyContentImages(ctx);
|
|
3495
|
+
* ```
|
|
3496
|
+
*/
|
|
3497
|
+
async function copyContentImages(ctx) {
|
|
3498
|
+
if (!ctx.config.images) {
|
|
3499
|
+
ctx.log.debug("build:content-images", { skipped: true });
|
|
3500
|
+
return 0;
|
|
3501
|
+
}
|
|
3502
|
+
const contentDir = ctx.require(contentPlugin).contentDir();
|
|
3503
|
+
if (!(0, node_fs.existsSync)(contentDir)) {
|
|
3504
|
+
ctx.log.debug("build:content-images", {
|
|
3505
|
+
skipped: true,
|
|
3506
|
+
reason: "no content dir"
|
|
3507
|
+
});
|
|
3508
|
+
return 0;
|
|
3509
|
+
}
|
|
3510
|
+
const entries = await (0, node_fs_promises.readdir)(contentDir, { withFileTypes: true });
|
|
3511
|
+
let copied = 0;
|
|
3512
|
+
for (const entry of entries) {
|
|
3513
|
+
if (!entry.isDirectory()) continue;
|
|
3514
|
+
const source = node_path$1.default.join(contentDir, entry.name, ARTICLE_IMAGE_DIR);
|
|
3515
|
+
if (!(0, node_fs.existsSync)(source)) continue;
|
|
3516
|
+
await (0, node_fs_promises.cp)(source, node_path$1.default.join(ctx.config.outDir, entry.name, ARTICLE_IMAGE_DIR), { recursive: true });
|
|
3517
|
+
copied += 1;
|
|
3518
|
+
}
|
|
3519
|
+
ctx.log.debug("build:content-images", { copied });
|
|
3520
|
+
return copied;
|
|
3521
|
+
}
|
|
3522
|
+
//#endregion
|
|
3448
3523
|
//#region src/plugins/build/phases/feeds.ts
|
|
3449
3524
|
/**
|
|
3450
3525
|
* @file build phase 4 — feeds. Generates RSS/Atom/JSON from cached content plus
|
|
@@ -4690,6 +4765,7 @@ const PHASE_ORDER = [
|
|
|
4690
4765
|
"content",
|
|
4691
4766
|
"images",
|
|
4692
4767
|
"pages",
|
|
4768
|
+
"content-images",
|
|
4693
4769
|
"feeds",
|
|
4694
4770
|
"sitemap",
|
|
4695
4771
|
"og-images",
|
|
@@ -4796,6 +4872,7 @@ async function runPipeline(ctx, options) {
|
|
|
4796
4872
|
await withPhase(phaseContext, "bundle", () => bundle(phaseContext));
|
|
4797
4873
|
await Promise.all([withPhase(phaseContext, "content", () => loadContent(phaseContext)), withPhase(phaseContext, "images", () => processImages(phaseContext))]);
|
|
4798
4874
|
const pages = await withPhase(phaseContext, "pages", () => renderPages(phaseContext));
|
|
4875
|
+
await withPhase(phaseContext, "content-images", () => copyContentImages(phaseContext));
|
|
4799
4876
|
await runOutputs(phaseContext);
|
|
4800
4877
|
await withPhase(phaseContext, "root-index", async () => {
|
|
4801
4878
|
if (pages.rootHtml !== null) await (0, node_fs_promises.writeFile)(node_path$1.default.join(outDir, "index.html"), pages.rootHtml, "utf8");
|
package/dist/index.d.cts
CHANGED
|
@@ -1575,7 +1575,7 @@ interface State$2 {
|
|
|
1575
1575
|
* const phase: PhaseName = "bundle";
|
|
1576
1576
|
* ```
|
|
1577
1577
|
*/
|
|
1578
|
-
type PhaseName = "bundle" | "content" | "images" | "pages" | "feeds" | "sitemap" | "og-images" | "public" | "not-found" | "locale-redirects" | "root-index";
|
|
1578
|
+
type PhaseName = "bundle" | "content" | "images" | "pages" | "content-images" | "feeds" | "sitemap" | "og-images" | "public" | "not-found" | "locale-redirects" | "root-index";
|
|
1579
1579
|
/**
|
|
1580
1580
|
* Result of a completed build run.
|
|
1581
1581
|
*
|
|
@@ -1849,6 +1849,12 @@ type Api$1 = {
|
|
|
1849
1849
|
* @param article - The source article.
|
|
1850
1850
|
*/
|
|
1851
1851
|
articleToCard(article: Article): ArticleCard;
|
|
1852
|
+
/**
|
|
1853
|
+
* The configured content source directory (e.g. `"./content"`). Lets the build copy each
|
|
1854
|
+
* article's co-located assets (`<contentDir>/<slug>/images/`) into the output so the absolute
|
|
1855
|
+
* image URLs the renderer emits resolve.
|
|
1856
|
+
*/
|
|
1857
|
+
contentDir(): string;
|
|
1852
1858
|
};
|
|
1853
1859
|
//#endregion
|
|
1854
1860
|
//#region src/plugins/content/index.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1575,7 +1575,7 @@ interface State$2 {
|
|
|
1575
1575
|
* const phase: PhaseName = "bundle";
|
|
1576
1576
|
* ```
|
|
1577
1577
|
*/
|
|
1578
|
-
type PhaseName = "bundle" | "content" | "images" | "pages" | "feeds" | "sitemap" | "og-images" | "public" | "not-found" | "locale-redirects" | "root-index";
|
|
1578
|
+
type PhaseName = "bundle" | "content" | "images" | "pages" | "content-images" | "feeds" | "sitemap" | "og-images" | "public" | "not-found" | "locale-redirects" | "root-index";
|
|
1579
1579
|
/**
|
|
1580
1580
|
* Result of a completed build run.
|
|
1581
1581
|
*
|
|
@@ -1849,6 +1849,12 @@ type Api$1 = {
|
|
|
1849
1849
|
* @param article - The source article.
|
|
1850
1850
|
*/
|
|
1851
1851
|
articleToCard(article: Article): ArticleCard;
|
|
1852
|
+
/**
|
|
1853
|
+
* The configured content source directory (e.g. `"./content"`). Lets the build copy each
|
|
1854
|
+
* article's co-located assets (`<contentDir>/<slug>/images/`) into the output so the absolute
|
|
1855
|
+
* image URLs the renderer emits resolve.
|
|
1856
|
+
*/
|
|
1857
|
+
contentDir(): string;
|
|
1852
1858
|
};
|
|
1853
1859
|
//#endregion
|
|
1854
1860
|
//#region src/plugins/content/index.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1322,6 +1322,23 @@ function calculateReadingTime(text) {
|
|
|
1322
1322
|
function articleToUrl(locale, slug) {
|
|
1323
1323
|
return `/${locale}/${slug}/`;
|
|
1324
1324
|
}
|
|
1325
|
+
/** Matches an `<img>` `src` that points at the co-located `images/` dir (relative or root-relative). */
|
|
1326
|
+
const RELATIVE_IMAGE_SRC = /(<img\b[^>]*?\bsrc=")(?:\.?\/)?images\//g;
|
|
1327
|
+
/**
|
|
1328
|
+
* Rewrite relative co-located image URLs (`./images/x.webp`) in rendered article HTML to the shared
|
|
1329
|
+
* absolute path the build copies them to (`/<slug>/images/...`), so they resolve from any locale page.
|
|
1330
|
+
*
|
|
1331
|
+
* @param html - The rendered article HTML.
|
|
1332
|
+
* @param slug - Article directory name.
|
|
1333
|
+
* @returns The HTML with image `src`s rewritten to absolute paths.
|
|
1334
|
+
* @example
|
|
1335
|
+
* ```ts
|
|
1336
|
+
* rewriteImageUrls('<img src="./images/a.webp">', "post"); // '<img src="/post/images/a.webp">'
|
|
1337
|
+
* ```
|
|
1338
|
+
*/
|
|
1339
|
+
function rewriteImageUrls(html, slug) {
|
|
1340
|
+
return html.replaceAll(RELATIVE_IMAGE_SRC, `$1/${slug}/images/`);
|
|
1341
|
+
}
|
|
1325
1342
|
/**
|
|
1326
1343
|
* Build the canonical "article not found" error for {@link createContentApi.load}.
|
|
1327
1344
|
* Centralised so the null-resolve path and the production draft-suppression path
|
|
@@ -1437,7 +1454,7 @@ async function readArticle(ctx, slug, fileLocale, outLocale, isFallback) {
|
|
|
1437
1454
|
ctx.state.dirtyPaths.delete(filePath);
|
|
1438
1455
|
const { frontmatter, body } = parseFrontmatter(raw, ctx.config);
|
|
1439
1456
|
const processor = ensureProcessor(ctx.state, ctx.config);
|
|
1440
|
-
const html = String(await processor.process(body));
|
|
1457
|
+
const html = rewriteImageUrls(String(await processor.process(body)), slug);
|
|
1441
1458
|
const { readingTime, wordCount } = calculateReadingTime(body);
|
|
1442
1459
|
return {
|
|
1443
1460
|
frontmatter,
|
|
@@ -1649,6 +1666,18 @@ function createContentApi(ctx) {
|
|
|
1649
1666
|
*/
|
|
1650
1667
|
articleToCard(article) {
|
|
1651
1668
|
return toCard(article);
|
|
1669
|
+
},
|
|
1670
|
+
/**
|
|
1671
|
+
* The configured content source directory (e.g. `"./content"`).
|
|
1672
|
+
*
|
|
1673
|
+
* @returns The content directory path from config.
|
|
1674
|
+
* @example
|
|
1675
|
+
* ```ts
|
|
1676
|
+
* api.contentDir(); // "./content"
|
|
1677
|
+
* ```
|
|
1678
|
+
*/
|
|
1679
|
+
contentDir() {
|
|
1680
|
+
return ctx.config.contentDir;
|
|
1652
1681
|
}
|
|
1653
1682
|
};
|
|
1654
1683
|
}
|
|
@@ -3432,6 +3461,52 @@ function readCachedContent(ctx) {
|
|
|
3432
3461
|
return cached instanceof Map ? cached : /* @__PURE__ */ new Map();
|
|
3433
3462
|
}
|
|
3434
3463
|
//#endregion
|
|
3464
|
+
//#region src/plugins/build/phases/content-images.ts
|
|
3465
|
+
/**
|
|
3466
|
+
* @file build phase — content-images. Copies each article's co-located image directory
|
|
3467
|
+
* (`<contentDir>/<slug>/images/`) to a single shared output dir (`<outDir>/<slug>/images/`) reused by
|
|
3468
|
+
* every locale, matching the absolute `/<slug>/images/...` URLs the content renderer emits. Gated by
|
|
3469
|
+
* `config.images`.
|
|
3470
|
+
*/
|
|
3471
|
+
/** Conventional per-article image subdirectory name (alongside `<slug>/<locale>.md`). */
|
|
3472
|
+
const ARTICLE_IMAGE_DIR = "images";
|
|
3473
|
+
/**
|
|
3474
|
+
* Copy every article's co-located `images/` directory to `<outDir>/<slug>/images/`. No-op when
|
|
3475
|
+
* `config.images` is false or the content directory does not exist.
|
|
3476
|
+
*
|
|
3477
|
+
* @param ctx - Plugin context (provides `config`, `log`, `require`).
|
|
3478
|
+
* @returns The number of directories copied (one per article that has an `images/` dir).
|
|
3479
|
+
* @example
|
|
3480
|
+
* ```ts
|
|
3481
|
+
* const copied = await copyContentImages(ctx);
|
|
3482
|
+
* ```
|
|
3483
|
+
*/
|
|
3484
|
+
async function copyContentImages(ctx) {
|
|
3485
|
+
if (!ctx.config.images) {
|
|
3486
|
+
ctx.log.debug("build:content-images", { skipped: true });
|
|
3487
|
+
return 0;
|
|
3488
|
+
}
|
|
3489
|
+
const contentDir = ctx.require(contentPlugin).contentDir();
|
|
3490
|
+
if (!existsSync(contentDir)) {
|
|
3491
|
+
ctx.log.debug("build:content-images", {
|
|
3492
|
+
skipped: true,
|
|
3493
|
+
reason: "no content dir"
|
|
3494
|
+
});
|
|
3495
|
+
return 0;
|
|
3496
|
+
}
|
|
3497
|
+
const entries = await readdir(contentDir, { withFileTypes: true });
|
|
3498
|
+
let copied = 0;
|
|
3499
|
+
for (const entry of entries) {
|
|
3500
|
+
if (!entry.isDirectory()) continue;
|
|
3501
|
+
const source = path.join(contentDir, entry.name, ARTICLE_IMAGE_DIR);
|
|
3502
|
+
if (!existsSync(source)) continue;
|
|
3503
|
+
await cp(source, path.join(ctx.config.outDir, entry.name, ARTICLE_IMAGE_DIR), { recursive: true });
|
|
3504
|
+
copied += 1;
|
|
3505
|
+
}
|
|
3506
|
+
ctx.log.debug("build:content-images", { copied });
|
|
3507
|
+
return copied;
|
|
3508
|
+
}
|
|
3509
|
+
//#endregion
|
|
3435
3510
|
//#region src/plugins/build/phases/feeds.ts
|
|
3436
3511
|
/**
|
|
3437
3512
|
* @file build phase 4 — feeds. Generates RSS/Atom/JSON from cached content plus
|
|
@@ -4677,6 +4752,7 @@ const PHASE_ORDER = [
|
|
|
4677
4752
|
"content",
|
|
4678
4753
|
"images",
|
|
4679
4754
|
"pages",
|
|
4755
|
+
"content-images",
|
|
4680
4756
|
"feeds",
|
|
4681
4757
|
"sitemap",
|
|
4682
4758
|
"og-images",
|
|
@@ -4783,6 +4859,7 @@ async function runPipeline(ctx, options) {
|
|
|
4783
4859
|
await withPhase(phaseContext, "bundle", () => bundle(phaseContext));
|
|
4784
4860
|
await Promise.all([withPhase(phaseContext, "content", () => loadContent(phaseContext)), withPhase(phaseContext, "images", () => processImages(phaseContext))]);
|
|
4785
4861
|
const pages = await withPhase(phaseContext, "pages", () => renderPages(phaseContext));
|
|
4862
|
+
await withPhase(phaseContext, "content-images", () => copyContentImages(phaseContext));
|
|
4786
4863
|
await runOutputs(phaseContext);
|
|
4787
4864
|
await withPhase(phaseContext, "root-index", async () => {
|
|
4788
4865
|
if (pages.rootHtml !== null) await writeFile(path.join(outDir, "index.html"), pages.rootHtml, "utf8");
|
package/package.json
CHANGED