@human-synthesis/norns-core 0.0.10 → 0.0.11
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/README.md +18 -3
- package/package.json +28 -19
- package/src/preprocess.js +283 -24
- package/types/index.d.ts +1 -0
- package/types/preprocess.d.ts +103 -0
package/README.md
CHANGED
|
@@ -37,9 +37,24 @@ export default {
|
|
|
37
37
|
|
|
38
38
|
- `.n` files default `<script>` to `lang="civet"` and `<template>` to `lang="pug"` — write neither attribute and it just works.
|
|
39
39
|
- `<script lang="civet">` blocks are compiled to JavaScript via [@danielx/civet](https://civet.dev) before svelte-preprocess sees them.
|
|
40
|
-
- Top-level Pug-only content is auto-wrapped in `<template lang="pug">` so you don't need the wrapper boilerplate.
|
|
41
|
-
- Pug class shorthand is rewritten so Tailwind variants (`.hover:bg-X`)
|
|
42
|
-
- `+if` / `+elseif` / `+else` chains are rewritten to Svelte block syntax (`{#if}/{:else if}/{:else}/{/if}`)
|
|
40
|
+
- Top-level Pug-only content is auto-wrapped in `<template lang="pug">` so you don't need the wrapper boilerplate; a trailing `<script>` block may omit its closing tag.
|
|
41
|
+
- Pug class shorthand is rewritten so Tailwind variants (`.hover:bg-X`), fractional values (`.gap-2.5`) and slashes (`.bg-white/40`) work without escaping.
|
|
42
|
+
- `+if` / `+elseif` / `+else` chains are rewritten to Svelte block syntax (`{#if}/{:else if}/{:else}/{/if}`), and `+snippet('name', args)` blocks to `{#snippet name(args)}`.
|
|
43
|
+
- Pug and Civet errors are reported as `file:line:column` **in the `.n` file you wrote**, with a code frame — not against svelte-preprocess's ~50-line mixin prelude or the script block's own numbering.
|
|
44
|
+
|
|
45
|
+
## Template syntax
|
|
46
|
+
|
|
47
|
+
| Write | Becomes |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `+if('cond')` … `+elseif('other')` … `+else` | `{#if cond}` … `{:else if other}` … `{:else}` … `{/if}` |
|
|
50
|
+
| `+each('items as item (item.id)')` | `{#each items as item (item.id)}` … `{/each}` — the Svelte `as` form; `item of items` is **not** valid |
|
|
51
|
+
| `+snippet('row', user, idx)` | `{#snippet row(user, idx)}` … `{/snippet}` |
|
|
52
|
+
| `\| {@render row(u, i)}` / `\| {@html raw}` | passed through as text — any line starting with `{` needs the `\| ` prefix |
|
|
53
|
+
| `attr!="{expr}"` | `attr={expr}` (Svelte expression); plain `attr="text"` stays a string |
|
|
54
|
+
| `.flex.items-center.gap-2.5(class="hover:bg-x")` | `class="flex items-center gap-2.5 hover:bg-x"` |
|
|
55
|
+
| `+key('expr')`, `+await('p')` / `+then('v')` / `+catch('e')` | the matching Svelte blocks (svelte-preprocess mixins) |
|
|
56
|
+
|
|
57
|
+
The `<script>` block is Civet by default (`lang="ts"` / `lang="js"` opt out): `{ a, b = 1 } := $props()`, `count .= $state 0` (use `.=` for anything you reassign), `total := $derived a + b`, `$effect => …`.
|
|
43
58
|
|
|
44
59
|
## Auto-imports — see the umbrella
|
|
45
60
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@human-synthesis/norns-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Norns core — Svelte preprocessor for Civet, Pug, and .n files",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Daniel Teodoroiu (https://humansynthesis.ai)",
|
|
@@ -11,28 +11,37 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"src",
|
|
14
|
+
"types",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
17
|
+
"types": "./types/index.d.ts",
|
|
16
18
|
"scripts": {
|
|
17
|
-
"test": "bun test"
|
|
19
|
+
"test": "bun test",
|
|
20
|
+
"build:types": "tsc -p tsconfig.types.json"
|
|
18
21
|
},
|
|
19
22
|
"exports": {
|
|
20
|
-
".":
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./types/index.d.ts",
|
|
25
|
+
"default": "./src/index.js"
|
|
23
26
|
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
"./preprocess": {
|
|
28
|
+
"types": "./types/preprocess.d.ts",
|
|
29
|
+
"default": "./src/preprocess.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"svelte": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@danielx/civet": "^0.11.0",
|
|
38
|
+
"pug": "^3.0.3",
|
|
39
|
+
"svelte-preprocess": "^6.0.3"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
38
47
|
}
|
package/src/preprocess.js
CHANGED
|
@@ -3,7 +3,6 @@ import { compile as compileCivet } from '@danielx/civet';
|
|
|
3
3
|
|
|
4
4
|
export { transformIfChains, transformSnippets, rewritePugClasses, extractPugClasses };
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
const SCRIPT_TAG = /<script\b([^>]*)>/i;
|
|
8
7
|
const TEMPLATE_TAG = /<template\b([^>]*)>/i;
|
|
9
8
|
|
|
@@ -47,6 +46,259 @@ function escapeRegex(s) {
|
|
|
47
46
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
48
47
|
}
|
|
49
48
|
|
|
49
|
+
/* === Error mapping ===========================================================
|
|
50
|
+
*
|
|
51
|
+
* svelte-preprocess renders Pug with a ~50-line mixin prelude prepended, so
|
|
52
|
+
* raw Pug errors point ~50 lines past the real one; on top of that, `.n`
|
|
53
|
+
* files are re-arranged (template first, +if/+snippet rewritten) before Pug
|
|
54
|
+
* sees them. Civet errors inside `<script>` blocks are relative to the
|
|
55
|
+
* block, not the file. Both are mapped back to `file:line:column` in the
|
|
56
|
+
* source the user actually wrote, so an agent (or a human) opens the right
|
|
57
|
+
* line on the first try.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/** Original file contents, keyed by filename, stashed by nornDefaultLangs. */
|
|
61
|
+
const ORIGINAL_SOURCES = new Map();
|
|
62
|
+
const FALLBACK_PUG_PRELUDE = 52;
|
|
63
|
+
|
|
64
|
+
function rememberSource(filename, content) {
|
|
65
|
+
if (!filename) return;
|
|
66
|
+
if (ORIGINAL_SOURCES.size > 2000) ORIGINAL_SOURCES.clear();
|
|
67
|
+
ORIGINAL_SOURCES.set(filename, content);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Parse Pug's numbered code frame out of an error message. */
|
|
71
|
+
function parsePugFrame(message) {
|
|
72
|
+
const frame = [];
|
|
73
|
+
let marked = null;
|
|
74
|
+
for (const line of String(message).split('\n')) {
|
|
75
|
+
const m = line.match(/^\s*(>)?\s*(\d+)\|(.*)$/);
|
|
76
|
+
if (!m) continue;
|
|
77
|
+
const n = Number(m[2]);
|
|
78
|
+
frame.push({ n, text: m[3].replace(/^ /, ''), marked: !!m[1] });
|
|
79
|
+
if (m[1]) marked = n;
|
|
80
|
+
}
|
|
81
|
+
return { frame, marked };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The human-readable core of a Pug error (last non-frame, non-location line). */
|
|
85
|
+
function pugMessageCore(message) {
|
|
86
|
+
const lines = String(message).split('\n');
|
|
87
|
+
let core = '';
|
|
88
|
+
for (const raw of lines) {
|
|
89
|
+
const line = raw.trim();
|
|
90
|
+
if (!line) continue;
|
|
91
|
+
if (/^\[svelte-preprocess\]/.test(line)) continue;
|
|
92
|
+
if (/^>?\s*\d+\|/.test(line)) continue;
|
|
93
|
+
if (/^-+\^?$/.test(line)) continue;
|
|
94
|
+
if (/^\S+:\d+:\d+$/.test(line)) continue;
|
|
95
|
+
core = line;
|
|
96
|
+
}
|
|
97
|
+
return core || 'Pug error';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractTemplate(content) {
|
|
101
|
+
const m = content.match(/<template\b[^>]*>([\s\S]*?)<\/template>/i);
|
|
102
|
+
return m ? m[1] : content;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reverse the `.n` rewrites for one template line so it can be looked up in
|
|
107
|
+
* the original source: `| {#if x}` came from `+if('x')`, etc.
|
|
108
|
+
*/
|
|
109
|
+
function sourceVariants(text) {
|
|
110
|
+
const t = text.trim();
|
|
111
|
+
const out = [text, t];
|
|
112
|
+
let m;
|
|
113
|
+
if ((m = t.match(/^\|\s*\{#if (.+)\}$/))) out.push(`+if('${m[1]}')`, `+if("${m[1]}")`);
|
|
114
|
+
else if ((m = t.match(/^\|\s*\{:else if (.+)\}$/)))
|
|
115
|
+
out.push(`+elseif('${m[1]}')`, `+elseif("${m[1]}")`);
|
|
116
|
+
else if (/^\|\s*\{:else\}$/.test(t)) out.push('+else');
|
|
117
|
+
else if ((m = t.match(/^\|\s*\{#snippet (\w+)\((.*)\)\}$/))) {
|
|
118
|
+
out.push(m[2] ? `+snippet('${m[1]}', ${m[2]})` : `+snippet('${m[1]}')`);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Find the original line for a (possibly rewritten) template line.
|
|
125
|
+
*
|
|
126
|
+
* @returns {{ line: number, exact: boolean }}
|
|
127
|
+
*/
|
|
128
|
+
function findOriginalLine(text, origLines, approx) {
|
|
129
|
+
const trimmed = text.trim();
|
|
130
|
+
const clamp = (n) => Math.min(Math.max(1, n), Math.max(1, origLines.length));
|
|
131
|
+
if (!trimmed) return { line: clamp(approx), exact: false };
|
|
132
|
+
|
|
133
|
+
const nearest = (idxs) =>
|
|
134
|
+
idxs.reduce(
|
|
135
|
+
(best, j) => (Math.abs(j + 1 - approx) < Math.abs(best + 1 - approx) ? j : best),
|
|
136
|
+
idxs[0]
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
for (const variant of sourceVariants(text)) {
|
|
140
|
+
const v = variant.trim();
|
|
141
|
+
if (!v) continue;
|
|
142
|
+
const hits = [];
|
|
143
|
+
for (let j = 0; j < origLines.length; j++) {
|
|
144
|
+
if (origLines[j] === variant || origLines[j].trim() === v) hits.push(j);
|
|
145
|
+
}
|
|
146
|
+
if (hits.length === 1) return { line: hits[0] + 1, exact: true };
|
|
147
|
+
if (hits.length > 1) return { line: nearest(hits) + 1, exact: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Class-shorthand rewrites move classes into `(class="...")`; fall back to
|
|
151
|
+
// the tag/leading-class prefix.
|
|
152
|
+
const prefix = trimmed.match(/^[\w.#-]+/)?.[0];
|
|
153
|
+
if (prefix && prefix.length >= 3) {
|
|
154
|
+
const hits = [];
|
|
155
|
+
for (let j = 0; j < origLines.length; j++) {
|
|
156
|
+
if (origLines[j].trim().startsWith(prefix)) hits.push(j);
|
|
157
|
+
}
|
|
158
|
+
if (hits.length > 0) return { line: nearest(hits) + 1, exact: false };
|
|
159
|
+
}
|
|
160
|
+
return { line: clamp(approx), exact: false };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function frameOf(lines, line, column) {
|
|
164
|
+
const from = Math.max(1, line - 2);
|
|
165
|
+
const to = Math.min(lines.length, line + 1);
|
|
166
|
+
const width = String(to).length;
|
|
167
|
+
const out = [];
|
|
168
|
+
for (let n = from; n <= to; n++) {
|
|
169
|
+
out.push(`${n === line ? '>' : ' '} ${String(n).padStart(width)}| ${lines[n - 1] ?? ''}`);
|
|
170
|
+
if (n === line && column)
|
|
171
|
+
out.push(` ${' '.repeat(width)}| ${' '.repeat(Math.max(0, column - 1))}^`);
|
|
172
|
+
}
|
|
173
|
+
return out.join('\n');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Turn a raw svelte-preprocess Pug error into one that points at the
|
|
178
|
+
* source file. Non-Pug errors pass through untouched.
|
|
179
|
+
*/
|
|
180
|
+
function mapPugError(e, content, filename) {
|
|
181
|
+
if (!e || typeof e.message !== 'string') return e;
|
|
182
|
+
if (!/Pug error|pug/i.test(e.message) && typeof e.line !== 'number') return e;
|
|
183
|
+
|
|
184
|
+
const { frame, marked } = parsePugFrame(e.message);
|
|
185
|
+
const pugLine = typeof e.line === 'number' ? e.line : marked;
|
|
186
|
+
if (!pugLine) return e;
|
|
187
|
+
|
|
188
|
+
const tplLines = extractTemplate(content).split('\n');
|
|
189
|
+
|
|
190
|
+
// Vote for the prelude offset using every frame line we can find verbatim
|
|
191
|
+
// in the template we handed to Pug.
|
|
192
|
+
const votes = new Map();
|
|
193
|
+
for (const f of frame) {
|
|
194
|
+
if (!f.text.trim()) continue;
|
|
195
|
+
for (let j = 0; j < tplLines.length; j++) {
|
|
196
|
+
if (tplLines[j] === f.text) {
|
|
197
|
+
const off = f.n - (j + 1);
|
|
198
|
+
votes.set(off, (votes.get(off) ?? 0) + 1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let offset = FALLBACK_PUG_PRELUDE;
|
|
203
|
+
let best = 0;
|
|
204
|
+
for (const [off, n] of votes) {
|
|
205
|
+
if (n > best) {
|
|
206
|
+
best = n;
|
|
207
|
+
offset = off;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const tplLine = pugLine - offset;
|
|
212
|
+
const text = tplLines[tplLine - 1] ?? '';
|
|
213
|
+
|
|
214
|
+
const original = (filename && ORIGINAL_SOURCES.get(filename)) ?? content;
|
|
215
|
+
const origLines = original.split('\n');
|
|
216
|
+
const tplStart = Math.max(
|
|
217
|
+
0,
|
|
218
|
+
origLines.findIndex((l) => /<template\b/i.test(l))
|
|
219
|
+
);
|
|
220
|
+
const { line, exact } = findOriginalLine(text, origLines, tplLine + tplStart);
|
|
221
|
+
const column = typeof e.column === 'number' && e.column > 0 ? e.column : null;
|
|
222
|
+
|
|
223
|
+
const core = pugMessageCore(e.message);
|
|
224
|
+
const name = filename ? filename.split(/[\\/]/).pop() : 'template';
|
|
225
|
+
const frameText = frameOf(origLines, line, column);
|
|
226
|
+
const err = new Error(
|
|
227
|
+
`${name}:${line}:${column ?? 1}: Pug: ${core}${exact ? '' : ' (approximate line)'}\n\n${frameText}`
|
|
228
|
+
);
|
|
229
|
+
err.name = 'PugError';
|
|
230
|
+
err.code = 'norns_pug_error';
|
|
231
|
+
err.line = line;
|
|
232
|
+
err.column = column;
|
|
233
|
+
err.filename = filename;
|
|
234
|
+
err.frame = frameText;
|
|
235
|
+
err.approximate = !exact;
|
|
236
|
+
err.pugLine = pugLine;
|
|
237
|
+
err.cause = e;
|
|
238
|
+
return err;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Map a Civet ParseError thrown for a `<script>` block back to the line in
|
|
243
|
+
* the containing file.
|
|
244
|
+
*/
|
|
245
|
+
function mapCivetScriptError(e, content, filename) {
|
|
246
|
+
if (!e || typeof e.line !== 'number') return e;
|
|
247
|
+
const original = filename && ORIGINAL_SOURCES.get(filename);
|
|
248
|
+
if (!original) return e;
|
|
249
|
+
|
|
250
|
+
const blockRe = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
251
|
+
let m;
|
|
252
|
+
let bodyStart = -1;
|
|
253
|
+
let firstBodyStart = -1;
|
|
254
|
+
while ((m = blockRe.exec(original)) !== null) {
|
|
255
|
+
const start = m.index + m[0].indexOf('>') + 1;
|
|
256
|
+
if (firstBodyStart < 0) firstBodyStart = start;
|
|
257
|
+
if (m[1] === content) {
|
|
258
|
+
bodyStart = start;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (bodyStart < 0) bodyStart = firstBodyStart;
|
|
263
|
+
if (bodyStart < 0) return e;
|
|
264
|
+
|
|
265
|
+
const bodyStartLine = original.slice(0, bodyStart).split('\n').length;
|
|
266
|
+
const line = bodyStartLine + e.line - 1;
|
|
267
|
+
const column = typeof e.column === 'number' ? e.column : null;
|
|
268
|
+
const origLines = original.split('\n');
|
|
269
|
+
const name = filename.split(/[\\/]/).pop();
|
|
270
|
+
const rest = String(e.message).split('\n');
|
|
271
|
+
const head = rest.shift() ?? '';
|
|
272
|
+
const core = head.replace(/^\S+:\d+:\d+\s*/, '');
|
|
273
|
+
const frameText = frameOf(origLines, line, column);
|
|
274
|
+
e.message = [`${name}:${line}:${column ?? 1}: Civet: ${core}`, ...rest, '', frameText].join('\n');
|
|
275
|
+
e.line = line;
|
|
276
|
+
e.column = column;
|
|
277
|
+
e.filename = filename;
|
|
278
|
+
e.frame = frameText;
|
|
279
|
+
e.code = 'norns_civet_error';
|
|
280
|
+
return e;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Wrap svelte-preprocess so Pug failures come back mapped to the source.
|
|
285
|
+
*/
|
|
286
|
+
function withMappedPugErrors(sp) {
|
|
287
|
+
return {
|
|
288
|
+
...sp,
|
|
289
|
+
name: sp.name ?? 'norns-svelte-preprocess',
|
|
290
|
+
markup: sp.markup
|
|
291
|
+
? async (args) => {
|
|
292
|
+
try {
|
|
293
|
+
return await sp.markup(args);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
throw mapPugError(e, args.content, args.filename);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
: undefined
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
50
302
|
function stripQuotes(s) {
|
|
51
303
|
s = s.trim();
|
|
52
304
|
if (s.length >= 2) {
|
|
@@ -501,6 +753,7 @@ function nornDefaultLangs() {
|
|
|
501
753
|
name: 'norns-default-langs',
|
|
502
754
|
markup({ content, filename }) {
|
|
503
755
|
if (!filename || !filename.endsWith('.n')) return null;
|
|
756
|
+
rememberSource(filename, content);
|
|
504
757
|
|
|
505
758
|
let out = autoCloseTrailingBlock(content);
|
|
506
759
|
out = transformIfChains(out);
|
|
@@ -565,11 +818,16 @@ function nornsCivetScript() {
|
|
|
565
818
|
name: 'norns-civet-script',
|
|
566
819
|
async script({ content, attributes, filename }) {
|
|
567
820
|
if (attributes.lang !== 'civet' && attributes.lang !== 'cv') return null;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
821
|
+
let result;
|
|
822
|
+
try {
|
|
823
|
+
result = await compileCivet(content, {
|
|
824
|
+
js: true,
|
|
825
|
+
sourceMap: true,
|
|
826
|
+
filename: filename ?? 'unknown'
|
|
827
|
+
});
|
|
828
|
+
} catch (e) {
|
|
829
|
+
throw mapCivetScriptError(e, content, filename);
|
|
830
|
+
}
|
|
573
831
|
// Drop the `lang` attribute so svelte-preprocess doesn't try to load
|
|
574
832
|
// a `./transformers/civet` module — at this point the script body is
|
|
575
833
|
// already plain JS, no further script-level transform needed.
|
|
@@ -583,7 +841,6 @@ function nornsCivetScript() {
|
|
|
583
841
|
};
|
|
584
842
|
}
|
|
585
843
|
|
|
586
|
-
|
|
587
844
|
/**
|
|
588
845
|
* Norns preprocessor stack.
|
|
589
846
|
*
|
|
@@ -601,22 +858,24 @@ export function nornsPreprocess(options = {}) {
|
|
|
601
858
|
return [
|
|
602
859
|
nornDefaultLangs(),
|
|
603
860
|
nornsCivetScript(),
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
861
|
+
withMappedPugErrors(
|
|
862
|
+
sveltePreprocess({
|
|
863
|
+
pug: {},
|
|
864
|
+
typescript: {
|
|
865
|
+
compilerOptions: {
|
|
866
|
+
// Silence TS 6.x's deprecation warning for older moduleResolution
|
|
867
|
+
// values (node10) that some toolchains still default to.
|
|
868
|
+
ignoreDeprecations: '6.0',
|
|
869
|
+
// Preserve value imports (Svelte component imports look "unused"
|
|
870
|
+
// to the TS transpiler since their usage lives in the template,
|
|
871
|
+
// but they MUST be emitted). verbatimModuleSyntax keeps any
|
|
872
|
+
// non-`import type` imports verbatim.
|
|
873
|
+
verbatimModuleSyntax: true,
|
|
874
|
+
isolatedModules: true
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
...options
|
|
878
|
+
})
|
|
879
|
+
)
|
|
621
880
|
];
|
|
622
881
|
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { nornsPreprocess } from "./preprocess.js";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Norns preprocessor stack.
|
|
3
|
+
*
|
|
4
|
+
* - `.norn` files default `<script>` to Civet and `<template>` to Pug (and
|
|
5
|
+
* auto-wrap top-level content in `<template lang="pug">` if no template
|
|
6
|
+
* block is present).
|
|
7
|
+
* - `<script lang="civet">` blocks are compiled to JS via @danielx/civet
|
|
8
|
+
* before svelte-preprocess sees them. Civet emits ESM-correct
|
|
9
|
+
* `let count = $state(0)` directly, so no rune-fusion or import-lift
|
|
10
|
+
* passes are needed.
|
|
11
|
+
*
|
|
12
|
+
* @param {import('svelte-preprocess').AutoPreprocessOptions} [options]
|
|
13
|
+
*/
|
|
14
|
+
export function nornsPreprocess(options?: import("svelte-preprocess").AutoPreprocessOptions): any[];
|
|
15
|
+
/**
|
|
16
|
+
* Rewrite Pug `+if('expr') / +elseif('expr') / +else` chains to raw Svelte
|
|
17
|
+
* block syntax emitted via Pug `|` text. Bypasses svelte-preprocess's `+if`
|
|
18
|
+
* mixin (which doesn't support chaining).
|
|
19
|
+
*
|
|
20
|
+
* Input:
|
|
21
|
+
* +if('a')
|
|
22
|
+
* div one
|
|
23
|
+
* +elseif('b')
|
|
24
|
+
* div two
|
|
25
|
+
* +else
|
|
26
|
+
* div three
|
|
27
|
+
*
|
|
28
|
+
* Output:
|
|
29
|
+
* | {#if a}
|
|
30
|
+
* div one
|
|
31
|
+
* | {:else if b}
|
|
32
|
+
* div two
|
|
33
|
+
* | {:else}
|
|
34
|
+
* div three
|
|
35
|
+
* | {/if}
|
|
36
|
+
*/
|
|
37
|
+
export function transformIfChains(content: any): any;
|
|
38
|
+
/**
|
|
39
|
+
* Rewrite Pug `+snippet('name', args…)` blocks to Svelte 5 `{#snippet name(args)}`
|
|
40
|
+
* via Pug `|` text emit. Recurses into the body so nested snippets work
|
|
41
|
+
* (`Tabs > +snippet('item', tab) > Card > +snippet('header')`).
|
|
42
|
+
*
|
|
43
|
+
* Input:
|
|
44
|
+
* +snippet('header')
|
|
45
|
+
* h2 Title
|
|
46
|
+
*
|
|
47
|
+
* +snippet('row', user, idx)
|
|
48
|
+
* .row Hello {user.name} {idx}
|
|
49
|
+
*
|
|
50
|
+
* Output:
|
|
51
|
+
* | {#snippet header()}
|
|
52
|
+
* h2 Title
|
|
53
|
+
* | {/snippet}
|
|
54
|
+
*
|
|
55
|
+
* | {#snippet row(user, idx)}
|
|
56
|
+
* .row Hello {user.name} {idx}
|
|
57
|
+
* | {/snippet}
|
|
58
|
+
*/
|
|
59
|
+
export function transformSnippets(content: any): any;
|
|
60
|
+
/**
|
|
61
|
+
* Rewrite Pug element lines whose class shorthand contains characters Pug's
|
|
62
|
+
* lexer rejects (`:`, `/`) or that Pug already mis-parses (fractional `.\d+`
|
|
63
|
+
* continuations like `.gap-2.5`). Route those classes from shorthand into the
|
|
64
|
+
* `(class="...")` attribute. Pug then sees only safe class shorthand.
|
|
65
|
+
*
|
|
66
|
+
* Examples:
|
|
67
|
+
* `.text-blue.hover:bg-red(href="/")`
|
|
68
|
+
* → `.text-blue(class="hover:bg-red" href="/")`
|
|
69
|
+
* `.gap-2.5.flex`
|
|
70
|
+
* → `.flex(class="gap-2.5")`
|
|
71
|
+
* `.bg-white/40.text-4xl(class="static")`
|
|
72
|
+
* → `.text-4xl(class="bg-white/40 static")`
|
|
73
|
+
*
|
|
74
|
+
* Skips lines inside `<script>` / `<style>` blocks.
|
|
75
|
+
*/
|
|
76
|
+
export function rewritePugClasses(content: any): any;
|
|
77
|
+
/**
|
|
78
|
+
* Walk `content` and return the set of Pug class-shorthand names found in
|
|
79
|
+
* element class chains (`.foo.bar-baz.hover:bg-red`) plus the value of any
|
|
80
|
+
* `class="..."` attribute on the same lines.
|
|
81
|
+
*
|
|
82
|
+
* Tailwind v4's content scanner extracts utility candidates from string
|
|
83
|
+
* contexts (`class="…"`, JS strings) but doesn't recognise Pug's chained
|
|
84
|
+
* shorthand — the dotted chain looks like one token. Pages render with the
|
|
85
|
+
* class names present in the markup but no matching CSS, which is silent
|
|
86
|
+
* and hard to spot. The companion `nornsTailwindPlugin()` Vite plugin in
|
|
87
|
+
* `@human-synthesis/norns` calls this and feeds the union into Tailwind via
|
|
88
|
+
* an injected `@source inline(...)` directive.
|
|
89
|
+
*
|
|
90
|
+
* Skips lines inside `<script>` / `<style>` blocks. Skips lines that begin
|
|
91
|
+
* with `|`, `<`, `+`, `:`, or `//` (Pug text emits, raw HTML, mixin calls,
|
|
92
|
+
* pug filters, and comments). For each remaining line, reads an optional
|
|
93
|
+
* tag, then chained `.<class>` segments (handling `:`, `/`, and fractional
|
|
94
|
+
* `.\d+` continuations), then collects the value of any `class="…"` or
|
|
95
|
+
* `class!="…"` attribute that follows.
|
|
96
|
+
*
|
|
97
|
+
* Pure function — does not mutate `content`. Returns a `Set<string>` so
|
|
98
|
+
* callers can union across many files without dedup work.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} content
|
|
101
|
+
* @returns {Set<string>}
|
|
102
|
+
*/
|
|
103
|
+
export function extractPugClasses(content: string): Set<string>;
|