@prostojs/rewrite 0.1.0 → 0.2.0
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.
- package/README.md +356 -238
- package/dist/index.cjs +815 -3949
- package/dist/index.d.ts +14 -13
- package/dist/index.mjs +814 -3948
- package/package.json +81 -84
package/README.md
CHANGED
|
@@ -1,123 +1,193 @@
|
|
|
1
1
|
# @prostojs/rewrite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
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).
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
5
|
+
- Templates compile to cached JS functions — repeated renders are near-instant
|
|
6
|
+
- Under 27 KB bundled, zero runtime dependencies beyond `@prostojs/parser`
|
|
7
|
+
- Auto-detects text vs HTML mode by file extension; switch modes mid-file with directives
|
|
8
|
+
- Point at a directory to scaffold entire projects with a single call
|
|
8
9
|
|
|
9
10
|
## Install
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
```bash
|
|
13
|
+
npm install @prostojs/rewrite
|
|
14
|
+
# or
|
|
15
|
+
pnpm add @prostojs/rewrite
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { ProstoRewrite } from '@prostojs/rewrite'
|
|
22
|
+
|
|
23
|
+
const rw = new ProstoRewrite()
|
|
24
|
+
|
|
25
|
+
// Text mode — rewrite a source code template
|
|
26
|
+
const result = rw.textRewriter.rewrite(
|
|
27
|
+
`Hello {{ name }}! You have {{ count }} items.`,
|
|
28
|
+
{ name: 'World', count: 3 }
|
|
29
|
+
)
|
|
30
|
+
// => "Hello World! You have 3 items."
|
|
31
|
+
|
|
32
|
+
// HTML mode — rewrite markup
|
|
33
|
+
const html = rw.htmlRewriter.rewrite(
|
|
34
|
+
`<ul><li v-for="item of items">{{ item }}</li></ul>`,
|
|
35
|
+
{ items: ['apple', 'banana', 'cherry'] }
|
|
36
|
+
)
|
|
37
|
+
// => "<ul><li>apple</li>\n<li>banana</li>\n<li>cherry</li></ul>"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Text Mode
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
### Expressions
|
|
45
|
+
|
|
46
|
+
Use `{{ }}` delimiters (customizable) to interpolate any JavaScript expression:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
const greeting = '{{ salutation }} {{ name }}!'
|
|
50
|
+
const sum = {{ a + b }}
|
|
51
|
+
```
|
|
12
52
|
|
|
13
|
-
`
|
|
53
|
+
Context: `{ salutation: 'Hello', name: 'World', a: 2, b: 3 }`
|
|
14
54
|
|
|
15
|
-
|
|
55
|
+
Output:
|
|
16
56
|
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<script src="https://unpkg.com/@prostojs/rewrite"></script>
|
|
57
|
+
const greeting = 'Hello World!'
|
|
58
|
+
const sum = 5
|
|
20
59
|
```
|
|
21
60
|
|
|
22
|
-
|
|
61
|
+
### Conditional Blocks (IF / ELSE / ELSEIF)
|
|
23
62
|
|
|
24
|
-
|
|
63
|
+
```js
|
|
64
|
+
//=IF (env === 'production')
|
|
65
|
+
const API = 'https://api.prod.com'
|
|
66
|
+
//=ELSEIF (env === 'staging')
|
|
67
|
+
const API = 'https://api.staging.com'
|
|
68
|
+
//=ELSE
|
|
69
|
+
const API = 'http://localhost:3000'
|
|
70
|
+
//=ENDIF
|
|
71
|
+
```
|
|
25
72
|
|
|
73
|
+
Context: `{ env: 'staging' }` produces:
|
|
26
74
|
```js
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
// import { ProstoRewrite } from '@prostojs/rewrite'
|
|
75
|
+
const API = 'https://api.staging.com'
|
|
76
|
+
```
|
|
30
77
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
78
|
+
The comment prefix (`//`, `#`, `--`, etc.) is automatically detected from the line. Use whatever comment style fits your language:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
#=IF (includeRedis)
|
|
82
|
+
redis:
|
|
83
|
+
image: redis:alpine
|
|
84
|
+
#=ENDIF
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Operation blocks reference:**
|
|
88
|
+
|
|
89
|
+
| Key | Example | Description |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `IF` | `//=IF(condition)` | Includes lines below only if `condition` is truthy. |
|
|
92
|
+
| `ELSEIF` | `//=ELSEIF(condition)` | Alternative branch. Must follow `IF` or `ELSEIF`. |
|
|
93
|
+
| `ELSE` | `//=ELSE` | Fallback branch. Must follow `IF` or `ELSEIF`. |
|
|
94
|
+
| `ENDIF` | `//=ENDIF` | Closes the `IF` block chain. |
|
|
95
|
+
| `FOR` | `//=FOR(a of b)` | Iterates with any valid JS loop expression. |
|
|
96
|
+
| `ENDFOR` | `//=ENDFOR` | Closes the `FOR` block. |
|
|
97
|
+
|
|
98
|
+
> Operation blocks can have spaces between words: `ELSE IF`, `END FOR`, etc.
|
|
99
|
+
> All blocks work with `#` and `//` comment prefixes.
|
|
100
|
+
|
|
101
|
+
### Loops (FOR / ENDFOR)
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
//=FOR (const route of routes)
|
|
105
|
+
app.get('{{ route.path }}', {{ route.handler }})
|
|
106
|
+
//=ENDFOR
|
|
59
107
|
```
|
|
60
108
|
|
|
61
|
-
|
|
109
|
+
Context: `{ routes: [{ path: '/api', handler: 'apiHandler' }, { path: '/', handler: 'indexHandler' }] }`
|
|
62
110
|
|
|
63
|
-
|
|
111
|
+
Output:
|
|
112
|
+
```js
|
|
113
|
+
app.get('/api', apiHandler)
|
|
114
|
+
app.get('/', indexHandler)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Nesting
|
|
118
|
+
|
|
119
|
+
Blocks nest freely:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
//=IF (features.auth)
|
|
123
|
+
//=FOR (const provider of authProviders)
|
|
124
|
+
//=IF (provider !== 'local')
|
|
125
|
+
import {{ provider }}Strategy from './strategies/{{ provider }}'
|
|
126
|
+
//=ENDIF
|
|
127
|
+
//=ENDFOR
|
|
128
|
+
//=ENDIF
|
|
129
|
+
```
|
|
64
130
|
|
|
65
|
-
|
|
131
|
+
### Reveal Lines
|
|
66
132
|
|
|
67
|
-
|
|
68
|
-
must be written in comment `//` or `#`.
|
|
133
|
+
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:
|
|
69
134
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
135
|
+
```js
|
|
136
|
+
//=IF (useTypescript)
|
|
137
|
+
//: import type { Config } from './types'
|
|
138
|
+
//=ENDIF
|
|
139
|
+
const config = {}
|
|
140
|
+
```
|
|
75
141
|
|
|
76
|
-
|
|
77
|
-
Key | Example | Description
|
|
78
|
-
---|---|---
|
|
79
|
-
`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.
|
|
80
|
-
`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.
|
|
81
|
-
`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.
|
|
82
|
-
`ENDIF`**|`//=ENDIF` or `#=ENDIF`|Ends the `IF` blocks series.
|
|
83
|
-
`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.
|
|
84
|
-
`ENDFOR`**|`//=ENDFOR` or `#=ENDFOR`|Ends the `FOR` block.
|
|
142
|
+
Context: `{ useTypescript: true }`
|
|
85
143
|
|
|
86
|
-
|
|
144
|
+
Output:
|
|
145
|
+
```js
|
|
146
|
+
import type { Config } from './types'
|
|
147
|
+
const config = {}
|
|
148
|
+
```
|
|
87
149
|
|
|
88
|
-
|
|
150
|
+
Reveal lines can contain expressions too:
|
|
89
151
|
|
|
90
|
-
|
|
152
|
+
```js
|
|
153
|
+
//: const {{ varName }} = {{ JSON.stringify(defaultValue) }}
|
|
154
|
+
```
|
|
91
155
|
|
|
92
|
-
|
|
93
|
-
---|---|---
|
|
94
|
-
`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.
|
|
95
|
-
`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.
|
|
96
|
-
`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.
|
|
156
|
+
### Directives
|
|
97
157
|
|
|
98
|
-
|
|
158
|
+
| Directive | Example | Description |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `ignore-next-line` | `//!@ignore-next-line` | The next line passes through as-is, without interpolation. |
|
|
161
|
+
| `html-mode-on` | `//!@html-mode-on` | Switch to HTML mode (mixed rewriter only). |
|
|
162
|
+
| `html-mode-off` | `//!@html-mode-off` | Switch back to text mode. |
|
|
99
163
|
|
|
100
|
-
|
|
164
|
+
```js
|
|
165
|
+
//!@ ignore-next-line
|
|
166
|
+
const template = '{{ this is not interpolated }}'
|
|
167
|
+
const value = '{{ this IS interpolated }}'
|
|
168
|
+
```
|
|
101
169
|
|
|
102
|
-
|
|
170
|
+
### Full Text Mode Example
|
|
103
171
|
|
|
104
172
|
Source:
|
|
105
173
|
```js
|
|
106
174
|
let myVar = 1
|
|
107
175
|
//=IF (a === b)
|
|
108
|
-
|
|
176
|
+
//=FOR (const i of items)
|
|
109
177
|
//: const item{{ i }} = '{{ i }}' // reveal comment
|
|
110
|
-
|
|
178
|
+
//=IF (c === d)
|
|
111
179
|
myVar += 2
|
|
112
|
-
|
|
180
|
+
//=ELSE
|
|
113
181
|
myVar -= 4
|
|
114
|
-
|
|
115
|
-
|
|
182
|
+
//=ENDIF
|
|
183
|
+
//=END FOR
|
|
116
184
|
//=END IF
|
|
117
185
|
const myVar2 = 2
|
|
118
186
|
```
|
|
119
187
|
|
|
120
|
-
|
|
188
|
+
Context: `{ a: 1, b: 1, c: 2, d: 2, items: [1, 2] }`
|
|
189
|
+
|
|
190
|
+
Output:
|
|
121
191
|
```js
|
|
122
192
|
let myVar = 1
|
|
123
193
|
const item1 = '1' // reveal comment
|
|
@@ -127,46 +197,96 @@ myVar += 2
|
|
|
127
197
|
const myVar2 = 2
|
|
128
198
|
```
|
|
129
199
|
|
|
130
|
-
|
|
200
|
+
## HTML Mode
|
|
201
|
+
|
|
202
|
+
HTML mode parses markup structure and supports Vue-inspired directives for conditionals, loops, and dynamic attributes.
|
|
203
|
+
|
|
204
|
+
### Expressions
|
|
205
|
+
|
|
206
|
+
Interpolation works the same as text mode:
|
|
207
|
+
|
|
208
|
+
```html
|
|
209
|
+
<h1>{{ title }}</h1>
|
|
210
|
+
<p>Welcome, {{ user.name }}!</p>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Conditional Rendering (v-if / v-else-if / v-else)
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<div v-if="user.isAdmin">
|
|
217
|
+
<h2>Admin Panel</h2>
|
|
218
|
+
</div>
|
|
219
|
+
<div v-else-if="user.isEditor">
|
|
220
|
+
<h2>Editor Dashboard</h2>
|
|
221
|
+
</div>
|
|
222
|
+
<div v-else>
|
|
223
|
+
<h2>Welcome, {{ user.name }}</h2>
|
|
224
|
+
</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Only the matching block is rendered. The `v-if` / `v-else-if` / `v-else` chain must be on sibling elements.
|
|
228
|
+
|
|
229
|
+
### Loops (v-for)
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
<ul>
|
|
233
|
+
<li v-for="item of items">{{ item }}</li>
|
|
234
|
+
</ul>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Any valid JavaScript loop expression works:
|
|
131
238
|
|
|
132
|
-
|
|
239
|
+
```html
|
|
240
|
+
<tr v-for="let i = 0; i < rows.length; i++">
|
|
241
|
+
<td>{{ rows[i].name }}</td>
|
|
242
|
+
</tr>
|
|
243
|
+
```
|
|
133
244
|
|
|
134
|
-
|
|
245
|
+
`v-for` and `v-if` can be combined on the same element:
|
|
135
246
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `v-else-if="..."`: attribute for conditional rendering of the node. Expression must be a valid javascript condition expression.
|
|
140
|
-
- `v-else`: attribute for conditional rendering of the node.
|
|
141
|
-
- `:<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 equals `false`.
|
|
142
|
-
- `{{ ... }}`: 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.
|
|
247
|
+
```html
|
|
248
|
+
<li v-for="item of items" v-if="item.visible">{{ item.name }}</li>
|
|
249
|
+
```
|
|
143
250
|
|
|
144
|
-
###
|
|
251
|
+
### Dynamic Attributes
|
|
145
252
|
|
|
146
|
-
|
|
253
|
+
Prefix any attribute with `:` to evaluate it as a JavaScript expression:
|
|
147
254
|
|
|
148
|
-
|
|
255
|
+
```html
|
|
256
|
+
<img :src="baseUrl + '/images/' + image.file" :alt="image.title">
|
|
257
|
+
<a :href="link.url" :target="link.external ? '_blank' : undefined">{{ link.text }}</a>
|
|
258
|
+
```
|
|
149
259
|
|
|
150
|
-
|
|
260
|
+
**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:
|
|
151
261
|
|
|
152
|
-
|
|
262
|
+
```html
|
|
263
|
+
<input type="text" :disabled="isLocked" :required="isRequired">
|
|
264
|
+
```
|
|
153
265
|
|
|
154
|
-
|
|
266
|
+
Context: `{ isLocked: true, isRequired: false }`
|
|
155
267
|
|
|
156
|
-
|
|
268
|
+
Output:
|
|
269
|
+
```html
|
|
270
|
+
<input type="text" disabled>
|
|
271
|
+
```
|
|
157
272
|
|
|
158
|
-
|
|
273
|
+
### Switch to Text Mode
|
|
159
274
|
|
|
160
|
-
|
|
275
|
+
Inside an HTML file, switch to text-mode parsing with HTML comment directives:
|
|
161
276
|
|
|
162
|
-
|
|
277
|
+
```html
|
|
278
|
+
<script>
|
|
279
|
+
<!--!@ text-mode-on -->
|
|
280
|
+
//=FOR (const key of Object.keys(config))
|
|
281
|
+
window.{{ key }} = {{ JSON.stringify(config[key]) }}
|
|
282
|
+
//=ENDFOR
|
|
283
|
+
<!--!@ text-mode-off -->
|
|
284
|
+
</script>
|
|
285
|
+
```
|
|
163
286
|
|
|
164
|
-
|
|
287
|
+
And the reverse — switch to HTML inside a text file:
|
|
165
288
|
|
|
166
|
-
Text template with embedded html processor:
|
|
167
289
|
```js
|
|
168
|
-
const a = 'test'
|
|
169
|
-
//=IF (condition)
|
|
170
290
|
const html = `
|
|
171
291
|
//!@ html-mode-on
|
|
172
292
|
<div v-for='item of items'>
|
|
@@ -174,176 +294,174 @@ const html = `
|
|
|
174
294
|
</div>
|
|
175
295
|
//!@ html-mode-off
|
|
176
296
|
`
|
|
177
|
-
//=END IF
|
|
178
297
|
```
|
|
179
298
|
|
|
180
|
-
|
|
181
|
-
```html
|
|
182
|
-
<div v-if="condition">
|
|
183
|
-
<!--!@ text-mode-on -->
|
|
184
|
-
const a = 'b'
|
|
185
|
-
#=IF (condition)
|
|
186
|
-
console.log(a)
|
|
187
|
-
#=ENDIF
|
|
188
|
-
<!--!@ text-mode-off -->
|
|
189
|
-
</div>
|
|
190
|
-
```
|
|
299
|
+
## File and Directory Rewriting
|
|
191
300
|
|
|
192
|
-
|
|
301
|
+
The main use case: scaffold entire project templates.
|
|
193
302
|
|
|
194
|
-
|
|
303
|
+
### Rewrite a Single File
|
|
195
304
|
|
|
196
305
|
```js
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
text: {
|
|
233
|
-
exprDelimeters: ['{{', '}}'],
|
|
234
|
-
blockOperation: '=',
|
|
235
|
-
revealLine: ':',
|
|
236
|
-
directive: '!@',
|
|
237
|
-
},
|
|
238
|
-
})
|
|
306
|
+
import { ProstoRewrite } from '@prostojs/rewrite'
|
|
307
|
+
|
|
308
|
+
const rw = new ProstoRewrite()
|
|
309
|
+
|
|
310
|
+
// Returns rendered content; optionally writes to output path
|
|
311
|
+
const content = await rw.rewriteFile(
|
|
312
|
+
{
|
|
313
|
+
input: 'path/to/template.js',
|
|
314
|
+
output: 'path/to/output.js', // optional
|
|
315
|
+
mode: 'auto', // optional: 'text' | 'html' | 'auto'
|
|
316
|
+
},
|
|
317
|
+
{ name: 'my-app', version: '1.0.0' }
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Rewrite an Entire Directory
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
await rw.rewriteDir(
|
|
325
|
+
{
|
|
326
|
+
baseDir: './template',
|
|
327
|
+
output: './my-new-project',
|
|
328
|
+
include: ['**/*'], // optional glob patterns
|
|
329
|
+
exclude: ['node_modules/**'], // optional glob patterns
|
|
330
|
+
renameFile: (name) => name.replace('__name__', 'my-app'), // optional
|
|
331
|
+
onFile: (path, output) => console.log(`Wrote: ${path}`), // optional callback
|
|
332
|
+
mode: 'auto', // optional
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'my-app',
|
|
336
|
+
description: 'My awesome app',
|
|
337
|
+
useTypescript: true,
|
|
338
|
+
features: ['auth', 'api', 'database'],
|
|
339
|
+
}
|
|
340
|
+
)
|
|
239
341
|
```
|
|
240
342
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
textPattern | `string[]` | Glob pattern that defines files to be processed by `text` processor for `defaultMode` = `'auto'` mode.
|
|
247
|
-
text | `object` | Options for `text` processor.
|
|
248
|
-
text.exprDelimeters | `[string, string]` | Defines the delimeters for string expressions e.g. `{{ n.toLowerCase() }}`. Default: `['{{', '}}']`.
|
|
249
|
-
text.blockOperation | `string` | Defines the prefix for block operations (e.g. `for`, `if`, ...). Default: `'='`.
|
|
250
|
-
text.revealLine | `string` | Defines the prefix for reveal commented line. Default: `':'`.
|
|
251
|
-
text.directive | `string` | Defines the prefix for directives (e.g. `ignore-next-line`, `html-mode-on`, ...). Default: `'!@'`.
|
|
252
|
-
html | `object` | Options for `html` processor.
|
|
253
|
-
html.exprDelimeters | `[string, string]` | Defines the delimeters for string expressions e.g. `{{ n.toLowerCase() }}`. Default: `['{{', '}}']`.
|
|
254
|
-
html.attrExpression | `string` | Defines the prefix for attributes that have to be interpolated. Default: `':'`.
|
|
255
|
-
html.blockOperation | `string` | Defines the prefix for block-operations attributes (e.g. `for`, `if`, ...). Default: `'v-'`.
|
|
256
|
-
html.directive | `string` | Defines the prefix for directives (e.g. `text-mode-on`, ...). Default: `'!@'`.
|
|
257
|
-
html.voidTags | `string[]` | List of void tags (without closing tags and without innerText)
|
|
258
|
-
html.textTags | `string[]` | List of text tags (that contain text and no other tags)
|
|
343
|
+
**Mode auto-detection** picks the right parser based on file extension:
|
|
344
|
+
- **HTML mode:** `*.html`, `*.xhtml`, `*.xml`, `*.svg`
|
|
345
|
+
- **Text mode:** `*.js`, `*.ts`, `*.jsx`, `*.tsx`, `*.json`, `*.yml`, `*.yaml`, `*.md`, `*.txt`, `*.ini`, `Dockerfile`, `*config`, `.gitignore`
|
|
346
|
+
|
|
347
|
+
Files that don't match any pattern are copied as-is.
|
|
259
348
|
|
|
260
349
|
## Rewriters
|
|
261
350
|
|
|
262
|
-
|
|
263
|
-
- text
|
|
264
|
-
- html
|
|
265
|
-
- mixed
|
|
351
|
+
`ProstoRewrite` provides three rewriter flavors:
|
|
266
352
|
|
|
267
353
|
```js
|
|
268
354
|
const rw = new ProstoRewrite()
|
|
269
|
-
// 3 flavors:
|
|
270
|
-
const trw = rw.textRewriter // rewriter that can parse only text
|
|
271
|
-
const hrw = rw.htmlRewriter // rewriter that can parse only html
|
|
272
|
-
const mrw = rw.mixedRewriter // rewriter that can parse both
|
|
273
|
-
// source for text rewriter:
|
|
274
|
-
const source = '//=IF (a === 1)\nconst a = 1'
|
|
275
|
-
const context = { a: 1 }
|
|
276
|
-
// each rewriter has the same interface:
|
|
277
|
-
trw.genRewriteCode(source) // returns rendered function source code
|
|
278
|
-
trw.genRewriteFunction(source) // returns rewrite-function
|
|
279
|
-
trw.printAsTree(source) // prints parsed source as a tree
|
|
280
|
-
trw.rewrite(source, context) // renders the source with context
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
If you're going to use one template file for multiple renders it makes sense to cache its rewrite-function:
|
|
284
355
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const result1 = rewriteFunc(context1)
|
|
291
|
-
const result2 = rewriteFunc(context2)
|
|
292
|
-
const result3 = rewriteFunc(context3)
|
|
356
|
+
const trw = rw.textRewriter // text only
|
|
357
|
+
const hrw = rw.htmlRewriter // html only
|
|
358
|
+
const mrw = rw.mixedRewriter // both (supports mode-switching directives)
|
|
359
|
+
// mrw.text — text rewriter with html-mode-on support
|
|
360
|
+
// mrw.html — html rewriter with text-mode-on support
|
|
293
361
|
```
|
|
294
362
|
|
|
295
|
-
|
|
363
|
+
Each rewriter has the same interface:
|
|
296
364
|
|
|
297
|
-
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.
|
|
298
|
-
|
|
299
|
-
This kind of rewriter may be usefull when working with some configuration files that can have expressions as properties values.
|
|
300
|
-
|
|
301
|
-
**Usage example**
|
|
302
365
|
```js
|
|
303
|
-
|
|
366
|
+
// Generate the compiled JavaScript source (for inspection/debugging)
|
|
367
|
+
trw.genRewriteCode(source)
|
|
304
368
|
|
|
305
|
-
|
|
369
|
+
// Compile once, execute many times — ideal for repeated renders
|
|
370
|
+
const render = trw.genRewriteFunction(source)
|
|
371
|
+
const out1 = render({ name: 'Alice' })
|
|
372
|
+
const out2 = render({ name: 'Bob' })
|
|
306
373
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
console.log(srw.rewrite('before {{ a }} after', { a: 5 }))
|
|
310
|
-
// output:
|
|
311
|
-
// 'before 5 after'
|
|
374
|
+
// One-shot: parse + compile + execute
|
|
375
|
+
trw.rewrite(source, context)
|
|
312
376
|
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
console.log(srw.rewrite('{{ a }}', { a: 5 }) === 5 )
|
|
316
|
-
// output:
|
|
317
|
-
// true
|
|
377
|
+
// Debug: print the parse tree to console
|
|
378
|
+
trw.printAsTree(source)
|
|
318
379
|
```
|
|
319
380
|
|
|
320
|
-
|
|
381
|
+
### String Expression Rewriter
|
|
321
382
|
|
|
322
|
-
|
|
383
|
+
For simple interpolation without block operations (config values, file paths):
|
|
323
384
|
|
|
324
385
|
```js
|
|
325
386
|
import { getStringExpressionRewriter } from '@prostojs/rewrite'
|
|
326
387
|
|
|
327
388
|
const srw = getStringExpressionRewriter()
|
|
328
389
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
390
|
+
// Mixed string: always returns string
|
|
391
|
+
srw.rewrite('Hello {{ name }}, age {{ age }}', { name: 'World', age: 25 })
|
|
392
|
+
// => "Hello World, age 25"
|
|
332
393
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
394
|
+
// Single expression: preserves the original type
|
|
395
|
+
srw.rewrite('{{ count }}', { count: 42 })
|
|
396
|
+
// => 42 (number, not string)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
This is useful for configuration files where property values can contain expressions:
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
const config = {
|
|
403
|
+
path: "some/path/{{ key.toLowerCase() }}.{{ type === 'javascript' ? 'js' : 'json' }}",
|
|
336
404
|
}
|
|
337
405
|
|
|
338
|
-
// caching the rewrite function for path
|
|
339
|
-
// to avoid parsing the path string every time
|
|
340
406
|
const pathFunc = srw.genRewriteFunction(config.path)
|
|
407
|
+
pathFunc({ key: 'TEST', type: 'javascript' })
|
|
408
|
+
// => "some/path/test.js"
|
|
409
|
+
```
|
|
341
410
|
|
|
342
|
-
|
|
343
|
-
const thePath = pathFunc(context)
|
|
411
|
+
## Options
|
|
344
412
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
413
|
+
All options are optional. The example below shows the default values:
|
|
414
|
+
|
|
415
|
+
```js
|
|
416
|
+
const rw = new ProstoRewrite({
|
|
417
|
+
defaultMode: 'auto', // 'text' | 'html' | 'auto'
|
|
418
|
+
debug: false,
|
|
419
|
+
|
|
420
|
+
htmlPattern: ['*.{html,xhtml,xml,svg}'],
|
|
421
|
+
textPattern: [
|
|
422
|
+
'*.{js,jsx,ts,tsx,txt,json,yml,yaml,md,ini}',
|
|
423
|
+
'Dockerfile',
|
|
424
|
+
'*config',
|
|
425
|
+
'.gitignore',
|
|
426
|
+
],
|
|
427
|
+
|
|
428
|
+
text: {
|
|
429
|
+
exprDelimiters: ['{{', '}}'],
|
|
430
|
+
blockOperation: '=',
|
|
431
|
+
revealLine: ':',
|
|
432
|
+
directive: '!@',
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
html: {
|
|
436
|
+
exprDelimiters: ['{{', '}}'],
|
|
437
|
+
blockOperation: 'v-',
|
|
438
|
+
attrExpression: ':',
|
|
439
|
+
directive: '!@',
|
|
440
|
+
voidTags: ['area', 'base', 'br', 'col', 'command', 'embed', 'hr',
|
|
441
|
+
'img', 'input', 'keygen', 'link', 'meta', 'param',
|
|
442
|
+
'source', 'track', 'wbr'],
|
|
443
|
+
textTags: ['script', 'style'],
|
|
444
|
+
},
|
|
445
|
+
})
|
|
446
|
+
```
|
|
348
447
|
|
|
349
|
-
|
|
448
|
+
| Option | Type | Description |
|
|
449
|
+
|---|---|---|
|
|
450
|
+
| `defaultMode` | `'text'` \| `'html'` \| `'auto'` | Template processor selection. `auto` uses file patterns to decide. |
|
|
451
|
+
| `debug` | `boolean` | Print debug parse trees to console. |
|
|
452
|
+
| `htmlPattern` | `string[]` | Glob patterns for files processed by HTML parser. |
|
|
453
|
+
| `textPattern` | `string[]` | Glob patterns for files processed by text parser. |
|
|
454
|
+
| `text.exprDelimiters` | `[string, string]` | Expression delimiters. Default: `['{{', '}}']` |
|
|
455
|
+
| `text.blockOperation` | `string` | Prefix for block operations (`IF`, `FOR`, ...). Default: `'='` |
|
|
456
|
+
| `text.revealLine` | `string` | Prefix for reveal lines. Default: `':'` |
|
|
457
|
+
| `text.directive` | `string` | Prefix for directives (`ignore-next-line`, ...). Default: `'!@'` |
|
|
458
|
+
| `html.exprDelimiters` | `[string, string]` | Expression delimiters. Default: `['{{', '}}']` |
|
|
459
|
+
| `html.blockOperation` | `string` | Prefix for block attributes (`if`, `for`, ...). Default: `'v-'` |
|
|
460
|
+
| `html.attrExpression` | `string` | Prefix for expression attributes. Default: `':'` |
|
|
461
|
+
| `html.directive` | `string` | Prefix for directives (`text-mode-on`, ...). Default: `'!@'` |
|
|
462
|
+
| `html.voidTags` | `string[]` | Self-closing HTML tags. |
|
|
463
|
+
| `html.textTags` | `string[]` | Tags with text-only content (no child tag parsing). |
|
|
464
|
+
|
|
465
|
+
## License
|
|
466
|
+
|
|
467
|
+
MIT
|