norns

Vite plugin

nornsCivetPlugin() compiles Civet modules and teaches Vite the Norns extensions; pugTailwindExtract() closes Tailwind's blind spot on Pug class chains.

import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { nornsCivetPlugin, pugTailwindExtract } from '@human-synthesis/norns/vite';
import { nornsAutoImport } from '@human-synthesis/norns/auto-import';
import { presetUI } from '@human-synthesis/norns-ui/auto-import';

const ui = presetUI();

export default defineConfig({
  plugins: [
    pugTailwindExtract(),
    nornsCivetPlugin(),
    nornsAutoImport({ components: ui.components }),
    tailwindcss(),
    sveltekit()
  ],
  server: { allowedHosts: true }
});

#nornsCivetPlugin()

Runs with enforce: 'pre' and does four things:

  • Compiles .c and .civet files through @danielx/civet with source maps, so +page.server.c, hooks.server.c, +server.c and any module you write in Civet load like .js.
  • Registers the extensions .svelte, .n, .civet, .c with Vite's resolver, after Vite's defaults, so import X from './Foo' finds Foo.n or Foo.c.
  • Resolves bare sibling imports. import { play } from 'store' (no ./) resolves to a sibling store.c / store.n / store.js when one exists next to the importer. Package imports are untouched because they contain a slash or a scope; imports from inside node_modules are never rewritten.
  • Workspace mode. When the framework packages resolve to symlinks outside node_modules (the framework's own development workspace), they are excluded from dependency pre-bundling and lifted out of the watch ignore list so edits to the framework source hot-reload. Published installs never enter this branch.

#pugTailwindExtract(options?)

Tailwind v4's scanner extracts class candidates from string contexts. Pug's chained shorthand, .flex.items-center.p-4, reads as one dotted token and is dropped, and .grid.gap-6(class="...") loses the class before the paren. The page renders with the classes in the HTML but no CSS for them.

The plugin walks every .n file, extracts each shorthand class with extractPugClasses from norns-core, and writes the deduplicated set into a sidecar HTML file that Tailwind can scan. It re-runs on every .n change in dev.

Option Default Notes
root 'src' directory to scan
ext '.n' file extension
outFile 'node_modules/.cache/norns/tailwind-pug-classes.html' sidecar path, relative to the project root, taken verbatim

Reference the sidecar from your CSS, with a path relative to the CSS file:

@import 'tailwindcss';
@source "./**/*.n";
@source "../node_modules/.cache/norns/tailwind-pug-classes.html";

The first @source is the raw scan, which handles chains that end at whitespace; the sidecar covers the rest.

#Vite options worth setting

  • server.allowedHosts: true (or an explicit list) when the dev server sits behind any reverse proxy. norns lint warns when it is missing.
  • In the framework workspace only: resolve.dedupe: ['@sveltejs/kit'] and ssr.noExternal: ['@human-synthesis/norns'], so the symlinked norns package uses the app's single copy of SvelteKit instead of the fork's. A normal install does not need either.