@human-synthesis/norns-core 0.0.6 → 0.0.8
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 +15 -3
- package/package.json +3 -5
- package/src/index.js +1 -1
- package/src/preprocess.js +121 -260
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**AI-driven software architecture and development framework, based on Svelte.**
|
|
4
4
|
|
|
5
|
-
Svelte preprocessor for the Norns stack: Pug +
|
|
5
|
+
Svelte preprocessor for the Norns stack: **Pug + Civet** in `.n` files. The `.c` extension is recognised as an alias for `.civet` — both compile through Civet.
|
|
6
6
|
|
|
7
7
|
## Stack
|
|
8
8
|
|
|
9
9
|
- [Svelte 5](https://svelte.dev) — components and runes
|
|
10
10
|
- [Pug](https://pugjs.org) — templates
|
|
11
|
-
- [
|
|
11
|
+
- [Civet](https://civet.dev) — script (TypeScript-flavored, indented)
|
|
12
12
|
- [Vite](https://vitejs.dev) — bundler
|
|
13
13
|
- [bun](https://bun.sh) — runtime / package manager
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ Svelte preprocessor for the Norns stack: Pug + CoffeeScript in `.n` files, with
|
|
|
18
18
|
bun add -D @human-synthesis/norns-core svelte
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Most users want the umbrella package [`@human-synthesis/norns`](https://github.com/human-synthesis/norns) instead.
|
|
21
|
+
Most users want the umbrella package [`@human-synthesis/norns`](https://github.com/human-synthesis/norns) instead — it adds the SvelteKit config, the Vite plugin, and the runtime layer.
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
@@ -33,6 +33,18 @@ export default {
|
|
|
33
33
|
};
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## What it does
|
|
37
|
+
|
|
38
|
+
- `.n` files default `<script>` to `lang="civet"` and `<template>` to `lang="pug"` — write neither attribute and it just works.
|
|
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`) and fractional values (`.gap-2.5`) work without escaping.
|
|
42
|
+
- `+if` / `+elseif` / `+else` chains are rewritten to Svelte block syntax (`{#if}/{:else if}/{:else}/{/if}`).
|
|
43
|
+
|
|
44
|
+
## Auto-imports — see the umbrella
|
|
45
|
+
|
|
46
|
+
The auto-import layer (helpers, components, project utilities, UI library presets) lives in [`@human-synthesis/norns`](https://github.com/human-synthesis/norns), not here. `norns-core` is the syntax preprocessor only — the import resolver needs Vite-plugin hooks the umbrella provides.
|
|
47
|
+
|
|
36
48
|
## License
|
|
37
49
|
|
|
38
50
|
MIT © Daniel Teodoroiu / [Human Synthesis](https://humansynthesis.ai). Built on top of [Svelte](https://github.com/sveltejs/svelte) © Svelte Contributors, MIT licensed.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@human-synthesis/norns-core",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Norns core — Svelte preprocessor for
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"description": "Norns core — Svelte preprocessor for Civet, Pug, and .n files",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Daniel Teodoroiu (https://humansynthesis.ai)",
|
|
7
7
|
"type": "module",
|
|
@@ -25,9 +25,7 @@
|
|
|
25
25
|
"svelte": "^5.0.0"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
29
|
-
"coffeescript": "^2.7.0",
|
|
30
|
-
"magic-string": "^0.30.10",
|
|
28
|
+
"@danielx/civet": "^0.11.0",
|
|
31
29
|
"pug": "^3.0.3",
|
|
32
30
|
"svelte-preprocess": "^6.0.3"
|
|
33
31
|
},
|
package/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { nornsPreprocess
|
|
1
|
+
export { nornsPreprocess } from './preprocess.js';
|
package/src/preprocess.js
CHANGED
|
@@ -1,197 +1,8 @@
|
|
|
1
1
|
import { sveltePreprocess } from 'svelte-preprocess';
|
|
2
|
-
import {
|
|
3
|
-
import MagicString from 'magic-string';
|
|
4
|
-
|
|
5
|
-
const RUNES = new Set([
|
|
6
|
-
'$state',
|
|
7
|
-
'$state.raw',
|
|
8
|
-
'$derived',
|
|
9
|
-
'$derived.by',
|
|
10
|
-
'$effect',
|
|
11
|
-
'$effect.pre',
|
|
12
|
-
'$effect.root',
|
|
13
|
-
'$props',
|
|
14
|
-
'$bindable'
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
function isRuneCall(node) {
|
|
18
|
-
if (!node || node.type !== 'CallExpression') return false;
|
|
19
|
-
const c = node.callee;
|
|
20
|
-
if (c.type === 'Identifier') return RUNES.has(c.name);
|
|
21
|
-
if (c.type === 'MemberExpression' && c.object.type === 'Identifier') {
|
|
22
|
-
return RUNES.has(`${c.object.name}.${c.property.name}`);
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
2
|
+
import { compile as compileCivet } from '@danielx/civet';
|
|
26
3
|
|
|
27
|
-
|
|
28
|
-
* Fuse `var X; X = expr` patterns (CoffeeScript output) into `let X = expr`,
|
|
29
|
-
* so Svelte 5 accepts runes in declaration position and doesn't warn about
|
|
30
|
-
* non-state variables being "updated".
|
|
31
|
-
*
|
|
32
|
-
* Also handles `var X; ({X, ...} = $props())` (destructured props).
|
|
33
|
-
*
|
|
34
|
-
* Walks function bodies recursively so closures get the same treatment.
|
|
35
|
-
*/
|
|
36
|
-
export { transformIfChains, rewritePugClasses };
|
|
37
|
-
|
|
38
|
-
export function fuseRuneDeclarations(code) {
|
|
39
|
-
let ast;
|
|
40
|
-
try {
|
|
41
|
-
ast = parse(code, {
|
|
42
|
-
ecmaVersion: 'latest',
|
|
43
|
-
sourceType: 'module',
|
|
44
|
-
allowReturnOutsideFunction: true,
|
|
45
|
-
allowAwaitOutsideFunction: true
|
|
46
|
-
});
|
|
47
|
-
} catch {
|
|
48
|
-
return code;
|
|
49
|
-
}
|
|
4
|
+
export { transformIfChains, transformSnippets, rewritePugClasses };
|
|
50
5
|
|
|
51
|
-
const s = new MagicString(code);
|
|
52
|
-
walkBody(ast.body);
|
|
53
|
-
|
|
54
|
-
function walkBody(body) {
|
|
55
|
-
const fused = new Set();
|
|
56
|
-
const varStmts = [];
|
|
57
|
-
const claimed = new Set();
|
|
58
|
-
|
|
59
|
-
for (let i = 0; i < body.length; i++) {
|
|
60
|
-
const stmt = body[i];
|
|
61
|
-
|
|
62
|
-
if (stmt.type === 'VariableDeclaration' && stmt.kind === 'var') {
|
|
63
|
-
varStmts.push(stmt);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (stmt.type !== 'ExpressionStatement') continue;
|
|
67
|
-
const e = stmt.expression;
|
|
68
|
-
|
|
69
|
-
// Pattern: X = expr (where X is var-declared earlier in this scope).
|
|
70
|
-
// We fuse to `let X = expr` for any expression. Coffee always emits
|
|
71
|
-
// `var X; X = ...` for top-level assignments, and Svelte 5 reports
|
|
72
|
-
// `non_reactive_update` warnings for any `var` that's reassigned —
|
|
73
|
-
// even if the user only wrote a single function declaration. We
|
|
74
|
-
// limit to TOP LEVEL only (no recursion into nested function bodies)
|
|
75
|
-
// to avoid MagicString chunk conflicts.
|
|
76
|
-
//
|
|
77
|
-
// Special-case: `$derived(IIFE())` — Coffee compiles `$derived do ->`
|
|
78
|
-
// to `$derived((function(){...})())`, which evaluates ONCE at
|
|
79
|
-
// definition time instead of reactively. Rewrite to `$derived.by(fn)`.
|
|
80
|
-
if (
|
|
81
|
-
e.type === 'AssignmentExpression' &&
|
|
82
|
-
e.operator === '=' &&
|
|
83
|
-
e.left.type === 'Identifier' &&
|
|
84
|
-
isVarDeclared(varStmts, e.left.name) &&
|
|
85
|
-
!claimed.has(e.left.name)
|
|
86
|
-
) {
|
|
87
|
-
claimed.add(e.left.name);
|
|
88
|
-
fused.add(e.left.name);
|
|
89
|
-
|
|
90
|
-
const rewrite = rewriteDerivedIIFE(e.right, code);
|
|
91
|
-
if (rewrite) {
|
|
92
|
-
s.overwrite(
|
|
93
|
-
stmt.start,
|
|
94
|
-
stmt.end,
|
|
95
|
-
`let ${e.left.name} = $derived.by(${rewrite});`
|
|
96
|
-
);
|
|
97
|
-
} else {
|
|
98
|
-
s.appendLeft(stmt.start, 'let ');
|
|
99
|
-
}
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Pattern: ({X, Y} = $props()) → let {X, Y} = $props()
|
|
104
|
-
if (
|
|
105
|
-
e.type === 'AssignmentExpression' &&
|
|
106
|
-
e.operator === '=' &&
|
|
107
|
-
e.left.type === 'ObjectPattern' &&
|
|
108
|
-
isRuneCall(e.right)
|
|
109
|
-
) {
|
|
110
|
-
const names = collectPatternNames(e.left);
|
|
111
|
-
if (names.every((n) => isVarDeclared(varStmts, n) && !claimed.has(n))) {
|
|
112
|
-
for (const n of names) {
|
|
113
|
-
claimed.add(n);
|
|
114
|
-
fused.add(n);
|
|
115
|
-
}
|
|
116
|
-
const lhs = code.slice(e.left.start, e.left.end);
|
|
117
|
-
const rhs = code.slice(e.right.start, e.right.end);
|
|
118
|
-
s.overwrite(stmt.start, stmt.end, `let ${lhs} = ${rhs};`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
for (const stmt of varStmts) {
|
|
124
|
-
const remaining = stmt.declarations.filter(
|
|
125
|
-
(d) => !(d.id.type === 'Identifier' && fused.has(d.id.name))
|
|
126
|
-
);
|
|
127
|
-
if (remaining.length === 0) {
|
|
128
|
-
s.remove(stmt.start, stmt.end);
|
|
129
|
-
} else if (remaining.length < stmt.declarations.length) {
|
|
130
|
-
const rebuilt = remaining
|
|
131
|
-
.map((d) =>
|
|
132
|
-
d.init ? `${d.id.name} = ${code.slice(d.init.start, d.init.end)}` : d.id.name
|
|
133
|
-
)
|
|
134
|
-
.join(', ');
|
|
135
|
-
s.overwrite(stmt.start, stmt.end, `var ${rebuilt};`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Note: we deliberately do NOT recurse into nested function bodies.
|
|
140
|
-
// Svelte runes ($state, $derived, etc.) only apply at component
|
|
141
|
-
// top-level. Inner function bodies (event handlers, callbacks) have
|
|
142
|
-
// `var X; X = ...` patterns from CoffeeScript output too, but they
|
|
143
|
-
// don't trigger Svelte warnings and don't need fusion. Recursing
|
|
144
|
-
// also creates MagicString chunk conflicts when an outer assignment
|
|
145
|
-
// is being modified at the same time as something inside its body.
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function isVarDeclared(varStmts, name) {
|
|
149
|
-
for (const stmt of varStmts) {
|
|
150
|
-
for (const d of stmt.declarations) {
|
|
151
|
-
if (d.id.type === 'Identifier' && d.id.name === name && !d.init) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* If `node` is `$derived((function(){...})())` (the result of Coffee's
|
|
161
|
-
* `$derived do ->`), return the source text of the inner FunctionExpression
|
|
162
|
-
* so the caller can rewrite to `$derived.by(<fn>)`. Otherwise null.
|
|
163
|
-
*/
|
|
164
|
-
function rewriteDerivedIIFE(node, src) {
|
|
165
|
-
if (!node || node.type !== 'CallExpression') return null;
|
|
166
|
-
if (node.callee.type !== 'Identifier' || node.callee.name !== '$derived') return null;
|
|
167
|
-
if (node.arguments.length !== 1) return null;
|
|
168
|
-
const arg = node.arguments[0];
|
|
169
|
-
if (arg.type !== 'CallExpression') return null;
|
|
170
|
-
if (arg.arguments.length !== 0) return null;
|
|
171
|
-
const fn = arg.callee;
|
|
172
|
-
if (fn.type !== 'FunctionExpression' && fn.type !== 'ArrowFunctionExpression') return null;
|
|
173
|
-
return src.slice(fn.start, fn.end);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function collectPatternNames(pat) {
|
|
177
|
-
const names = [];
|
|
178
|
-
if (pat.type === 'ObjectPattern') {
|
|
179
|
-
for (const prop of pat.properties) {
|
|
180
|
-
if (prop.type === 'Property') {
|
|
181
|
-
let v = prop.value;
|
|
182
|
-
// Unwrap default value: `{ x = 1 }` has Property → AssignmentPattern → Identifier
|
|
183
|
-
if (v.type === 'AssignmentPattern') v = v.left;
|
|
184
|
-
if (v.type === 'Identifier') names.push(v.name);
|
|
185
|
-
} else if (prop.type === 'RestElement' && prop.argument.type === 'Identifier') {
|
|
186
|
-
names.push(prop.argument.name);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return names;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return s.toString();
|
|
194
|
-
}
|
|
195
6
|
|
|
196
7
|
const SCRIPT_TAG = /<script\b([^>]*)>/i;
|
|
197
8
|
const TEMPLATE_TAG = /<template\b([^>]*)>/i;
|
|
@@ -201,7 +12,7 @@ function hasLangAttr(attrs) {
|
|
|
201
12
|
}
|
|
202
13
|
|
|
203
14
|
/**
|
|
204
|
-
* For .norn files: inject lang="
|
|
15
|
+
* For .norn files: inject lang="civet" / lang="pug" defaults on
|
|
205
16
|
* <script> and <template> blocks, and auto-wrap any top-level non-script /
|
|
206
17
|
* non-style content in <template lang="pug">.
|
|
207
18
|
*/
|
|
@@ -226,6 +37,12 @@ const IF_RE = /^(\s*)\+if\s*\((.+)\)\s*$/;
|
|
|
226
37
|
const ELSEIF_RE_TPL = (ind) => new RegExp(`^${escapeRegex(ind)}\\+elseif\\s*\\((.+)\\)\\s*$`);
|
|
227
38
|
const ELSE_RE_TPL = (ind) => new RegExp(`^${escapeRegex(ind)}\\+else\\s*$`);
|
|
228
39
|
|
|
40
|
+
// `+snippet('name')` or `+snippet('name', arg1, arg2)`. Lazy match with `$`
|
|
41
|
+
// anchor lets the args list contain parens (e.g. `+snippet('row', fn(a))`)
|
|
42
|
+
// because the engine extends the lazy capture only until the outer `)` lands
|
|
43
|
+
// at end-of-line.
|
|
44
|
+
const SNIPPET_RE = /^(\s*)\+snippet\s*\(\s*['"](\w+)['"](?:\s*,\s*([\s\S]+?))?\s*\)\s*$/;
|
|
45
|
+
|
|
229
46
|
function escapeRegex(s) {
|
|
230
47
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
231
48
|
}
|
|
@@ -492,6 +309,78 @@ function transformIfChains(content) {
|
|
|
492
309
|
return out.join('\n');
|
|
493
310
|
}
|
|
494
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Rewrite Pug `+snippet('name', args…)` blocks to Svelte 5 `{#snippet name(args)}`
|
|
314
|
+
* via Pug `|` text emit. Recurses into the body so nested snippets work
|
|
315
|
+
* (`Tabs > +snippet('item', tab) > Card > +snippet('header')`).
|
|
316
|
+
*
|
|
317
|
+
* Input:
|
|
318
|
+
* +snippet('header')
|
|
319
|
+
* h2 Title
|
|
320
|
+
*
|
|
321
|
+
* +snippet('row', user, idx)
|
|
322
|
+
* .row Hello {user.name} {idx}
|
|
323
|
+
*
|
|
324
|
+
* Output:
|
|
325
|
+
* | {#snippet header()}
|
|
326
|
+
* h2 Title
|
|
327
|
+
* | {/snippet}
|
|
328
|
+
*
|
|
329
|
+
* | {#snippet row(user, idx)}
|
|
330
|
+
* .row Hello {user.name} {idx}
|
|
331
|
+
* | {/snippet}
|
|
332
|
+
*/
|
|
333
|
+
function transformSnippets(content) {
|
|
334
|
+
const lines = content.split('\n');
|
|
335
|
+
const out = [];
|
|
336
|
+
let i = 0;
|
|
337
|
+
|
|
338
|
+
while (i < lines.length) {
|
|
339
|
+
const m = lines[i].match(SNIPPET_RE);
|
|
340
|
+
if (!m) {
|
|
341
|
+
out.push(lines[i]);
|
|
342
|
+
i++;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const indent = m[1];
|
|
347
|
+
const name = m[2];
|
|
348
|
+
const args = m[3] ? m[3].trim() : '';
|
|
349
|
+
const indentDiff = detectIndentDiff(lines, i + 1, indent);
|
|
350
|
+
|
|
351
|
+
out.push(`${indent}| {#snippet ${name}(${args})}`);
|
|
352
|
+
i++;
|
|
353
|
+
|
|
354
|
+
// Collect body lines (more indented than the +snippet header) and
|
|
355
|
+
// process them recursively so nested +snippet blocks resolve.
|
|
356
|
+
/** @type {string[]} */
|
|
357
|
+
const body = [];
|
|
358
|
+
while (i < lines.length) {
|
|
359
|
+
const cur = lines[i];
|
|
360
|
+
|
|
361
|
+
if (cur.trim() === '') {
|
|
362
|
+
body.push(cur);
|
|
363
|
+
i++;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const lineIndent = cur.match(/^(\s*)/)[1];
|
|
368
|
+
if (lineIndent.length > indent.length) {
|
|
369
|
+
body.push(cur.startsWith(indentDiff) ? cur.slice(indentDiff.length) : cur);
|
|
370
|
+
i++;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (body.length > 0) out.push(transformSnippets(body.join('\n')));
|
|
378
|
+
out.push(`${indent}| {/snippet}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return out.join('\n');
|
|
382
|
+
}
|
|
383
|
+
|
|
495
384
|
function nornDefaultLangs() {
|
|
496
385
|
return {
|
|
497
386
|
name: 'norns-default-langs',
|
|
@@ -500,6 +389,7 @@ function nornDefaultLangs() {
|
|
|
500
389
|
|
|
501
390
|
let out = autoCloseTrailingBlock(content);
|
|
502
391
|
out = transformIfChains(out);
|
|
392
|
+
out = transformSnippets(out);
|
|
503
393
|
out = rewritePugClasses(out);
|
|
504
394
|
|
|
505
395
|
// If no <template> exists, scan for script/style blocks and wrap the rest.
|
|
@@ -527,9 +417,9 @@ function nornDefaultLangs() {
|
|
|
527
417
|
}
|
|
528
418
|
}
|
|
529
419
|
|
|
530
|
-
// Inject lang="
|
|
420
|
+
// Inject lang="civet" on <script> tags missing lang=
|
|
531
421
|
out = out.replace(SCRIPT_TAG, (full, attrs) =>
|
|
532
|
-
hasLangAttr(attrs) ? full : `<script lang="
|
|
422
|
+
hasLangAttr(attrs) ? full : `<script lang="civet"${attrs}>`
|
|
533
423
|
);
|
|
534
424
|
|
|
535
425
|
// Inject lang="pug" on <template> tags missing lang=
|
|
@@ -542,88 +432,61 @@ function nornDefaultLangs() {
|
|
|
542
432
|
};
|
|
543
433
|
}
|
|
544
434
|
|
|
545
|
-
function nornCoffeeRuneFusion() {
|
|
546
|
-
return {
|
|
547
|
-
name: 'norns-coffee-rune-fusion',
|
|
548
|
-
script({ content, attributes }) {
|
|
549
|
-
if (attributes.lang !== 'coffee' && attributes.lang !== 'coffeescript') return null;
|
|
550
|
-
const code = fuseRuneDeclarations(content);
|
|
551
|
-
return code === content ? null : { code };
|
|
552
|
-
}
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
|
|
556
435
|
/**
|
|
557
|
-
*
|
|
436
|
+
* Compile `<script lang="civet">` blocks to JavaScript via Civet.
|
|
558
437
|
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
* + Svelte's MagicString-based pipeline don't handle this gracefully when
|
|
563
|
-
* there are also nested var/assignment patterns inside script function bodies.
|
|
438
|
+
* Runs before svelte-preprocess so that downstream stages see plain JS.
|
|
439
|
+
* Civet emits source maps; we forward them so devtools can resolve back to
|
|
440
|
+
* the original `.civet` source.
|
|
564
441
|
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
442
|
+
* Civet's emit characteristics (verified May 2026, civet@0.11):
|
|
443
|
+
* - `count .= $state 0` → `let count = $state(0)`
|
|
444
|
+
* - `count := $state 0` → `const count = $state(0)`
|
|
445
|
+
* - `{ a, b = 0 } := $props()` → `const { a, b = 0 } = $props()`
|
|
446
|
+
* - imports stay where written; if user writes them at top, output is fine
|
|
567
447
|
*/
|
|
568
|
-
|
|
569
|
-
let ast;
|
|
570
|
-
try {
|
|
571
|
-
ast = parse(code, {
|
|
572
|
-
ecmaVersion: 'latest',
|
|
573
|
-
sourceType: 'module',
|
|
574
|
-
allowReturnOutsideFunction: true,
|
|
575
|
-
allowAwaitOutsideFunction: true
|
|
576
|
-
});
|
|
577
|
-
} catch {
|
|
578
|
-
return code;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const imports = ast.body.filter((s) => s.type === 'ImportDeclaration');
|
|
582
|
-
if (imports.length === 0) return code;
|
|
583
|
-
|
|
584
|
-
const firstNonImportIdx = ast.body.findIndex((s) => s.type !== 'ImportDeclaration');
|
|
585
|
-
if (firstNonImportIdx === -1) return code; // already all imports
|
|
586
|
-
|
|
587
|
-
const lateImports = imports.filter((i) => ast.body.indexOf(i) > firstNonImportIdx);
|
|
588
|
-
if (lateImports.length === 0) return code; // already in the right order
|
|
589
|
-
|
|
590
|
-
const s = new MagicString(code);
|
|
591
|
-
const importTexts = imports.map((i) => code.slice(i.start, i.end));
|
|
592
|
-
for (const i of imports) {
|
|
593
|
-
s.remove(i.start, i.end);
|
|
594
|
-
}
|
|
595
|
-
s.prependLeft(0, importTexts.join('\n') + '\n');
|
|
596
|
-
return s.toString();
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function nornsCoffeeImportLift() {
|
|
448
|
+
function nornsCivetScript() {
|
|
600
449
|
return {
|
|
601
|
-
name: 'norns-
|
|
602
|
-
script({ content, attributes }) {
|
|
603
|
-
if (attributes.lang !== '
|
|
604
|
-
const
|
|
605
|
-
|
|
450
|
+
name: 'norns-civet-script',
|
|
451
|
+
async script({ content, attributes, filename }) {
|
|
452
|
+
if (attributes.lang !== 'civet' && attributes.lang !== 'cv') return null;
|
|
453
|
+
const result = await compileCivet(content, {
|
|
454
|
+
js: true,
|
|
455
|
+
sourceMap: true,
|
|
456
|
+
filename: filename ?? 'unknown'
|
|
457
|
+
});
|
|
458
|
+
// Drop the `lang` attribute so svelte-preprocess doesn't try to load
|
|
459
|
+
// a `./transformers/civet` module — at this point the script body is
|
|
460
|
+
// already plain JS, no further script-level transform needed.
|
|
461
|
+
const { lang: _drop, ...nextAttrs } = attributes;
|
|
462
|
+
return {
|
|
463
|
+
code: result.code,
|
|
464
|
+
map: result.sourceMap?.json?.(filename ?? 'unknown') ?? null,
|
|
465
|
+
attributes: nextAttrs
|
|
466
|
+
};
|
|
606
467
|
}
|
|
607
468
|
};
|
|
608
469
|
}
|
|
609
470
|
|
|
471
|
+
|
|
610
472
|
/**
|
|
611
473
|
* Norns preprocessor stack.
|
|
612
474
|
*
|
|
613
|
-
* - `.norn` files default `<script>` to
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
* -
|
|
617
|
-
*
|
|
618
|
-
*
|
|
475
|
+
* - `.norn` files default `<script>` to Civet and `<template>` to Pug (and
|
|
476
|
+
* auto-wrap top-level content in `<template lang="pug">` if no template
|
|
477
|
+
* block is present).
|
|
478
|
+
* - `<script lang="civet">` blocks are compiled to JS via @danielx/civet
|
|
479
|
+
* before svelte-preprocess sees them. Civet emits ESM-correct
|
|
480
|
+
* `let count = $state(0)` directly, so no rune-fusion or import-lift
|
|
481
|
+
* passes are needed.
|
|
619
482
|
*
|
|
620
483
|
* @param {import('svelte-preprocess').AutoPreprocessOptions} [options]
|
|
621
484
|
*/
|
|
622
485
|
export function nornsPreprocess(options = {}) {
|
|
623
486
|
return [
|
|
624
487
|
nornDefaultLangs(),
|
|
488
|
+
nornsCivetScript(),
|
|
625
489
|
sveltePreprocess({
|
|
626
|
-
coffeescript: { bare: true },
|
|
627
490
|
pug: {},
|
|
628
491
|
typescript: {
|
|
629
492
|
compilerOptions: {
|
|
@@ -639,8 +502,6 @@ export function nornsPreprocess(options = {}) {
|
|
|
639
502
|
}
|
|
640
503
|
},
|
|
641
504
|
...options
|
|
642
|
-
})
|
|
643
|
-
nornsCoffeeImportLift(),
|
|
644
|
-
nornCoffeeRuneFusion()
|
|
505
|
+
})
|
|
645
506
|
];
|
|
646
507
|
}
|