Skip to content

Commit

Permalink
Merge pull request #407 from moonbitlang/zhiyuan/esbuild
Browse files Browse the repository at this point in the history
Zhiyuan/esbuild
  • Loading branch information
bzy-debug authored Jan 8, 2025
2 parents 120a74b + b0f2c67 commit 5624669
Show file tree
Hide file tree
Showing 17 changed files with 806 additions and 2,506 deletions.
11 changes: 3 additions & 8 deletions moonbit-tour/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,22 @@ pnpm build
pnpm preview
```

open <http://localhost:4173> to view the tour.
open <http://localhost:3000> to view the tour.

## How to add new tour

### Add new lesson

1. Create a new folder under the chapter folder following the naming convention `lesson<n>_<lesson-name>` (count start from 1).
1. Write the lesson content in `index.md` and lesson code in `index.mbt` under the created folder.

To see the render result while writing lesson on the fly, follow the instruction below:

1. Setup development environment.

```sh
pnpm install
pnpm dev
```

1. Write the lesson content in `tour/index.md` and lesson code in `tour/index.mbt`. You can see the render result in <http://localhost:5173>
1. Create a new folder under the chapter folder following the naming convention `lesson<n>_<lesson-name>` (count start from 1).

1. After you finish writing the lesson, copy `tour/index.md` and `tour/index.mbt` to the corresponding lesson folder.
1. Write the lesson content in `index.md` and lesson code in `index.mbt` under the created folder.

### Add new chapter

Expand Down
95 changes: 95 additions & 0 deletions moonbit-tour/build/build.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as chokidar from "chokidar";
import * as esbuild from "esbuild";
import { tailwindPlugin } from "esbuild-plugin-tailwindcss";
import * as fs from "fs/promises";
import * as path from "path";
import * as page from "./page";

const isDev = process.env["DEV"] === "true";
const isWatch = process.argv.includes("--watch");
const isBuild = process.argv.includes("--build");

const plugin = (): esbuild.Plugin => {
return {
name: "my-plugin",
setup(build) {
build.onStart(async () => {
console.log("start build");
build.initialOptions.outdir &&
(await fs.rm(build.initialOptions.outdir, {
recursive: true,
force: true,
}));
});
build.onEnd(async () => {
console.log("end build");

const names = ["lsp-server.js", "moonc-worker.js", "onig.wasm"];
await Promise.all(
names.map((name) => {
fs.copyFile(
`./node_modules/@moonbit/moonpad-monaco/dist/${name}`,
`./dist/${name}`,
);
}),
);

const template = await fs.readFile("index.html", "utf8");

const pages = await page.collectTourPage();
await Promise.all(
pages.map(async (p) => {
const content = page.render(template, p);
await fs.mkdir(`./dist/${path.dirname(p.path)}`, {
recursive: true,
});
await fs.writeFile(`./dist/${p.path}`, content, {
encoding: "utf8",
});
}),
);
});
},
};
};

const ctx = await esbuild.context({
entryPoints: [
"./src/main.ts",
"./node_modules/monaco-editor-core/esm/vs/editor/editor.worker.js",
],
bundle: true,
minify: !isDev,
format: "esm",
outdir: "./dist",
entryNames: "[name]",
loader: {
".ttf": "file",
".woff2": "file",
},
logLevel: "info",
plugins: [tailwindPlugin(), plugin()],
});

await ctx.rebuild();

if (isBuild) {
process.exit(0);
}

if (isDev) {
await ctx.serve({
servedir: "dist",
});
}

if (isWatch) {
chokidar
.watch(["index.html", "src", "tour"], {
ignoreInitial: true,
})
.on("all", async (e, path) => {
console.log(`[watch] ${e} ${path}`);
await ctx.rebuild();
});
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import fs from "fs/promises";
import path from "path";
import * as vite from "vite";
import { BASE } from "./const";
import { generateTOC, scanTour, slug } from "./scan-tour";

const head = `<link rel="icon" href="${BASE}/favicon.ico" />
const head = `<link rel="icon" href="/favicon.ico" />
<title>%TITLE%</title>
<script type="module" crossorigin src="${BASE}/assets/index.js"></script>
<link rel="stylesheet" crossorigin href="${BASE}/assets/index.css">`;
<script type="module" crossorigin src="/assets/index.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index.css">`;

const generatePlugin = (): vite.Plugin => {
return {
Expand All @@ -27,18 +26,18 @@ const generatePlugin = (): vite.Plugin => {
"",
);
if (i === 0) {
res = res.replace("%BACK%", `<a href="${BASE}/index.html">Back</a>`);
res = res.replace("%BACK%", `<a href="/index.html">Back</a>`);
}
if (i - 1 >= 0) {
res = res.replace(
"%BACK%",
`<a href="${BASE}/${slug(lessons[i - 1])}/index.html">Back</a>`,
`<a href="/${slug(lessons[i - 1])}/index.html">Back</a>`,
);
}
if (i + 1 < lessons.length) {
res = res.replace(
"%NEXT%",
`<a href="${BASE}/${slug(lessons[i + 1])}/index.html">Next</a>`,
`<a href="/${slug(lessons[i + 1])}/index.html">Next</a>`,
);
}
if (i === lessons.length - 1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as fs from "fs/promises";
import * as vite from "vite";
import { BASE } from "./const";
import * as remark from "./remark";
import { scanTour, slug } from "./scan-tour";
import * as shiki from "./shiki";
Expand All @@ -17,15 +16,15 @@ const indexPlugin = (): vite.Plugin => {
return html
.replace(
"%HEAD%",
`<link rel="icon" href="${BASE}/favicon.ico" />
`<link rel="icon" href="/favicon.ico" />
<title>moonbit tour</title>`,
)
.replace("%MARKDOWN%", mdHtml)
.replace("%CODE%", mbtHtml)
.replace("%BACK%", `<span class="text-zinc-500">Back</span>`)
.replace(
"%NEXT%",
`<a href="${BASE}/${slug(lessons[0])}/index.html">Next</a>`,
`<a href="/${slug(lessons[0])}/index.html">Next</a>`,
);
},
};
Expand Down
68 changes: 68 additions & 0 deletions moonbit-tour/build/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as fs from "fs/promises";
import * as remark from "./remark";
import * as scan from "./scan-tour";
import * as shiki from "./shiki";

type Page = {
title: string;
markdown: string;
code: string;
back: string;
next: string;
path: string;
};

export async function collectTourPage(): Promise<Page[]> {
const pages: Page[] = [];
const chapters = await scan.scanTour();
const lessons = chapters.flatMap((c) => c.lessons);

const indexMd = await fs.readFile("tour/index.md", "utf8");
const indexMbt = await fs.readFile("tour/index.mbt", "utf8");
pages.push({
title: "MoonBit Language Tour",
markdown: indexMd,
code: indexMbt,
back: `<span class="text-zinc-500">Back</span>`,
next: `<a href="/${scan.slug(lessons[0])}/index.html">Next</a>`,
path: "index.html",
});

for (const [i, l] of lessons.entries()) {
const p: Page = {
title: `${l.lesson} - MoonBit Language Tour`,
markdown: l.markdown,
code: l.code,
back:
i === 0
? `<a href="/index.html">Back</a>`
: `<a href="/${scan.slug(lessons[i - 1])}/index.html">Back</a>`,
next:
i === lessons.length - 1
? `<span class="text-zinc-500">Next</span>`
: `<a href="/${scan.slug(lessons[i + 1])}/index.html">Next</a>`,
path: `${scan.slug(l)}/index.html`,
};
pages.push(p);
}

const toc = scan.generateTOC(chapters);
pages.push({
title: `Table of Contents - MoonBit Language Tour`,
markdown: toc.markdown,
code: toc.code,
back: `<span class="text-zinc-500">Back</span>`,
next: `<span class="text-zinc-500">Next</span>`,
path: "table-of-contents/index.html",
});
return pages;
}

export function render(template: string, page: Page): string {
return template
.replace("%TITLE%", page.title)
.replace("%MARKDOWN%", remark.mdToHtml(page.markdown))
.replace("%CODE%", shiki.renderMoonBitCode(page.code))
.replace("%BACK%", page.back)
.replace("%NEXT%", page.next);
}
File renamed without changes.
17 changes: 6 additions & 11 deletions moonbit-tour/dev/scan-tour.ts → moonbit-tour/build/scan-tour.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import fs from "fs/promises";
import path from "path";
import { BASE } from "./const";
import * as remark from "./remark";
import * as shiki from "./shiki";

type Chapter = {
chapter: string;
Expand Down Expand Up @@ -46,8 +43,8 @@ async function scanTour(): Promise<Chapter[]> {
lesson,
lessonIndex: i,
lessonsLength: arr.length,
markdown: remark.mdToHtml(md),
code: shiki.renderMoonBitCode(mbt),
markdown: md,
code: mbt,
};
}),
);
Expand All @@ -66,17 +63,15 @@ function generateTOC(chapters: Chapter[]): { markdown: string; code: string } {
for (const c of chapters) {
lines.push(`## ${c.chapter}`);
for (const l of c.lessons) {
lines.push(`- [${l.lesson}](${BASE}/${slug(l)}/index.html)`);
lines.push(`- [${l.lesson}](/${slug(l)}/index.html)`);
}
}
return {
markdown: remark.mdToHtml(lines.join("\n")),
code: shiki.renderMoonBitCode(`fn main {
markdown: lines.join("\n"),
code: `fn main {
println("hello, world")
}`),
}`,
};
}

generateTOC(await scanTour());

export { generateTOC, scanTour, slug };
File renamed without changes.
3 changes: 0 additions & 3 deletions moonbit-tour/dev/const.ts

This file was deleted.

6 changes: 4 additions & 2 deletions moonbit-tour/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
%HEAD%
<link rel="icon" href="/favicon.ico" />
<title>%TITLE%</title>
<script type="module" crossorigin src="/main.js"></script>
<link rel="stylesheet" crossorigin href="/main.css" />
</head>
<body class="flex flex-col font-sans md:h-screen">
<header class="flex h-8 items-center gap-2 bg-purple-200 px-2 py-6 md:px-6">
Expand Down Expand Up @@ -54,7 +57,6 @@
<pre id="output"></pre>
</div>
</section>
<script type="module" src="/src/main.ts"></script>
</main>
</body>
</html>
17 changes: 10 additions & 7 deletions moonbit-tour/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,34 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"dev": "DEV=true tsx ./build/build.mts --watch",
"build": "tsc && tsx ./build/build.mts --build",
"preview": "serve dist",
"format": "prettier --write ."
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
"@types/node": "^22.10.1",
"@types/node": "^22.10.2",
"autoprefixer": "^10.4.20",
"chokidar": "^4.0.3",
"esbuild": "^0.24.2",
"esbuild-plugin-tailwindcss": "^1.2.1",
"postcss": "^8.4.49",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.9",
"rehype-stringify": "^10.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"serve": "^14.2.4",
"shiki": "^1.24.1",
"tailwindcss": "^3.4.16",
"tsx": "^4.19.2",
"typescript": "~5.6.2",
"unified": "^11.0.5",
"vite": "^6.0.1"
"unified": "^11.0.5"
},
"dependencies": {
"@moonbit/moonpad-monaco": "^0.1.202501031",
"@moonbit/moonpad-monaco": "^0.1.202501070",
"monaco-editor-core": "^0.52.0"
}
}
6 changes: 0 additions & 6 deletions moonbit-tour/postcss.config.js

This file was deleted.

1 change: 1 addition & 0 deletions moonbit-tour/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module "*.css" {}
Loading

0 comments on commit 5624669

Please sign in to comment.