basecampjs 0.0.11 → 0.0.13

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.
Files changed (2) hide show
  1. package/index.js +62 -20
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -23,10 +23,11 @@ const md = new MarkdownIt({ html: true, linkify: true, typographer: true });
23
23
 
24
24
  const defaultConfig = {
25
25
  siteName: "Campsite",
26
+ siteUrl: "https://example.com",
26
27
  srcDir: "src",
27
28
  outDir: "dist",
28
29
  templateEngine: "nunjucks",
29
- markdown: true,
30
+ frontmatter: true,
30
31
  minifyCSS: false,
31
32
  minifyHTML: false,
32
33
  cacheBustAssets: false,
@@ -38,6 +39,7 @@ const defaultConfig = {
38
39
  inputFormats: [".jpg", ".jpeg", ".png"],
39
40
  preserveOriginal: true
40
41
  },
42
+ port: 4173,
41
43
  integrations: { nunjucks: true, liquid: false, mustache: false, vue: false, alpine: false }
42
44
  };
43
45
 
@@ -388,6 +390,26 @@ function createLiquidEnv(layoutsDir, pagesDir, srcDir, partialsDir) {
388
390
  });
389
391
  }
390
392
 
393
+ async function loadMustachePartials(partialsDir) {
394
+ const partials = {};
395
+ if (!existsSync(partialsDir)) return partials;
396
+
397
+ try {
398
+ const files = await walkFiles(partialsDir);
399
+ await Promise.all(files.map(async (file) => {
400
+ if (extname(file).toLowerCase() === ".mustache") {
401
+ const content = await readFile(file, "utf8");
402
+ const partialName = basename(file, ".mustache");
403
+ partials[partialName] = content;
404
+ }
405
+ }));
406
+ } catch (err) {
407
+ console.error(kolor.yellow(`Warning: Failed to load Mustache partials: ${err.message}`));
408
+ }
409
+
410
+ return partials;
411
+ }
412
+
391
413
  function toUrlPath(outRel) {
392
414
  const normalized = outRel.replace(/\\/g, "/");
393
415
  let path = `/${normalized}`;
@@ -416,7 +438,7 @@ function shouldRenderMarkdown(frontmatter, config, defaultValue) {
416
438
  return defaultValue;
417
439
  }
418
440
 
419
- async function renderWithLayout(layoutName, html, ctx, env, liquidEnv, layoutsDir) {
441
+ async function renderWithLayout(layoutName, html, ctx, env, liquidEnv, layoutsDir, partials = {}) {
420
442
  if (!layoutName) return html;
421
443
  const ext = extname(layoutName).toLowerCase();
422
444
  const layoutCtx = {
@@ -438,7 +460,7 @@ async function renderWithLayout(layoutName, html, ctx, env, liquidEnv, layoutsDi
438
460
  const layoutPath = join(layoutsDir, layoutName);
439
461
  if (existsSync(layoutPath)) {
440
462
  const layoutTemplate = await readFile(layoutPath, "utf8");
441
- return Mustache.render(layoutTemplate, layoutCtx);
463
+ return Mustache.render(layoutTemplate, layoutCtx, partials);
442
464
  }
443
465
  }
444
466
 
@@ -446,20 +468,25 @@ async function renderWithLayout(layoutName, html, ctx, env, liquidEnv, layoutsDi
446
468
  return html;
447
469
  }
448
470
 
449
- async function renderPage(filePath, { pagesDir, layoutsDir, outDir, env, liquidEnv, config, data }) {
471
+ async function renderPage(filePath, { pagesDir, layoutsDir, outDir, env, liquidEnv, config, data, partialsDir }) {
450
472
  const rel = relative(pagesDir, filePath);
451
473
  const ext = extname(filePath).toLowerCase();
452
474
  const outRel = rel.replace(/\.liquid(\.html)?$/i, ".html").replace(ext, ".html");
453
475
  const outPath = join(outDir, outRel);
454
476
  const path = toUrlPath(outRel);
455
477
  await ensureDir(dirname(outPath));
478
+
479
+ // Load Mustache partials if needed
480
+ const partials = (ext === ".mustache" || (await readFile(filePath, "utf8")).match(/layout:.*\.mustache/))
481
+ ? await loadMustachePartials(partialsDir)
482
+ : {};
456
483
 
457
484
  if (ext === ".md") {
458
485
  const raw = await readFile(filePath, "utf8");
459
486
  const parsed = matter(raw);
460
487
  const html = md.render(parsed.content);
461
488
  const ctx = pageContext(parsed.data, html, config, rel, data, path);
462
- const rendered = await renderWithLayout(parsed.data.layout, html, ctx, env, liquidEnv, layoutsDir);
489
+ const rendered = await renderWithLayout(parsed.data.layout, html, ctx, env, liquidEnv, layoutsDir, partials);
463
490
  await writeFile(outPath, rendered, "utf8");
464
491
  return;
465
492
  }
@@ -473,7 +500,7 @@ async function renderPage(filePath, { pagesDir, layoutsDir, outDir, env, liquidE
473
500
  if (shouldRenderMarkdown(parsed.data, config, false)) {
474
501
  pageHtml = md.render(pageHtml);
475
502
  }
476
- const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir);
503
+ const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir, partials);
477
504
  await writeFile(outPath, rendered, "utf8");
478
505
  return;
479
506
  }
@@ -486,7 +513,7 @@ async function renderPage(filePath, { pagesDir, layoutsDir, outDir, env, liquidE
486
513
  if (shouldRenderMarkdown(parsed.data, config, false)) {
487
514
  pageHtml = md.render(pageHtml);
488
515
  }
489
- const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir);
516
+ const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir, partials);
490
517
  await writeFile(outPath, rendered, "utf8");
491
518
  return;
492
519
  }
@@ -495,11 +522,11 @@ async function renderPage(filePath, { pagesDir, layoutsDir, outDir, env, liquidE
495
522
  const raw = await readFile(filePath, "utf8");
496
523
  const parsed = matter(raw);
497
524
  const ctx = pageContext(parsed.data, parsed.content, config, rel, data, path);
498
- let pageHtml = Mustache.render(parsed.content, ctx);
525
+ let pageHtml = Mustache.render(parsed.content, ctx, partials);
499
526
  if (shouldRenderMarkdown(parsed.data, config, false)) {
500
527
  pageHtml = md.render(pageHtml);
501
528
  }
502
- const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir);
529
+ const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir, partials);
503
530
  await writeFile(outPath, rendered, "utf8");
504
531
  return;
505
532
  }
@@ -512,7 +539,7 @@ async function renderPage(filePath, { pagesDir, layoutsDir, outDir, env, liquidE
512
539
  if (shouldRenderMarkdown(parsed.data, config, false)) {
513
540
  pageHtml = md.render(pageHtml);
514
541
  }
515
- const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir);
542
+ const rendered = await renderWithLayout(parsed.data.layout, pageHtml, ctx, env, liquidEnv, layoutsDir, partials);
516
543
  await writeFile(outPath, rendered, "utf8");
517
544
  return;
518
545
  }
@@ -602,7 +629,7 @@ async function cacheBustAssets(outDir) {
602
629
  return assetMap;
603
630
  }
604
631
 
605
- async function build(cwdArg = cwd) {
632
+ async function build(cwdArg = cwd, options = {}) {
606
633
  const config = await loadConfig(cwdArg);
607
634
  const srcDir = resolve(cwdArg, config.srcDir || "src");
608
635
  const pagesDir = join(srcDir, "pages");
@@ -635,7 +662,9 @@ async function build(cwdArg = cwd) {
635
662
  await cleanDir(outDir);
636
663
  await copyPublic(publicDir, outDir, config.excludeFiles);
637
664
 
638
- if (config.compressPhotos) {
665
+ // Only compress photos during production builds, not during dev mode
666
+ const shouldCompressPhotos = options.skipImageCompression !== true && config.compressPhotos;
667
+ if (shouldCompressPhotos) {
639
668
  await processImages(outDir, config);
640
669
  }
641
670
 
@@ -645,19 +674,22 @@ async function build(cwdArg = cwd) {
645
674
  return;
646
675
  }
647
676
 
648
- await Promise.all(files.map((file) => renderPage(file, { pagesDir, layoutsDir, outDir, env, liquidEnv, config, data })));
677
+ await Promise.all(files.map((file) => renderPage(file, { pagesDir, layoutsDir, outDir, env, liquidEnv, config, data, partialsDir })));
649
678
 
650
- if (config.minifyCSS) {
679
+ // Skip minification and cache busting in dev mode for faster rebuilds
680
+ const isDevMode = options.devMode === true;
681
+
682
+ if (!isDevMode && config.minifyCSS) {
651
683
  await minifyCSSFiles(outDir);
652
684
  console.log(kolor.green("CSS minified"));
653
685
  }
654
686
 
655
- if (config.minifyHTML) {
687
+ if (!isDevMode && config.minifyHTML) {
656
688
  await minifyHTMLFiles(outDir, config);
657
689
  console.log(kolor.green("HTML minified"));
658
690
  }
659
691
 
660
- if (config.cacheBustAssets) {
692
+ if (!isDevMode && config.cacheBustAssets) {
661
693
  console.log(kolor.cyan("Cache-busting assets..."));
662
694
  const assetMap = await cacheBustAssets(outDir);
663
695
  const assetCount = Object.keys(assetMap).length;
@@ -668,6 +700,14 @@ async function build(cwdArg = cwd) {
668
700
  }
669
701
  }
670
702
 
703
+ // Generate robots.txt dynamically if it doesn't exist in public directory
704
+ const publicRobotsTxt = join(publicDir, "robots.txt");
705
+ const distRobotsTxt = join(outDir, "robots.txt");
706
+ if (!existsSync(publicRobotsTxt) && !existsSync(distRobotsTxt)) {
707
+ const robotsTxt = `User-agent: *\nAllow: /\n\nSitemap: ${config.siteUrl}/sitemap.xml\n`;
708
+ await writeFile(distRobotsTxt, robotsTxt, "utf8");
709
+ }
710
+
671
711
  console.log(kolor.green(`Built ${files.length} page(s) → ${relative(cwdArg, outDir)}`));
672
712
  }
673
713
 
@@ -740,7 +780,8 @@ async function dev(cwdArg = cwd) {
740
780
  }
741
781
  building = true;
742
782
  try {
743
- await build(cwdArg);
783
+ // Skip image compression, minification, and cache busting during dev mode for faster rebuilds
784
+ await build(cwdArg, { skipImageCompression: true, devMode: true });
744
785
  } catch (err) {
745
786
  console.error(kolor.red(`Build failed: ${err.message}`));
746
787
  } finally {
@@ -767,7 +808,7 @@ async function dev(cwdArg = cwd) {
767
808
  runBuild();
768
809
  });
769
810
 
770
- serve(outDir);
811
+ serve(outDir, config.port || 4173);
771
812
  }
772
813
 
773
814
  function slugify(text) {
@@ -1413,7 +1454,7 @@ async function preview() {
1413
1454
  const config = await loadConfig(cwd);
1414
1455
  const outDir = resolve(cwd, config.outDir || "dist");
1415
1456
  console.log(kolor.cyan(kolor.bold("🔥 Starting preview server...\n")));
1416
- serve(outDir);
1457
+ serve(outDir, config.port || 4173);
1417
1458
  }
1418
1459
 
1419
1460
  async function main() {
@@ -1459,13 +1500,14 @@ async function main() {
1459
1500
  if (!existsSync(outDir)) {
1460
1501
  await build();
1461
1502
  }
1462
- serve(outDir);
1503
+ serve(outDir, config.port || 4173);
1463
1504
  break;
1464
1505
  }
1465
1506
  case "preview":
1466
1507
  await preview();
1467
1508
  break;
1468
1509
  case "clean":
1510
+ case "cleanup":
1469
1511
  await clean();
1470
1512
  break;
1471
1513
  case "check":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basecampjs",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "description": "BasecampJS engine for CampsiteJS static site generator.",
6
6
  "bin": {