@prostojs/rewrite
v0.1.1
Published
Easy and light templates renderer
Downloads
1,734
Maintainers
Readme
@prostojs/rewrite
Easy and light templates renderer with HTML (XML) support based on @prostojs/parser.
- Write syntactically valid templates (js, yaml, ...)
- Use vue-like syntax for html-templates
- Mix html and text modes at single template
Install
npm:
npm install @prostojs/rewrite
Via CDN:
<script src="https://unpkg.com/@prostojs/tree"></script>
<script src="https://unpkg.com/@prostojs/parser"></script>
<script src="https://unpkg.com/@prostojs/rewrite"></script>
Usage
It's possible to point the ProstoRewrite
to a file or a directory. It's also possible to rewrite any string using rewriters directly.
const { ProstoRewrite } = require('@prostojs/rewrite')
// or import module
// import { ProstoRewrite } from '@prostojs/rewrite'
const rw = new ProstoRewrite()
main()
async function main() {
const context = { a: 1 } // context object for templates interpolation
// rewrite a single file
const renderedContent = await rw.rewriteFile({
// required:
input: 'path/to/file/filename.js',
// optional:
output: 'path/to/rewrite/filename.js',
mode: 'auto', // text | html | auto
}, context)
// rewrite files in directory
await rw.rewriteDir({
// required:
baseDir: 'path/to/files',
// optional:
include: ['*.{js,html}'], // glob pattern to include
exclude: ['*.svg'], // glob pattern to exclude
output: 'output/path',
mode: 'auto', // text | html | auto
onFile: (path, output) => {
console.log('Result for file ' + path + '\n' + output)
}
}, context)
}
Templates
TEXT
The text template is good for any text source (js, ts, yaml, ...).
To keep the source files syntactically valid all the control flow blocks
must be written in comment //
or #
.
Text template consists of:
- operation blocks (
//=IF(...)
), - directives (
//!@ignore-next-line
), - reveal comments (
//: const a = 1
), - string expressions (
conat a = {{ scopedValue }}
).
Operation blocks* :
Key | Example | Description
---|---|---
IF
|//=IF(condition)
or #=IF(condition)
|WIll add the lines below only if the condition
returns true
. The condition
must be a valid javascript and can use context
vars.
ELSEIF
|//=ELSEIF(condition)
or #=ELSEIF(condition)
|WIll add the lines below only if the condition
returns true
. The condition
must be a valid javascript and can use context
vars. Must be used after IF
or ELSEIF
operation block.
ELSE
|//=ELSE
or #=ELSE
|WIll add the lines below only if the previous conditions didn't match. Must be used after IF
or ELSEIF
operation block.
ENDIF
|//=ENDIF
or #=ENDIF
|Ends the IF
blocks series.
FOR
|//=FOR(a of b)
or #=FOR(a of b)
|Will iterate through the loop. The a of b
part must be a valid javascript loop expression and can use context
vars.
ENDFOR
**|//=ENDFOR
or #=ENDFOR
|Ends the FOR
block.
*The operation block can have spaces before it and after it, but it must take only one line. All the operation blocks can start with #
as well as with //
**The operation block may have spaces between words e.g. ELSE IF
, END FOR
...
Directives:
Key|Example|Description
---|---|---
ignore-next-line
|//!@ignore-next-line
or #!@ignore-next-line
|Instruct the processor to ignore (not interpolate) the next line of the source. The next line will be copied without any change.
html-mode-on
|//!@html-mode-on
or #!@html-mode-on
|Instructs the processor (only if it is mixed rewriter) to start using HTML mode for the futher lines of the source.
html-mode-off
|//!@html-mode-off
or #!@html-mode-off
|Instructs the processor (only if it is mixed rewriter) to stop using HTML mode for the futher lines of the source and return to the text mode.
Reveal comments: the line prefixed with reveal comment
prefix will be rendered as uncommented line. Use this when needed to keep the source file syntatically valid.
String expressions: vue-like string expressions {{ scopedValue }}
accepts valid javascript code and must interpolate into a string
or anything that can be casted to string
. The expression can access the context vars.
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
Will be rewritten with context = { a: 1, b: 1, c: 2, d: 2, items: [1, 2] }
:
let myVar = 1
const item1 = '1' // reveal comment
myVar += 2
const item2 = '2' // reveal comment
myVar += 2
const myVar2 = 2
HTML
The html template is good for XML-like sources (html, xml, svg, ...).
By default html template uses vue-like syntax.
It supports:
v-for="..."
: attribute for loop the node. Expression must be a valid javascript loop expression.v-if="..."
: attribute for conditional rendering of the node. Expression must be a valid javascript condition expression.v-else-if="..."
: attribute for conditional rendering of the node. Expression must be a valid javascript condition expression.v-else
: attribute for conditional rendering of the node.:<attr_name>="..."
: expression as attribute value. Expression must be a valid javascript code that returns string/boolean or anything that casts to a string/boolean. If the expression result is type of boolean, the attribute will have no value and will be hidden if the result equalsfalse
.{{ ... }}
: vue-like string expressions{{ scopedValue }}
accepts valid javascript code and must interpolate into astring
or anything that can be casted tostring
. The expression can access the context vars.
TEXT & HTML
By default ProstoRewrite
supports both: text and html modes at the same time. The default mode is controled by the options.
If you want to switch to html mode in the middle of the text source you can use the following instruction:
//!@html-mode-on
or #!@html-mode-on
And to switch back use:
//!@html-mode-off
or #!@html-mode-off
If you want to switch to text mode in the middle of the html source you can use the following instruction:
<!--!@text-mode-on-->
And to switch back use:
<!--!@text-mode-off-->
Examples:
Text template with embedded html processor:
const a = 'test'
//=IF (condition)
const html = `
//!@ html-mode-on
<div v-for='item of items'>
<span>{{ item }}</span>
</div>
//!@ html-mode-off
`
//=END IF
Html template with embedded text processor:
<div v-if="condition">
<!--!@ text-mode-on -->
const a = 'b'
#=IF (condition)
console.log(a)
#=ENDIF
<!--!@ text-mode-off -->
</div>
Options
Options object is totally optional. The example below demonstrates 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',
],
html: {
exprDelimeters: ['{{', '}}'],
attrExpression: ':',
blockOperation: 'v-',
directive: '!@',
voidTags: [
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
],
textTags: ['script', 'style'],
},
text: {
exprDelimeters: ['{{', '}}'],
blockOperation: '=',
revealLine: ':',
directive: '!@',
},
})
Option | Type | Description
---|---|---
defaultMode | 'text'
| 'html'
| 'auto'
| Determines the type of template processor used for templates. When auto
it will use htmlPattern
and textPattern
options to decide on which processor to use.
debug | boolean
| Pushes debug messages to console when true
.
htmlPattern | string[]
| Glob pattern that defines files to be processed by html
processor for defaultMode
= 'auto'
mode.
textPattern | string[]
| Glob pattern that defines files to be processed by text
processor for defaultMode
= 'auto'
mode.
text | object
| Options for text
processor.
text.exprDelimeters | [string, string]
| Defines the delimeters for string expressions e.g. {{ n.toLowerCase() }}
. Default: ['{{', '}}']
.
text.blockOperation | string
| Defines the prefix for block operations (e.g. for
, if
, ...). Default: '='
.
text.revealLine | string
| Defines the prefix for reveal commented line. Default: ':'
.
text.directive | string
| Defines the prefix for directives (e.g. ignore-next-line
, html-mode-on
, ...). Default: '!@'
.
html | object
| Options for html
processor.
html.exprDelimeters | [string, string]
| Defines the delimeters for string expressions e.g. {{ n.toLowerCase() }}
. Default: ['{{', '}}']
.
html.attrExpression | string
| Defines the prefix for attributes that have to be interpolated. Default: ':'
.
html.blockOperation | string
| Defines the prefix for block-operations attributes (e.g. for
, if
, ...). Default: 'v-'
.
html.directive | string
| Defines the prefix for directives (e.g. text-mode-on
, ...). Default: '!@'
.
html.voidTags | string[]
| List of void tags (without closing tags and without innerText)
html.textTags | string[]
| List of text tags (that contain text and no other tags)
Rewriters
Instance of ProstoRewrite
ships 3 versions of rewriters:
- text
- html
- mixed
const rw = new ProstoRewrite()
// 3 flavors:
const trw = rw.textRewriter // rewriter that can parse only text
const hrw = rw.htmlRewriter // rewriter that can parse only html
const mrw = rw.mixedRewriter // rewriter that can parse both
// source for text rewriter:
const source = '//=IF (a === 1)\nconst a = 1'
const context = { a: 1 }
// each rewriter has the same interface:
trw.genRewriteCode(source) // returns rendered function source code
trw.genRewriteFunction(source) // returns rewrite-function
trw.printAsTree(source) // prints parsed source as a tree
trw.rewrite(source, context) // renders the source with context
If you're going to use one template file for multiple renders it makes sense to cache its rewrite-function:
// An abstract example of using cached rewrite-function
const rw = new ProstoRewrite()
const trw = rw.textRewriter
const rewriteFunc = trw.genRewriteFunction(source)
const result1 = rewriteFunc(context1)
const result2 = rewriteFunc(context2)
const result3 = rewriteFunc(context3)
Besides those there is one more String Expression Rewriter comes as a bonus.
It is can parse only string expressions and preserves the expression type if its source consists only from an expression. When the source has some surrounding string(s) it will cast the expression to string type.
This kind of rewriter may be usefull when working with some configuration files that can have expressions as properties values.
Usage example
import { getStringExpressionRewriter } from '@prostojs/rewrite'
const srw = getStringExpressionRewriter()
// the source has surrounding strings and the expression
// will be casted to string type as well:
console.log(srw.rewrite('before {{ a }} after', { a: 5 }))
// output:
// 'before 5 after'
// the source consists only of a single expression
// therefore its type will be preserved:
console.log(srw.rewrite('{{ a }}', { a: 5 }) === 5 )
// output:
// true
Configuraton example
Let's assume that we want a config that must store some path generation logic. We can not store function as we're supposed to store the config to DB. We can use a string expression like so:
import { getStringExpressionRewriter } from '@prostojs/rewrite'
const srw = getStringExpressionRewriter()
const config = {
path: 'some/path/{{ key.toLowerCase() }}.{{ type === \'javascript\' ? \'js\' : \'json\' }}'
}
const context = {
key: 'TEST',
type: 'javascript',
}
// caching the rewrite function for path
// to avoid parsing the path string every time
const pathFunc = srw.genRewriteFunction(config.path)
// rewriting the config path
const thePath = pathFunc(context)
console.log(thePath)
// output:
// 'some/path/test.js'