@human-synthesis/norns 0.0.12 → 0.0.15

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/vite.js +33 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@human-synthesis/norns",
3
- "version": "0.0.12",
3
+ "version": "0.0.15",
4
4
  "description": "Norns — SvelteKit with Civet, Pug, and the .n / .civet / .c file extensions",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Teodoroiu (https://humansynthesis.ai)",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@danielx/civet": "^0.11.0",
39
- "@human-synthesis/norns-core": "^0.0.9"
39
+ "@human-synthesis/norns-core": "^0.0.10"
40
40
  },
41
41
  "engines": {
42
42
  "node": ">=18"
package/src/vite.js CHANGED
@@ -2,6 +2,7 @@ import { readFile, stat, realpath, readdir, mkdir, writeFile } from 'node:fs/pro
2
2
  import { dirname, join } from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
4
  import { compile as compileCivet } from '@danielx/civet';
5
+ import { extractPugClasses } from '@human-synthesis/norns-core/preprocess';
5
6
 
6
7
  const DEFAULT_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'];
7
8
  const NORNS_EXTENSIONS = ['.svelte', '.n', '.civet', '.c'];
@@ -143,53 +144,34 @@ export function nornsCivetPlugin() {
143
144
 
144
145
  /* === pugTailwindExtract ==================================================
145
146
  *
146
- * Tailwind v4's content extractor tokenizes candidates against a fixed
147
- * non-class alphabet. Pug class-shorthand chains followed directly by an
148
- * attribute paren `.grid.gap-6(class="…")` — fail that tokenizer: the
149
- * substring `.gap-6(` reads as one non-class token, so `gap-6` never
150
- * reaches the candidate set and the CSS rule is never generated.
147
+ * Tailwind v4's content scanner only picks up utility candidates from
148
+ * space-separated string contexts (`class="…"`, JS strings, etc.). It does
149
+ * NOT understand Pug's chained-class shorthand: `.flex.items-center.p-4`
150
+ * reads as one dotted token, doesn't match any utility, and gets dropped.
151
+ * Same for chains followed by an attribute paren `.grid.gap-6(class="…")`.
151
152
  *
152
- * Because the `tag.cls.cls(attrs)` form is idiomatic Pug, asking authors
153
- * to either move every utility into `class="…"` or hand-maintain a
154
- * safelist is a paper cut on every page they touch.
153
+ * Net effect without help: the HTML emitted by `.n` files has the class
154
+ * names, but Tailwind never generates rules for them. Pages render with
155
+ * silently-missing layout (no error, no warning, just visually wrong).
155
156
  *
156
- * This plugin walks every `.n` file under `root`, extracts each `.cls`
157
- * segment via a permissive regex, and writes the deduplicated set into a
158
- * single sidecar HTML file. Consumers reference the file from their CSS
159
- * via `@source "./.tailwind-pug-classes.html";` so Tailwind picks it up
160
- * like any other content source.
157
+ * This plugin walks every `.n` file under `root`, extracts each shorthand
158
+ * class via `extractPugClasses` from `@human-synthesis/norns-core`, and
159
+ * writes the deduplicated set into a sidecar HTML file. Consumers
160
+ * reference the file from their CSS via
161
+ * `@source "./.tailwind-pug-classes.html";`
162
+ * so Tailwind picks it up like any other content source.
161
163
  *
162
- * The regex is permissive on purpose it captures every `.candidate`
163
- * segment in the source, including occasional false positives like
164
- * `.svelte` inside template text. Those are free: Tailwind's own
165
- * candidate-validation step drops anything that isn't a real utility, so
166
- * the only cost is a few extra bytes in the sidecar.
164
+ * Extraction skips `<script>` and `<style>` blocks, mixin calls (`+if`),
165
+ * text-emit lines (`|`), raw HTML lines (`<`), and Pug comments (`//`).
166
+ * It also pulls tokens out of any `class="…"` / `class!="…"` attribute on
167
+ * the same line redundant with Tailwind's own scan in most cases, free
168
+ * defence in depth.
167
169
  *
168
170
  * Runs with `enforce: 'pre'` so the scan sees the raw Pug source, not the
169
171
  * Svelte output that the rest of the chain emits.
170
172
  * ========================================================================
171
173
  */
172
174
 
173
- /**
174
- * Match every `.candidate` segment. Class names may contain Tailwind's
175
- * full alphabet — letters, digits, `-`, `_`, `:`, `/`, and arbitrary-value
176
- * brackets `[...]`. Pug shorthand never contains a `.` inside a class
177
- * (the dot is the delimiter), so `text-[1.5rem]`-style values never appear
178
- * in shorthand — those always live inside `class="…"`, which Tailwind
179
- * extracts directly.
180
- */
181
- const SEGMENT_RE = /\.([A-Za-z][\w\-:/]*(?:\[[^\]]*\])?)/g;
182
-
183
- function extractPugClasses(source) {
184
- const out = new Set();
185
- let m;
186
- SEGMENT_RE.lastIndex = 0;
187
- while ((m = SEGMENT_RE.exec(source))) {
188
- if (m[1]) out.add(m[1]);
189
- }
190
- return out;
191
- }
192
-
193
175
  async function walkNFiles(dir, ext, out = []) {
194
176
  let entries;
195
177
  try {
@@ -216,17 +198,21 @@ async function walkNFiles(dir, ext, out = []) {
216
198
  * @param {object} [options]
217
199
  * @param {string} [options.root] Directory to scan (default `src`).
218
200
  * @param {string} [options.ext] File extension (default `.n`).
219
- * @param {string} [options.outFile] Sidecar path relative to root
220
- * (default `.tailwind-pug-classes.html`).
221
- * Reference it from your CSS with:
222
- * @source "./.tailwind-pug-classes.html";
201
+ * @param {string} [options.outFile] Sidecar path relative to the project
202
+ * root. The path is taken verbatim. Defaults
203
+ * to `node_modules/.cache/norns/tailwind-pug-classes.html`
204
+ * node_modules is gitignored everywhere
205
+ * and `.cache/` is the conventional spot
206
+ * for build artifacts. Reference it from
207
+ * your CSS via `@source` with a path
208
+ * relative to the importing CSS file.
223
209
  *
224
210
  * @returns {import('vite').Plugin}
225
211
  */
226
212
  export function pugTailwindExtract({
227
213
  root = 'src',
228
214
  ext = '.n',
229
- outFile = '.tailwind-pug-classes.html'
215
+ outFile = 'node_modules/.cache/norns/tailwind-pug-classes.html'
230
216
  } = {}) {
231
217
  let projectRoot = process.cwd();
232
218
  const fileClasses = new Map(); // absolute path -> Set<string>
@@ -240,7 +226,10 @@ export function pugTailwindExtract({
240
226
  const html =
241
227
  '<!-- AUTO-GENERATED by @human-synthesis/norns/vite pugTailwindExtract. Do not edit. -->\n' +
242
228
  `<div class="${sorted.join(' ')}"></div>\n`;
243
- const out = join(projectRoot, root, outFile);
229
+ // `outFile` is resolved from `projectRoot` directly; it doesn't get
230
+ // joined with `root` (the scan dir). Lets the sidecar live anywhere —
231
+ // inside src/, in a cache dir like `.norns/`, wherever.
232
+ const out = join(projectRoot, outFile);
244
233
  await mkdir(dirname(out), { recursive: true });
245
234
  await writeFile(out, html, 'utf8');
246
235
  }