@zvndev/darkmown 2.0.1 → 2.2.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +18 -0
- package/README.md +61 -6
- package/SECURITY.md +4 -4
- package/docs/cli.md +8 -0
- package/docs/spec-alignment.md +26 -6
- package/grammar/wd-directives.gbnf +58 -0
- package/package.json +5 -1
- package/site/_/footer.wd +1 -1
- package/site/_/nav.wd +1 -0
- package/site/pages/app.wd +1 -1
- package/site/pages/blog/zero-js-by-default.md +1 -1
- package/site/pages/docs/index.wd +19 -2
- package/site/pages/index.wd +5 -3
- package/site/pages/playground/index.js +158 -0
- package/site/pages/playground/index.skin +128 -0
- package/site/pages/playground/index.wd +51 -0
- package/site/pages/showcase/index.wd +1 -1
- package/src/builder.js +15 -8
- package/src/catalog.js +586 -0
- package/src/cli.js +8 -0
- package/src/compiler/actions.js +45 -12
- package/src/compiler/collections.js +16 -11
- package/src/compiler/context.js +52 -3
- package/src/compiler/expr-ast.js +296 -0
- package/src/compiler/fetch.js +12 -4
- package/src/compiler/forms.js +78 -24
- package/src/compiler/fs-reader.js +25 -0
- package/src/compiler/includes.js +20 -15
- package/src/compiler/index.js +1 -1
- package/src/compiler/loops.js +26 -8
- package/src/compiler/media.js +21 -7
- package/src/compiler/page.js +68 -15
- package/src/compiler/predicates.js +29 -18
- package/src/compiler/reader.js +60 -0
- package/src/compiler/state.js +81 -32
- package/src/compiler/structure.js +48 -17
- package/src/compiler.js +57 -6
- package/src/grammar.js +131 -0
- package/src/headers.js +15 -13
- package/src/index.js +2 -0
- package/src/runtime.js +77 -45
- package/src/statics.js +6 -7
- package/types/compiler.d.ts +13 -0
- package/types/index.d.ts +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
tokens
|
|
2
|
+
pgink #eef2e6
|
|
3
|
+
pgbg #10160c
|
|
4
|
+
pgpanel #18210f
|
|
5
|
+
pgline #2c3a20
|
|
6
|
+
pgaccent #8fd694
|
|
7
|
+
pgmuted #9db089
|
|
8
|
+
pgradius 10px
|
|
9
|
+
|
|
10
|
+
page
|
|
11
|
+
margin 0
|
|
12
|
+
font system-ui, -apple-system, "Segoe UI", Roboto, sans-serif
|
|
13
|
+
color $pgink
|
|
14
|
+
bg $pgbg
|
|
15
|
+
line-height 1.6
|
|
16
|
+
|
|
17
|
+
main
|
|
18
|
+
max-width 1100px
|
|
19
|
+
margin 0 auto
|
|
20
|
+
padding 2.5rem 1.5rem 4rem
|
|
21
|
+
|
|
22
|
+
.pg-intro h1
|
|
23
|
+
font-size 2.6rem
|
|
24
|
+
line-height 1.05
|
|
25
|
+
margin 0 0 .6rem
|
|
26
|
+
color $pgink
|
|
27
|
+
|
|
28
|
+
.pg-intro p
|
|
29
|
+
color $pgmuted
|
|
30
|
+
max-width 46rem
|
|
31
|
+
|
|
32
|
+
.pg-intro a
|
|
33
|
+
color $pgaccent
|
|
34
|
+
|
|
35
|
+
.pg-picker
|
|
36
|
+
display flex
|
|
37
|
+
flex-wrap wrap
|
|
38
|
+
gap .5rem
|
|
39
|
+
margin 1.4rem 0 1rem
|
|
40
|
+
|
|
41
|
+
.pg-picker button
|
|
42
|
+
font inherit
|
|
43
|
+
font-size .92rem
|
|
44
|
+
font-weight 560
|
|
45
|
+
padding .5rem 1rem
|
|
46
|
+
color $pgink
|
|
47
|
+
bg $pgpanel
|
|
48
|
+
border 1px solid $pgline
|
|
49
|
+
radius 999px
|
|
50
|
+
cursor pointer
|
|
51
|
+
transition background .14s ease, border-color .14s ease, transform .14s ease
|
|
52
|
+
|
|
53
|
+
.pg-picker button:hover
|
|
54
|
+
border-color $pgaccent
|
|
55
|
+
transform translateY(-1px)
|
|
56
|
+
|
|
57
|
+
.pg-picker button:focus-visible
|
|
58
|
+
outline 2px solid $pgaccent
|
|
59
|
+
outline-offset 2px
|
|
60
|
+
|
|
61
|
+
.pg-workbench
|
|
62
|
+
display grid
|
|
63
|
+
grid-template-columns 1fr 1fr
|
|
64
|
+
gap 1rem
|
|
65
|
+
margin-top 1.5rem
|
|
66
|
+
align-items stretch
|
|
67
|
+
|
|
68
|
+
.pg-pane
|
|
69
|
+
display flex
|
|
70
|
+
flex-direction column
|
|
71
|
+
min-width 0
|
|
72
|
+
|
|
73
|
+
.pg-label
|
|
74
|
+
font-size .78rem
|
|
75
|
+
font-weight 600
|
|
76
|
+
letter-spacing .04em
|
|
77
|
+
text-transform uppercase
|
|
78
|
+
color $pgmuted
|
|
79
|
+
margin-bottom .5rem
|
|
80
|
+
|
|
81
|
+
.pg-editor
|
|
82
|
+
width 100%
|
|
83
|
+
min-height 30rem
|
|
84
|
+
flex 1
|
|
85
|
+
resize vertical
|
|
86
|
+
padding 1rem 1.1rem
|
|
87
|
+
color $pgink
|
|
88
|
+
bg $pgpanel
|
|
89
|
+
border 1px solid $pgline
|
|
90
|
+
radius $pgradius
|
|
91
|
+
font ui-monospace, "SF Mono", Menlo, Consolas, monospace
|
|
92
|
+
font-size .9rem
|
|
93
|
+
line-height 1.55
|
|
94
|
+
tab-size 2
|
|
95
|
+
|
|
96
|
+
.pg-editor:focus-visible
|
|
97
|
+
outline none
|
|
98
|
+
border-color $pgaccent
|
|
99
|
+
box-shadow 0 0 0 3px rgba(143,214,148,.16)
|
|
100
|
+
|
|
101
|
+
.pg-preview
|
|
102
|
+
width 100%
|
|
103
|
+
min-height 30rem
|
|
104
|
+
flex 1
|
|
105
|
+
border 1px solid $pgline
|
|
106
|
+
radius $pgradius
|
|
107
|
+
bg #12190f
|
|
108
|
+
|
|
109
|
+
.pg-error
|
|
110
|
+
margin .75rem 0 0
|
|
111
|
+
padding .9rem 1.1rem
|
|
112
|
+
color #ffd9d0
|
|
113
|
+
bg #2a1712
|
|
114
|
+
border 1px solid #6b2c1e
|
|
115
|
+
radius $pgradius
|
|
116
|
+
font ui-monospace, "SF Mono", Menlo, Consolas, monospace
|
|
117
|
+
font-size .82rem
|
|
118
|
+
line-height 1.5
|
|
119
|
+
white-space pre-wrap
|
|
120
|
+
word-break break-word
|
|
121
|
+
|
|
122
|
+
@media (max-width: 820px)
|
|
123
|
+
.pg-workbench
|
|
124
|
+
grid-template-columns 1fr
|
|
125
|
+
.pg-editor
|
|
126
|
+
min-height 18rem
|
|
127
|
+
.pg-preview
|
|
128
|
+
min-height 22rem
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Playground — run Markdown in your browser
|
|
3
|
+
description: Edit a .wd file and watch it compile and run in your browser — the same compiler the Darkmown CLI uses, no install required.
|
|
4
|
+
html: true
|
|
5
|
+
transitions: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
@include /nav.wd
|
|
9
|
+
|
|
10
|
+
<main>
|
|
11
|
+
|
|
12
|
+
:state activeExample = "store"
|
|
13
|
+
|
|
14
|
+
::: section .pg-intro
|
|
15
|
+
|
|
16
|
+
# Playground
|
|
17
|
+
|
|
18
|
+
Edit the Markdown on the left. It compiles **in your browser** — the exact same
|
|
19
|
+
compiler the CLI ships — and runs live on the right. No install, no build step.
|
|
20
|
+
|
|
21
|
+
::: div .pg-picker
|
|
22
|
+
:button "Store + cart" -> activeExample = "store"
|
|
23
|
+
:button "Counter" -> activeExample = "counter"
|
|
24
|
+
:button "Plain Markdown" -> activeExample = "markdown"
|
|
25
|
+
:::
|
|
26
|
+
|
|
27
|
+
Now editing the **{ activeExample }** example. Change any text and the preview
|
|
28
|
+
re-renders as you type. Break the syntax on purpose — the real compiler error,
|
|
29
|
+
with `file:line` and a fix hint, shows up in the preview pane.
|
|
30
|
+
|
|
31
|
+
:::
|
|
32
|
+
|
|
33
|
+
::: section .pg-workbench
|
|
34
|
+
|
|
35
|
+
::: div .pg-pane .pg-pane-source
|
|
36
|
+
<label class="pg-label" for="pg-editor">Source</label>
|
|
37
|
+
|
|
38
|
+
<textarea id="pg-editor" class="pg-editor" spellcheck="false" autocapitalize="off" autocomplete="off" aria-label="Markdown source"></textarea>
|
|
39
|
+
:::
|
|
40
|
+
|
|
41
|
+
::: div .pg-pane .pg-pane-preview
|
|
42
|
+
<label class="pg-label" for="pg-preview">Preview</label>
|
|
43
|
+
|
|
44
|
+
<iframe id="pg-preview" class="pg-preview" title="Live preview" sandbox="allow-scripts allow-same-origin"></iframe>
|
|
45
|
+
|
|
46
|
+
<pre id="pg-error" class="pg-error" role="alert" hidden></pre>
|
|
47
|
+
:::
|
|
48
|
+
|
|
49
|
+
:::
|
|
50
|
+
|
|
51
|
+
</main>
|
|
@@ -96,7 +96,7 @@ Darkmown's whole pitch is weight. A static page ships **zero** framework JavaScr
|
|
|
96
96
|
|
|
97
97
|
::: card .sc-scale
|
|
98
98
|
- **Static Darkmown page** — `0 KB` framework JS. Forever.
|
|
99
|
-
- **Reactive Darkmown page** — one shared `~7.
|
|
99
|
+
- **Reactive Darkmown page** — one shared `~7.7 KB` gzipped runtime, no per-component bundles.
|
|
100
100
|
- **A typical SPA** — ships a framework runtime in the **~15–60 KB** gzipped range *before* your app code, on every page (Alpine ~15 KB, Vue ~18 KB runtime, React 19 ~59 KB).
|
|
101
101
|
:::
|
|
102
102
|
|
package/src/builder.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { discoverApiRoutes } from "./api-runner.js";
|
|
5
|
+
import { llmsText } from "./catalog.js";
|
|
5
6
|
import { buildCollections } from "./compiler/collections.js";
|
|
6
7
|
import { compilePage, parseFrontmatter } from "./compiler.js";
|
|
7
8
|
import { createPaths, routesDir, shelfDir } from "./config.js";
|
|
@@ -352,9 +353,11 @@ function buildRoute(route, cwd, paths, collections, feedLink, warned) {
|
|
|
352
353
|
* incremental builds keeps them consistent by construction.
|
|
353
354
|
*
|
|
354
355
|
* Cloudflare Pages reads dist/_headers for security headers (Vercel reads
|
|
355
|
-
* vercel.json; the local server adds them in src/statics.js).
|
|
356
|
-
*
|
|
357
|
-
*
|
|
356
|
+
* vercel.json; the local server adds them in src/statics.js). Since 2.1 both CSP
|
|
357
|
+
* variants are eval-free (the reactive runtime walks a validated AST, not
|
|
358
|
+
* `new Function`), so static and reactive routes resolve to the same policy; the
|
|
359
|
+
* per-route static override is kept as defense-in-depth. A catch-all keeps assets
|
|
360
|
+
* and any future path covered.
|
|
358
361
|
* @param {RouteManifestEntry[]} manifest
|
|
359
362
|
* @param {Paths} paths
|
|
360
363
|
* @returns {void}
|
|
@@ -362,6 +365,10 @@ function buildRoute(route, cwd, paths, collections, feedLink, warned) {
|
|
|
362
365
|
function emitGlobalFiles(manifest, paths) {
|
|
363
366
|
fs.writeFileSync(path.join(paths.distRoot, "routes.json"), JSON.stringify(manifest, null, 2));
|
|
364
367
|
fs.writeFileSync(path.join(paths.distRoot, "_headers"), renderCloudflareHeaders(manifest));
|
|
368
|
+
// `llms.txt` is the compact `.wd` cheatsheet an AI authoring tool fetches to
|
|
369
|
+
// prime a small model. It depends only on the framework version, so it re-emits
|
|
370
|
+
// identically on every build — cheap, and always present next to the site.
|
|
371
|
+
fs.writeFileSync(path.join(paths.distRoot, "llms.txt"), llmsText());
|
|
365
372
|
}
|
|
366
373
|
|
|
367
374
|
/**
|
|
@@ -924,10 +931,10 @@ export function stripRuntimeComments(source) {
|
|
|
924
931
|
*
|
|
925
932
|
* Cloudflare applies every matching block in order; when blocks set the same
|
|
926
933
|
* header, the later block wins. So we emit the catch-all `/*` first (baseline
|
|
927
|
-
* security headers + the
|
|
928
|
-
* one block per static route that
|
|
929
|
-
*
|
|
930
|
-
* already carries the
|
|
934
|
+
* security headers + the reactive CSP, which since 2.1 is eval-free and satisfies
|
|
935
|
+
* every page), then one block per static route that re-states the same eval-free
|
|
936
|
+
* CSP as defense-in-depth. Reactive routes need no override — the catch-all
|
|
937
|
+
* already carries the eval-free CSP.
|
|
931
938
|
*
|
|
932
939
|
* Clean URLs mean a route like `/docs/` is served at both `/docs` and `/docs/`,
|
|
933
940
|
* so each static route emits a path glob (`/docs`, `/docs/*`) that covers both.
|
|
@@ -941,7 +948,7 @@ export function renderCloudflareHeaders(manifest) {
|
|
|
941
948
|
const blocks = [["/*", [...baseLines, ` Content-Security-Policy: ${REACTIVE_CSP}`]]];
|
|
942
949
|
|
|
943
950
|
for (const entry of manifest) {
|
|
944
|
-
if (entry.assets.runtime) continue; // reactive routes keep the catch-all (
|
|
951
|
+
if (entry.assets.runtime) continue; // reactive routes keep the catch-all (eval-free) CSP
|
|
945
952
|
for (const pattern of cloudflarePathPatterns(entry.route)) {
|
|
946
953
|
blocks.push([pattern, [` Content-Security-Policy: ${STATIC_CSP}`]]);
|
|
947
954
|
}
|