vite-njk-trace
v1.0.1
Published
Dev-only Vite plugin that wraps every Nunjucks template (and rendered page) with HTML comments so you can see template origins in DevTools.
Maintainers
Readme
vite-njk-trace
Dev-only Vite plugin that injects Nunjucks template boundaries into your rendered HTML as comments, so every fragment in DevTools' Elements panel reveals exactly which
.njkfile it came from.
Features
- Loader trace — wraps every
{% include %},{% extends %}, and{% import %}template with<!-- NJK: START … -->/<!-- NJK: END … -->comments. - Page wrapper — wraps the rendered page body via
transformIndexHtml(post order) so the page itself is also annotated. - Zero-config dev-only — registered with
apply: "serve", sovite buildis unaffected and ships no trace comments. - Reference counted — multiple
enableNjkTraceLoadercalls are balanced; the prototype is only patched on the first call and restored on the last. - Lazy nunjucks resolution —
nunjucksis loaded withcreateRequireonly when the plugin runs. Installingvite-njk-tracein a non-Nunjucks project does not throwERR_MODULE_NOT_FOUND. - Hardened paths — leading separators in
pagesDirare stripped, and every label uses forward slashes regardless of host OS. - Sanitised HTML —
-->in template paths is escaped to-- >so injected comments are always well-formed. - Dual ESM/CJS — ships
dist/index.mjs,dist/index.cjs,dist/index.d.ts, anddist/index.d.ctswith full TypeScript declarations. - No side effects —
"sideEffects": falsefor safe tree-shaking.
Compatibility
| Dependency | Supported versions |
| ---------- | ------------------ |
| Vite | ^5.0.0, ^6.0.0, ^7.0.0, ^8.0.0 |
| Nunjucks | ^3.0.0 |
| Node | >=18 |
Both vite and nunjucks are listed as optional peer dependencies —
install them in your project if they are not already present.
Installation
pnpm add -D vite-njk-trace
# or
npm install -D vite-njk-trace
# or
yarn add -D vite-njk-traceQuick start
// vite.config.ts
import { defineConfig } from "vite";
import path from "node:path";
import { fileURLToPath } from "node:url";
import viteNjkTrace from "vite-njk-trace";
const projectRoot = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig({
plugins: [
viteNjkTrace({
projectRoot,
pagesDir: "frontend/src/views/pages",
}),
],
});Now open http://localhost:5173/ in your browser, inspect any element, and you
will see comments like:
<!-- NJK: START frontend/src/views/partials/_header.njk -->
<header>…</header>
<!-- NJK: END frontend/src/views/partials/_header.njk -->
<main>
<h1>Home</h1>
<!-- NJK: START frontend/src/views/partials/_card.njk -->
<section class="card">…</section>
<!-- NJK: END frontend/src/views/partials/_card.njk -->
</main>
<!-- NJK: START frontend/src/views/pages/index.njk -->
<!-- NJK: END frontend/src/views/pages/index.njk -->Order of plugins / Troubleshooting
vite-njk-trace uses enforce: "post" and the configResolved hook so that
the Nunjucks loader patch is applied after all normal plugins (including
template-engine plugins like vite-plugin-njk-frontmatter) have finished their
own setup. This means plugin order in vite.config.js no longer affects
correctness.
For best practice, place viteNjkTrace() after your template-engine
plugins in the plugins array:
export default defineConfig({
plugins: [
vitePluginNjkFrontmatter(), // template engine plugin
viteNjkTrace({ projectRoot }), // ← after template plugins (recommended)
],
});If you only see the root template (index.njk) traced but not its included
partials, ensure that your template engine plugin registers its Nunjucks
Environment / FileSystemLoader during the config or configResolved
phase — hooks that run after configResolved (e.g. configureServer) are too
late for the loader patch to pick up the loader instance.
Configuration
| Option | Type | Default | Description |
| ------------------- | --------- | -------------------------------- | --------------------------------------------------------------------------- |
| projectRoot | string | process.cwd() | Absolute root for resolving relative template paths in the HTML comments. |
| pagesDir | string | "frontend/src/views/pages" | Path to the directory of .njk pages, relative to projectRoot. Leading slashes are stripped. |
| enablePagesTrace | boolean | true | When true, the page wrapper (via transformIndexHtml) is enabled. |
API
viteNjkTrace(options?) (default export)
Returns a PluginOption[] containing:
vite-njk-trace:loader— monkey-patchesFileSystemLoader.prototype.getSource(dev only).vite-njk-trace:pages— wraps the rendered page viatransformIndexHtml(only whenenablePagesTraceistrue).
Both sub-plugins are dev-only (apply: "serve").
Programmatic / standalone
import nunjucks from "nunjucks";
import {
enableNjkTraceLoader,
disableNjkTraceLoader,
} from "vite-njk-trace";
enableNjkTraceLoader({ projectRoot: __dirname });
nunjucks.configure("frontend/src/views", { autoescape: false });
console.log(nunjucks.render("pages/index.njk"));
disableNjkTraceLoader();Sub-plugins
import viteNjkTrace, { njkTracePages } from "vite-njk-trace";
export default defineConfig({
plugins: [
viteNjkTrace({ enablePagesTrace: false }),
njkTracePages({ pagesDir: "src/pages" }),
],
});Exported symbols
| Export | Kind | Description |
| ---------------------------- | -------- | ---------------------------------------------------------------------------- |
| default / viteNjkTracePlugin | function | Vite plugin: returns the loader + (optional) pages sub-plugins. |
| enableNjkTraceLoader | function | Wraps FileSystemLoader.prototype.getSource. Reference-counted. |
| disableNjkTraceLoader | function | Decrements the refcount; restores original getSource only at 0. |
| isWrapped | function | Inspect whether the loader prototype is currently patched. |
| getPatchCount | function | Returns the current reference count for the loader patch. |
| njkTracePagesPlugin | function | Builds the page-wrapper Vite plugin (transformIndexHtml). |
| njkTracePages | function | Convenience wrapper that resolves user options and returns a Vite plugin. |
| START / END | function | Comment generators (<!-- NJK: START … --> / <!-- NJK: END … -->). |
Behaviour
- Dev-only.
apply: "serve"keeps the plugin out of the production bundle. - Reference counted. Each
enableNjkTraceLoaderincrements, eachdisableNjkTraceLoaderdecrements. The prototype is patched on0 → 1and restored on1 → 0. - Safe. If
nunjucks.FileSystemLoadercannot be resolved,enableNjkTraceLoaderlogs a warning and returns a no-op teardown. The plugin never throws at import time. - Path safe. Leading separators in
pagesDirare stripped so it can never be promoted to an absolute path. Labels are always forward-slash, regardless of host OS. - HTML safe. Any
-->inside a template path is escaped to-- >so injected comments remain well-formed. - Page resolution. The page wrapper tries
<base>.njkfirst, then<base>/index.njk, mirroring Vite's HTML routing.
Project layout
vite-njk-trace/
├── src/
│ ├── index.ts # Public API + default plugin (PluginOption[])
│ ├── loader.ts # Lazy FileSystemLoader prototype patch (ref-counted)
│ ├── pages-plugin.ts # transformIndexHtml wrapper (path-hardened)
│ └── types.ts # Options interface
├── tests/
│ └── unit/ # Vitest unit tests
├── tests_plugin/ # Manual integration playground
├── dist/ # Build output (tsup): {index.cjs,index.mjs,index.d.ts,index.d.cts}
├── tsup.config.ts
├── tsconfig.json
├── vitest.config.ts
└── package.jsonScripts
pnpm build # tsup → dist/{index.cjs,index.mjs,index.d.ts,index.d.cts}
pnpm test # vitest run
pnpm typecheck # tsc --noEmit
pnpm prepublishOnly # build + testManual playground
# from the repo root
pnpm install
pnpm --filter vite-njk-trace build
pnpm --filter vite-njk-trace-playground dev
# → http://localhost:5173/