defuss-ssg 0.0.1

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.
@@ -0,0 +1,374 @@
1
+ 'use strict';
2
+
3
+ var chokidar = require('chokidar');
4
+ var express = require('express');
5
+ var serveStatic = require('serve-static');
6
+ var node_path = require('node:path');
7
+ var node_fs = require('node:fs');
8
+ var esbuild = require('esbuild');
9
+ var remarkFrontmatter = require('remark-frontmatter');
10
+ var rehypeKatex = require('rehype-katex');
11
+ var remarkMath = require('remark-math');
12
+ var remarkMdxFrontmatter = require('remark-mdx-frontmatter');
13
+ var tailwind = require('./tailwind-C4AuHybm.cjs');
14
+ var mdx = require('@mdx-js/esbuild');
15
+ var glob = require('fast-glob');
16
+ var server = require('defuss/server');
17
+ var promises = require('node:fs/promises');
18
+ var node_url = require('node:url');
19
+
20
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
21
+ const remarkPlugins = [
22
+ // Parse both YAML and TOML (or omit options to default to YAML)
23
+ [remarkFrontmatter, ["yaml", "toml"]],
24
+ // Export each key as an ESM binding: export const title = "…"
25
+ [remarkMdxFrontmatter, { name: "meta" }],
26
+ // Convert $…$ and $$…$$ into math nodes for KaTeX
27
+ remarkMath
28
+ ];
29
+ const rehypePlugins = [
30
+ //rehypeMdxTitle,
31
+ rehypeKatex
32
+ ];
33
+
34
+ const readConfig = async (projectDir, debug) => {
35
+ const configPath = node_path.join(projectDir, "config.ts");
36
+ let config = {};
37
+ if (node_fs.existsSync(configPath)) {
38
+ if (debug) {
39
+ console.log(`Using config from ${configPath}`);
40
+ }
41
+ const result = await esbuild.build({
42
+ entryPoints: [configPath],
43
+ format: "esm",
44
+ bundle: true,
45
+ target: ["esnext"],
46
+ write: false
47
+ });
48
+ const code = result.outputFiles[0].text;
49
+ const encoded = Buffer.from(code).toString("base64");
50
+ const dataUrl = `data:text/javascript;base64,${encoded}`;
51
+ const module = await import(dataUrl);
52
+ config = module.default;
53
+ }
54
+ config.pages = config.pages || configDefaults.pages;
55
+ config.output = config.output || configDefaults.output;
56
+ config.components = config.components || configDefaults.components;
57
+ config.assets = config.assets || configDefaults.assets;
58
+ config.plugins = config.plugins || configDefaults.plugins;
59
+ config.tmp = config.tmp || configDefaults.tmp;
60
+ config.remarkPlugins = config.remarkPlugins || configDefaults.remarkPlugins;
61
+ config.rehypePlugins = config.rehypePlugins || configDefaults.rehypePlugins;
62
+ return config;
63
+ };
64
+ const configDefaults = {
65
+ pages: "pages",
66
+ output: "dist",
67
+ components: "components",
68
+ assets: "assets",
69
+ tmp: ".ssg-temp",
70
+ plugins: [tailwind.tailwindPlugin],
71
+ remarkPlugins,
72
+ rehypePlugins
73
+ };
74
+
75
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('serve-BaOtT5p7.cjs', document.baseURI).href)));
76
+ const __dirname$1 = node_path.dirname(__filename$1);
77
+ const build = async ({
78
+ projectDir,
79
+ debug = false,
80
+ mode = "build"
81
+ }) => {
82
+ const startTime = performance.now();
83
+ const config = await readConfig(projectDir, debug);
84
+ if (debug) {
85
+ console.log("PRE config", config);
86
+ }
87
+ if (debug) {
88
+ console.log("Using config:", config);
89
+ }
90
+ const inputPagesDir = node_path.join(projectDir, config.pages);
91
+ const inputComponentsDir = node_path.join(projectDir, config.components);
92
+ const inputAssetsDir = node_path.join(projectDir, config.assets);
93
+ const tmpPagesDir = node_path.join(config.tmp, config.pages);
94
+ const tmpComponentsDir = node_path.join(config.tmp, config.components);
95
+ const outputProjectDir = node_path.join(projectDir, config.output);
96
+ const outputPagesDir = node_path.join(projectDir, config.output, config.pages);
97
+ const outputComponentsDir = node_path.join(
98
+ projectDir,
99
+ config.output,
100
+ config.components
101
+ );
102
+ const outputAssetsDir = node_path.join(projectDir, config.output, config.assets);
103
+ if (debug) {
104
+ console.log("Input pages dir:", inputPagesDir);
105
+ console.log("Input components dir:", inputComponentsDir);
106
+ console.log("Input assets dir:", inputAssetsDir);
107
+ console.log("Temp pages dir:", tmpPagesDir);
108
+ console.log("Temp components dir:", tmpComponentsDir);
109
+ console.log("Output pages dir:", outputPagesDir);
110
+ console.log("Output components dir:", outputComponentsDir);
111
+ console.log("Output assets dir:", outputAssetsDir);
112
+ }
113
+ if (!node_fs.existsSync(inputPagesDir)) {
114
+ throw new Error(`Input pages directory does not exist: ${inputPagesDir}`);
115
+ } else if (debug) {
116
+ console.log(`Input pages directory exists: ${inputPagesDir}`);
117
+ }
118
+ if (!node_fs.existsSync(inputComponentsDir)) {
119
+ console.warn(
120
+ `There is no components directory: ${inputComponentsDir}. You may not be able to use any custom components.`
121
+ );
122
+ }
123
+ if (!node_fs.existsSync(inputAssetsDir)) {
124
+ console.warn(
125
+ `There is no assets directory: ${inputAssetsDir}. You may not be able to serve any custom assets.`
126
+ );
127
+ }
128
+ for (const plugin of config.plugins || []) {
129
+ if (plugin.phase === "pre" && (plugin.mode === mode || plugin.mode === "both")) {
130
+ if (debug) {
131
+ console.log(`Running pre-plugin: ${plugin.name}`);
132
+ }
133
+ await plugin.fn(projectDir, config);
134
+ }
135
+ }
136
+ if (node_fs.existsSync(config.tmp)) {
137
+ if (debug) {
138
+ console.log(`Removing existing temp folder: ${config.tmp}`);
139
+ }
140
+ node_fs.rmdirSync(config.tmp, { recursive: true });
141
+ }
142
+ await promises.cp(projectDir, config.tmp, {
143
+ recursive: true,
144
+ filter: (src) => {
145
+ const relative = src.replace(node_path.join(projectDir, ""), "");
146
+ if (relative.startsWith(node_path.join("assets", "")) || relative.startsWith(node_path.join("node_modules", ""))) {
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ });
152
+ await promises.cp(
153
+ // because of this the packaging of defuss-ssg must be done with "files" including the "components" folder
154
+ // in a built situation, __dirname is the dist folder of defuss-ssg
155
+ node_path.resolve(node_path.join(__dirname$1, "components", "index.mjs")),
156
+ // dist folder
157
+ node_path.join(tmpComponentsDir, "hydrate.tsx")
158
+ // a valid JS is always a valid TS file
159
+ );
160
+ await promises.cp(
161
+ // because of this the packaging of defuss-ssg must be done with "files" including the "runtime" file
162
+ // in a built situation, __dirname is the dist folder of defuss-ssg
163
+ node_path.resolve(node_path.join(__dirname$1, "runtime.mjs")),
164
+ // dist folder
165
+ node_path.join(tmpComponentsDir, "runtime.ts")
166
+ // a valid JS is always a valid TS file
167
+ );
168
+ await esbuild.build({
169
+ entryPoints: [node_path.join(tmpPagesDir, "**/*.mdx")],
170
+ format: "esm",
171
+ bundle: true,
172
+ sourcemap: true,
173
+ target: ["esnext"],
174
+ outdir: tmpPagesDir,
175
+ plugins: [
176
+ mdx({
177
+ // using the defuss jsxImportSource so that the output code contains JSX runtime calls
178
+ // and can be rendered to HTML here on the server (in Node.js).
179
+ jsxImportSource: "defuss",
180
+ // We also use any remark/rehype plugins specified in the config file.
181
+ remarkPlugins: config.remarkPlugins,
182
+ rehypePlugins: config.rehypePlugins
183
+ })
184
+ ]
185
+ });
186
+ await esbuild.build({
187
+ entryPoints: [
188
+ node_path.join(tmpComponentsDir, "**/*.tsx"),
189
+ node_path.join(tmpComponentsDir, "**/*.ts")
190
+ ],
191
+ format: "esm",
192
+ bundle: true,
193
+ // making sure we can do code splitting for shared dependencies (e.g. defuss lib)
194
+ splitting: true,
195
+ target: ["esnext"],
196
+ outdir: tmpComponentsDir
197
+ });
198
+ const outputFiles = await glob.async(node_path.join(tmpPagesDir, "**/*.js"));
199
+ if (!node_fs.existsSync(outputProjectDir)) {
200
+ node_fs.mkdirSync(outputProjectDir, { recursive: true });
201
+ }
202
+ for (const outputFile of outputFiles) {
203
+ const outputHtmlFilePath = outputFile.replace(".js", ".html");
204
+ const relativeOutputHtmlFilePath = outputHtmlFilePath.replace(
205
+ `${tmpPagesDir}${node_path.sep}`,
206
+ ""
207
+ );
208
+ if (debug) {
209
+ console.log("Processing output file (JS):", outputFile);
210
+ console.log("Output HTML file path:", outputHtmlFilePath);
211
+ console.log("Relative output HTML path:", relativeOutputHtmlFilePath);
212
+ }
213
+ const code = await promises.readFile(outputFile, "utf-8");
214
+ const encoded = Buffer.from(code).toString("base64");
215
+ const dataUrl = `data:text/javascript;base64,${encoded}`;
216
+ const exports = await import(dataUrl);
217
+ if (debug) {
218
+ console.log("exports", exports);
219
+ }
220
+ let vdom = exports.default(exports);
221
+ for (const plugin of config.plugins || []) {
222
+ if (plugin.phase === "page-vdom" && (plugin.mode === mode || plugin.mode === "both")) {
223
+ if (debug) {
224
+ console.log(`Running page-vdom plugin: ${plugin.name}`);
225
+ }
226
+ vdom = await plugin.fn(
227
+ vdom,
228
+ relativeOutputHtmlFilePath,
229
+ projectDir,
230
+ config
231
+ );
232
+ }
233
+ }
234
+ const browserGlobals = server.getBrowserGlobals();
235
+ const document = server.getDocument(false, browserGlobals);
236
+ browserGlobals.document = document;
237
+ let el = server.renderSync(vdom, document.documentElement, {
238
+ browserGlobals
239
+ });
240
+ for (const plugin of config.plugins || []) {
241
+ if (plugin.phase === "page-dom" && (plugin.mode === mode || plugin.mode === "both")) {
242
+ if (debug) {
243
+ console.log(`Running page-dom plugin: ${plugin.name}`);
244
+ }
245
+ el = await plugin.fn(
246
+ el,
247
+ relativeOutputHtmlFilePath,
248
+ projectDir,
249
+ config
250
+ );
251
+ }
252
+ }
253
+ let html = server.renderToString(el);
254
+ for (const plugin of config.plugins || []) {
255
+ if (plugin.phase === "page-html" && (plugin.mode === mode || plugin.mode === "both")) {
256
+ if (debug) {
257
+ console.log(`Running page-html plugin: ${plugin.name}`);
258
+ }
259
+ html = await plugin.fn(
260
+ html,
261
+ relativeOutputHtmlFilePath,
262
+ projectDir,
263
+ config
264
+ );
265
+ }
266
+ }
267
+ if (debug) {
268
+ console.log("Writing HTML file", outputHtmlFilePath);
269
+ }
270
+ if (debug) {
271
+ console.log("Relative HTML path:", relativeOutputHtmlFilePath);
272
+ }
273
+ const finalOutputFile = node_path.join(
274
+ projectDir,
275
+ config.output,
276
+ relativeOutputHtmlFilePath
277
+ );
278
+ if (debug) {
279
+ console.log("Full HTML output path:", finalOutputFile);
280
+ }
281
+ const finalOutputDir = node_path.dirname(finalOutputFile);
282
+ if (!node_fs.existsSync(finalOutputDir)) {
283
+ node_fs.mkdirSync(finalOutputDir, { recursive: true });
284
+ }
285
+ await promises.writeFile(finalOutputFile, html);
286
+ }
287
+ await promises.cp(tmpComponentsDir, outputComponentsDir, { recursive: true });
288
+ await promises.cp(inputAssetsDir, outputAssetsDir, { recursive: true });
289
+ for (const plugin of config.plugins || []) {
290
+ if (plugin.phase === "post" && (plugin.mode === mode || plugin.mode === "both")) {
291
+ if (debug) {
292
+ console.log(`Running post-plugin: ${plugin.name}`);
293
+ }
294
+ await plugin.fn(projectDir, config);
295
+ }
296
+ }
297
+ if (!debug) {
298
+ node_fs.rmdirSync(config.tmp, { recursive: true });
299
+ }
300
+ const endTime = performance.now();
301
+ const totalTime = (endTime - startTime) / 1e3;
302
+ console.log(`Build completed in ${totalTime.toFixed(2)} seconds.`);
303
+ };
304
+
305
+ const serve = async ({ projectDir, debug = false }) => {
306
+ const config = await readConfig(projectDir, debug);
307
+ const outputDir = node_path.join(projectDir, config.output);
308
+ const pagesDir = node_path.join(projectDir, config.pages);
309
+ const componentsDir = node_path.join(projectDir, config.components);
310
+ const assetsDir = node_path.join(projectDir, config.assets);
311
+ await build({ projectDir, debug, mode: "serve" });
312
+ const app = express();
313
+ const port = 3e3;
314
+ app.use(serveStatic(outputDir));
315
+ app.listen(port, () => {
316
+ console.log(`Server running at http://localhost:${port}`);
317
+ });
318
+ let isBuilding = false;
319
+ let pendingBuild = false;
320
+ const triggerBuild = async () => {
321
+ if (isBuilding) {
322
+ pendingBuild = true;
323
+ if (debug) {
324
+ console.log("Build scheduled after current one completes");
325
+ }
326
+ return;
327
+ }
328
+ isBuilding = true;
329
+ try {
330
+ await build({ projectDir, debug, mode: "serve" });
331
+ } finally {
332
+ isBuilding = false;
333
+ if (pendingBuild) {
334
+ pendingBuild = false;
335
+ if (debug) {
336
+ console.log("Running pending build");
337
+ }
338
+ await triggerBuild();
339
+ }
340
+ }
341
+ };
342
+ const watcher = chokidar.watch([pagesDir, componentsDir, assetsDir], {
343
+ ignored: /(^|[\/\\])\../,
344
+ // Ignore dotfiles
345
+ persistent: true,
346
+ ignoreInitial: true
347
+ // Ignore initial add events
348
+ });
349
+ watcher.on("change", async (path) => {
350
+ if (debug) {
351
+ console.log(`File changed: ${path}`);
352
+ }
353
+ await triggerBuild();
354
+ });
355
+ watcher.on("add", async (path) => {
356
+ if (debug) {
357
+ console.log(`File added: ${path}`);
358
+ }
359
+ await triggerBuild();
360
+ });
361
+ watcher.on("unlink", async (path) => {
362
+ if (debug) {
363
+ console.log(`File removed: ${path}`);
364
+ }
365
+ await triggerBuild();
366
+ });
367
+ };
368
+
369
+ exports.build = build;
370
+ exports.configDefaults = configDefaults;
371
+ exports.readConfig = readConfig;
372
+ exports.rehypePlugins = rehypePlugins;
373
+ exports.remarkPlugins = remarkPlugins;
374
+ exports.serve = serve;