1import * as markodown from "./mod";
2import * as marko from "@marko/compiler";
3import type * as esbuild from "esbuild";
4import * as fs from "node:fs/promises";
5import * as path from "node:path";
6
7export type EsbuildPluginOptions =
8 & Omit<markodown.TransformOptions, "source" | "format">
9 & {
10 marko?: typeof marko;
11 markoOptions?: marko.Config;
12 };
13
14/**
15 * esbuild or Bun bundling plugin. Because Bun markets itself as compatible with
16 * plugins made for esbuild, this plugin is designed to work around differences
17 * in Bun's implementation to allow it to work. Bugs reported for subtle behavior
18 * mistakes that are not present when using `esbuild` will not be fixed.
19 */
20export default function esbuildPlugin(
21 options: EsbuildPluginOptions = {},
22): esbuild.Plugin {
23 const { compile } = options.marko ?? marko;
24 return {
25 name: "@clo/markodown/esbuild.ts",
26 async setup(b) {
27 b.onLoad(
28 { filter: /\.(?:mdo|mkd)$/ },
29 // NOTE: Bun doesn't pass `with`, so a default is needed
30 async ({ path: file, suffix, with: { type } = {} }) => {
31 if (type || suffix) {
32 // NOTE: Bun does not appear to support fallbacks with `null`
33 /* v8 ignore next -- @preserve */
34 // @ts-expect-error (third-party types not installed)
35 if (typeof Bun !== "undefined" && !b.esbuild?.build) {
36 return {
37 contents: (await fs.readFile(file)),
38 loader: path.extname(file).slice(1) as "tsx" | "ts",
39 };
40 }
41
42 return null;
43 }
44 const result = markodown.transform({
45 ...options,
46 source: await fs.readFile(file, "utf-8"),
47 selfImport: "./" + path.basename(file),
48 });
49 if (!result.success) {
50 return {
51 errors: result.errors.map((err) => ({
52 text: err.message,
53 location: {
54 line: err.line,
55 column: err.column - 1,
56 },
57 notes: err.labels.map((note) => ({
58 text: note.message,
59 location: {
60 line: note.line,
61 column: note.column - 1,
62 length: note.width,
63 },
64 })),
65 })),
66 watchFiles: [file],
67 };
68 }
69
70 // TODO: source maps!
71 // TODO: parse marko errors
72 const { code, meta } = await compile(
73 result.text,
74 file + ".marko",
75 options?.markoOptions ?? {},
76 );
77
78 return {
79 loader: "js",
80 contents: code,
81 watchFiles: meta.watchFiles,
82 resolveDir: path.dirname(file),
83 };
84 },
85 );
86 },
87 };
88}