npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@prostojs/rewrite

v0.2.3

Published

Easy and light templates renderer

Readme

@prostojs/rewrite

A lightweight template engine for code generation and scaffolding. Embed conditional blocks, loops, and expressions directly in real source files — templates stay syntactically valid and readable. Supports both source code (text mode with comment-based directives) and HTML/XML (Vue-like v-if, v-for, :attr bindings).

  • Templates compile to cached JS functions — repeated renders are near-instant
  • Under 27 KB bundled, zero runtime dependencies beyond @prostojs/parser
  • Auto-detects text vs HTML mode by file extension; switch modes mid-file with directives
  • Point at a directory to scaffold entire projects with a single call

Install

npm install @prostojs/rewrite
# or
pnpm add @prostojs/rewrite

Quick Start

import { ProstoRewrite } from '@prostojs/rewrite'

const rw = new ProstoRewrite()

// Text mode — rewrite a source code template
const result = rw.textRewriter.rewrite(
  `Hello {{ name }}! You have {{ count }} items.`,
  { name: 'World', count: 3 }
)
// => "Hello World! You have 3 items."

// HTML mode — rewrite markup
const html = rw.htmlRewriter.rewrite(
  `<ul><li v-for="item of items">{{ item }}</li></ul>`,
  { items: ['apple', 'banana', 'cherry'] }
)
// => "<ul><li>apple</li>\n<li>banana</li>\n<li>cherry</li></ul>"

Text Mode

Text mode is designed for source code, config files, Dockerfiles, and any non-HTML content. Directives are embedded in line comments, keeping your templates syntactically valid.

Expressions

Use {{ }} delimiters (customizable) to interpolate any JavaScript expression:

const greeting = '{{ salutation }} {{ name }}!'
const sum = {{ a + b }}

Context: { salutation: 'Hello', name: 'World', a: 2, b: 3 }

Output:

const greeting = 'Hello World!'
const sum = 5

Conditional Blocks (IF / ELSE / ELSEIF)

//=IF (env === 'production')
const API = 'https://api.prod.com'
//=ELSEIF (env === 'staging')
const API = 'https://api.staging.com'
//=ELSE
const API = 'http://localhost:3000'
//=ENDIF

Context: { env: 'staging' } produces:

const API = 'https://api.staging.com'

The comment prefix (//, #, --, etc.) is automatically detected from the line. Use whatever comment style fits your language:

#=IF (includeRedis)
redis:
  image: redis:alpine
#=ENDIF

Operation blocks reference:

| Key | Example | Description | |---|---|---| | IF | //=IF(condition) | Includes lines below only if condition is truthy. | | ELSEIF | //=ELSEIF(condition) | Alternative branch. Must follow IF or ELSEIF. | | ELSE | //=ELSE | Fallback branch. Must follow IF or ELSEIF. | | ENDIF | //=ENDIF | Closes the IF block chain. | | FOR | //=FOR(a of b) | Iterates with any valid JS loop expression. | | ENDFOR | //=ENDFOR | Closes the FOR block. |

Operation blocks can have spaces between words: ELSE IF, END FOR, etc. All blocks work with # and // comment prefixes.

Loops (FOR / ENDFOR)

//=FOR (const route of routes)
app.get('{{ route.path }}', {{ route.handler }})
//=ENDFOR

Context: { routes: [{ path: '/api', handler: 'apiHandler' }, { path: '/', handler: 'indexHandler' }] }

Output:

app.get('/api', apiHandler)
app.get('/', indexHandler)

Nesting

Blocks nest freely:

//=IF (features.auth)
//=FOR (const provider of authProviders)
//=IF (provider !== 'local')
import {{ provider }}Strategy from './strategies/{{ provider }}'
//=ENDIF
//=ENDFOR
//=ENDIF

Reveal Lines

Lines prefixed with //: (comment + reveal marker) are hidden in the template but appear in the output. This is the inverse of normal lines — useful for generating code that shouldn't be visible in the template source:

//=IF (useTypescript)
//: import type { Config } from './types'
//=ENDIF
const config = {}

Context: { useTypescript: true }

Output:

import type { Config } from './types'
const config = {}

Reveal lines can contain expressions too:

//: const {{ varName }} = {{ JSON.stringify(defaultValue) }}

Directives

| Directive | Example | Description | |---|---|---| | ignore-next-line | //!@ignore-next-line | The next line passes through as-is, without interpolation. | | html-mode-on | //!@html-mode-on | Switch to HTML mode (mixed rewriter only). | | html-mode-off | //!@html-mode-off | Switch back to text mode. |

//!@ ignore-next-line
const template = '{{ this is not interpolated }}'
const value = '{{ this IS interpolated }}'

Full Text Mode Example

Source:

let myVar = 1
//=IF (a === b)
//=FOR (const i of items)
//: const item{{ i }} = '{{ i }}' // reveal comment
//=IF (c === d)
myVar += 2
//=ELSE
myVar -= 4
//=ENDIF
//=END FOR
//=END IF
const myVar2 = 2

Context: { a: 1, b: 1, c: 2, d: 2, items: [1, 2] }

Output:

let myVar = 1
const item1 = '1' // reveal comment
myVar += 2
const item2 = '2' // reveal comment
myVar += 2
const myVar2 = 2

HTML Mode

HTML mode parses markup structure and supports Vue-inspired directives for conditionals, loops, and dynamic attributes.

Expressions

Interpolation works the same as text mode:

<h1>{{ title }}</h1>
<p>Welcome, {{ user.name }}!</p>

Conditional Rendering (v-if / v-else-if / v-else)

<div v-if="user.isAdmin">
    <h2>Admin Panel</h2>
</div>
<div v-else-if="user.isEditor">
    <h2>Editor Dashboard</h2>
</div>
<div v-else>
    <h2>Welcome, {{ user.name }}</h2>
</div>

Only the matching block is rendered. The v-if / v-else-if / v-else chain must be on sibling elements.

Loops (v-for)

<ul>
    <li v-for="item of items">{{ item }}</li>
</ul>

Any valid JavaScript loop expression works:

<tr v-for="let i = 0; i < rows.length; i++">
    <td>{{ rows[i].name }}</td>
</tr>

v-for and v-if can be combined on the same element:

<li v-for="item of items" v-if="item.visible">{{ item.name }}</li>

Dynamic Attributes

Prefix any attribute with : to evaluate it as a JavaScript expression:

<img :src="baseUrl + '/images/' + image.file" :alt="image.title">
<a :href="link.url" :target="link.external ? '_blank' : undefined">{{ link.text }}</a>

Smart boolean handling: if the expression evaluates to true, the attribute is rendered without a value (<input disabled>). If false, the attribute is omitted entirely:

<input type="text" :disabled="isLocked" :required="isRequired">

Context: { isLocked: true, isRequired: false }

Output:

<input type="text" disabled>

Switch to Text Mode

Inside an HTML file, switch to text-mode parsing with HTML comment directives:

<script>
    <!--!@ text-mode-on -->
    //=FOR (const key of Object.keys(config))
    window.{{ key }} = {{ JSON.stringify(config[key]) }}
    //=ENDFOR
    <!--!@ text-mode-off -->
</script>

And the reverse — switch to HTML inside a text file:

const html = `
//!@ html-mode-on
<div v-for='item of items'>
    <span>{{ item }}</span>
</div>
//!@ html-mode-off
`

File and Directory Rewriting

The main use case: scaffold entire project templates.

Rewrite a Single File

import { ProstoRewrite } from '@prostojs/rewrite'

const rw = new ProstoRewrite()

// Returns rendered content; optionally writes to output path
const content = await rw.rewriteFile(
  {
    input: 'path/to/template.js',
    output: 'path/to/output.js',  // optional
    mode: 'auto',                 // optional: 'text' | 'html' | 'auto'
  },
  { name: 'my-app', version: '1.0.0' }
)

Rewrite an Entire Directory

await rw.rewriteDir(
  {
    baseDir: './template',
    output: './my-new-project',
    include: ['**/*'],                                        // optional glob patterns
    exclude: ['node_modules/**'],                             // optional glob patterns
    renameFile: (name) => name.replace('__name__', 'my-app'), // optional
    onFile: (path, output) => console.log(`Wrote: ${path}`),  // optional callback
    mode: 'auto',                                             // optional
  },
  {
    name: 'my-app',
    description: 'My awesome app',
    useTypescript: true,
    features: ['auth', 'api', 'database'],
  }
)

Mode auto-detection picks the right parser based on file extension:

  • HTML mode: *.html, *.xhtml, *.xml, *.svg
  • Text mode: *.js, *.ts, *.jsx, *.tsx, *.json, *.yml, *.yaml, *.md, *.txt, *.ini, Dockerfile, *config, .gitignore

Files that don't match any pattern are copied as-is.

Rewriters

ProstoRewrite provides three rewriter flavors:

const rw = new ProstoRewrite()

const trw = rw.textRewriter    // text only
const hrw = rw.htmlRewriter    // html only
const mrw = rw.mixedRewriter   // both (supports mode-switching directives)
// mrw.text — text rewriter with html-mode-on support
// mrw.html — html rewriter with text-mode-on support

Each rewriter has the same interface:

// Generate the compiled JavaScript source (for inspection/debugging)
trw.genRewriteCode(source)

// Compile once, execute many times — ideal for repeated renders
const render = trw.genRewriteFunction(source)
const out1 = render({ name: 'Alice' })
const out2 = render({ name: 'Bob' })

// One-shot: parse + compile + execute
trw.rewrite(source, context)

// Debug: print the parse tree to console
trw.printAsTree(source)

String Expression Rewriter

For simple interpolation without block operations (config values, file paths):

import { getStringExpressionRewriter } from '@prostojs/rewrite'

const srw = getStringExpressionRewriter()

// Mixed string: always returns string
srw.rewrite('Hello {{ name }}, age {{ age }}', { name: 'World', age: 25 })
// => "Hello World, age 25"

// Single expression: preserves the original type
srw.rewrite('{{ count }}', { count: 42 })
// => 42 (number, not string)

This is useful for configuration files where property values can contain expressions:

const config = {
  path: "some/path/{{ key.toLowerCase() }}.{{ type === 'javascript' ? 'js' : 'json' }}",
}

const pathFunc = srw.genRewriteFunction(config.path)
pathFunc({ key: 'TEST', type: 'javascript' })
// => "some/path/test.js"

Options

All options are optional. The example below shows the default values:

const rw = new ProstoRewrite({
  defaultMode: 'auto',   // 'text' | 'html' | 'auto'
  debug: false,

  htmlPattern: ['*.{html,xhtml,xml,svg}'],
  textPattern: [
    '*.{js,jsx,ts,tsx,txt,json,yml,yaml,md,ini}',
    'Dockerfile',
    '*config',
    '.gitignore',
  ],

  text: {
    exprDelimiters: ['{{', '}}'],
    blockOperation: '=',
    revealLine: ':',
    directive: '!@',
  },

  html: {
    exprDelimiters: ['{{', '}}'],
    blockOperation: 'v-',
    attrExpression: ':',
    directive: '!@',
    voidTags: ['area', 'base', 'br', 'col', 'command', 'embed', 'hr',
               'img', 'input', 'keygen', 'link', 'meta', 'param',
               'source', 'track', 'wbr'],
    textTags: ['script', 'style'],
  },
})

| Option | Type | Description | |---|---|---| | defaultMode | 'text' | 'html' | 'auto' | Template processor selection. auto uses file patterns to decide. | | debug | boolean | Print debug parse trees to console. | | htmlPattern | string[] | Glob patterns for files processed by HTML parser. | | textPattern | string[] | Glob patterns for files processed by text parser. | | text.exprDelimiters | [string, string] | Expression delimiters. Default: ['{{', '}}'] | | text.blockOperation | string | Prefix for block operations (IF, FOR, ...). Default: '=' | | text.revealLine | string | Prefix for reveal lines. Default: ':' | | text.directive | string | Prefix for directives (ignore-next-line, ...). Default: '!@' | | html.exprDelimiters | [string, string] | Expression delimiters. Default: ['{{', '}}'] | | html.blockOperation | string | Prefix for block attributes (if, for, ...). Default: 'v-' | | html.attrExpression | string | Prefix for expression attributes. Default: ':' | | html.directive | string | Prefix for directives (text-mode-on, ...). Default: '!@' | | html.voidTags | string[] | Self-closing HTML tags. | | html.textTags | string[] | Tags with text-only content (no child tag parsing). |

License

MIT