blogger-hbs 0.0.2 → 0.0.4
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 +120 -0
- package/dist/cli/index.js +6 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/compiler/ast/index.d.ts +2 -2
- package/dist/compiler/ast/index.js +2 -2
- package/dist/compiler/ast/partials.d.ts +31 -0
- package/dist/compiler/ast/partials.js +82 -4
- package/dist/compiler/ast/partials.js.map +1 -1
- package/dist/compiler/ast/types.d.ts +9 -3
- package/dist/compiler/ast/walk.js +2 -0
- package/dist/compiler/ast/walk.js.map +1 -1
- package/dist/compiler/compile.d.ts +27 -11
- package/dist/compiler/compile.js +45 -27
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/emit.d.ts +8 -2
- package/dist/compiler/emit.js +24 -2
- package/dist/compiler/emit.js.map +1 -1
- package/dist/compiler/index.d.ts +2 -2
- package/dist/compiler/options.d.ts +31 -5
- package/dist/compiler/options.js +56 -46
- package/dist/compiler/options.js.map +1 -1
- package/dist/compiler/plugins.d.ts +36 -8
- package/dist/compiler/plugins.js +187 -45
- package/dist/compiler/plugins.js.map +1 -1
- package/dist/compiler/runtimes.d.ts +30 -0
- package/dist/compiler/runtimes.js +140 -0
- package/dist/compiler/runtimes.js.map +1 -0
- package/dist/compiler/scan.js +0 -1
- package/dist/compiler/scan.js.map +1 -1
- package/dist/compiler/standalone.js +0 -1
- package/dist/compiler/standalone.js.map +1 -1
- package/dist/compiler/validate.js +0 -1
- package/dist/compiler/validate.js.map +1 -1
- package/dist/compiler/watch.js +27 -14
- package/dist/compiler/watch.js.map +1 -1
- package/dist/config.d.ts +0 -3
- package/dist/config.js +28 -44
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +7 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +3 -6
- package/dist/plugins/index.d.ts +136 -14
- package/dist/plugins/index.js +19 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/runtime/chunks.d.ts +8 -0
- package/dist/runtime/import.d.ts +6 -1
- package/dist/runtime/import.js +8 -3
- package/dist/runtime/import.js.map +1 -1
- package/dist/runtime/index.d.ts +6 -3
- package/dist/runtime/index.js +4 -2
- package/dist/runtime/render.d.ts +37 -5
- package/dist/runtime/render.js +104 -7
- package/dist/runtime/render.js.map +1 -1
- package/dist/utils/schema.d.ts +55 -0
- package/dist/utils/schema.js +101 -0
- package/dist/utils/schema.js.map +1 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -34,3 +34,123 @@ blogger-hbs --help # all the options
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
The same is available from JavaScript with `compileTemplate`, `watchTemplate` and `renderTemplate`.
|
|
37
|
+
|
|
38
|
+
## Rendering anywhere
|
|
39
|
+
|
|
40
|
+
The build writes `dist/compiled.js`, an ES module that imports nothing: the templates, scripts, styles and the runtimes of the plugins are all in it. `blogger-hbs/runtime` renders it in any JavaScript runtime (Node.js, Deno, Bun, Cloudflare Workers, a browser) without the compiler or Node.js.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// a Cloudflare Worker
|
|
44
|
+
import { createRenderer } from 'blogger-hbs/runtime';
|
|
45
|
+
import * as theme from './dist/compiled.js';
|
|
46
|
+
|
|
47
|
+
// set up once, render many times, each render on its own
|
|
48
|
+
const renderer = createRenderer(theme);
|
|
49
|
+
|
|
50
|
+
export default {
|
|
51
|
+
async fetch() {
|
|
52
|
+
const html = await renderer.render({ title: 'Blog' });
|
|
53
|
+
return new Response(html, { headers: { 'content-type': 'text/html' } });
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`createRenderer(theme, { plugins })` takes runtime plugins of the host rendering the theme, for example with data of the request in `prepare`. The helpers of the theme itself are in `compiled.js`, see [Helpers of the theme](#helpers-of-the-theme). `formatXml` and `validateXml` are in `blogger-hbs/runtime` as well. A compiled module is rendered by the version of `blogger-hbs` it is compiled with, `createRenderer` checks it.
|
|
59
|
+
|
|
60
|
+
## Helpers of the theme
|
|
61
|
+
|
|
62
|
+
Helpers are given by the runtime of a plugin, which is bundled into `compiled.js`: the theme renders with them wherever it is rendered. A plugin of the theme itself only needs a name and its runtime.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// blogger-hbs.config.ts
|
|
66
|
+
export default defineConfig({
|
|
67
|
+
plugins: [{ name: 'theme', runtime: { entry: './src/runtime.ts' } }],
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// src/runtime.ts
|
|
73
|
+
import { defineRuntimePlugin } from 'blogger-hbs/runtime';
|
|
74
|
+
|
|
75
|
+
export default defineRuntimePlugin((api) => ({
|
|
76
|
+
helpers: {
|
|
77
|
+
upper: (text: string) => text.toUpperCase(),
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Plugins
|
|
83
|
+
|
|
84
|
+
A compile plugin provides partials (`{{> name.partial}}`) and data (`{{@name.value}}`) when compiling, and can come with a runtime half for rendering.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { fileURLToPath } from 'node:url';
|
|
88
|
+
import type { CompilePlugin } from 'blogger-hbs';
|
|
89
|
+
|
|
90
|
+
export function myPlugin(options: { prefix: string }): CompilePlugin {
|
|
91
|
+
return {
|
|
92
|
+
name: 'my',
|
|
93
|
+
provide({ requests }, plugin) {
|
|
94
|
+
// every partial used, with where and how: `{{> my.badge size=16}}`
|
|
95
|
+
for (const { name, usages } of requests) {
|
|
96
|
+
for (const usage of usages) {
|
|
97
|
+
if ('color' in usage.hash) {
|
|
98
|
+
plugin.error(`'color' is not a prop of ${name}`, usage);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return Object.fromEntries(
|
|
103
|
+
requests.map(({ name }) => [name, `<span class='${name}'/>`]),
|
|
104
|
+
);
|
|
105
|
+
},
|
|
106
|
+
// bundled into compiled.js, it can not use Node.js modules
|
|
107
|
+
runtime: {
|
|
108
|
+
entry: fileURLToPath(new URL('./runtime.js', import.meta.url)),
|
|
109
|
+
options: { prefix: options.prefix },
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// runtime.ts, published as runtime.js
|
|
117
|
+
import { defineRuntimePlugin } from 'blogger-hbs/runtime';
|
|
118
|
+
|
|
119
|
+
export default defineRuntimePlugin((api, options: { prefix: string }) => ({
|
|
120
|
+
helpers: {
|
|
121
|
+
greet: (name: string) =>
|
|
122
|
+
new api.SafeString(`${options.prefix} ${api.escapeExpression(name)}`),
|
|
123
|
+
},
|
|
124
|
+
// before every render, can be async, read as `{{@my.posts}}`
|
|
125
|
+
async prepare(context) {
|
|
126
|
+
return { posts: [] };
|
|
127
|
+
},
|
|
128
|
+
// after every render
|
|
129
|
+
transform: (html) => html,
|
|
130
|
+
}));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Hook | When |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `buildStart(plugin)` | first, in every compilation, also each time when watching |
|
|
136
|
+
| `preprocess(templates, plugin)` | with every template, their ASTs can be changed |
|
|
137
|
+
| `provide({ partials, requests, templates }, plugin)` | with the partials used, again for those the provided ones use |
|
|
138
|
+
| `finalize({ provided, missing, requests, available }, plugin)` | once, for partials that need all the others |
|
|
139
|
+
| `data({ provided, available, templates }, plugin)` | once, the values are JSON |
|
|
140
|
+
| `buildEnd(plugin, error?)` | last, when the templates are compiled |
|
|
141
|
+
|
|
142
|
+
### Partials of a plugin
|
|
143
|
+
|
|
144
|
+
A plugin `my` provides its main partial and any names separated by single dots:
|
|
145
|
+
|
|
146
|
+
| In a template | Key provided |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `{{> my}}`, `{{#> my}}...{{/my}}` | `.` |
|
|
149
|
+
| `{{> my.badge}}` | `badge` |
|
|
150
|
+
| `{{> my.badge.small}}` | `badge.small` |
|
|
151
|
+
|
|
152
|
+
A name is checked as it is written, `{{> my.}}`, `{{> my..badge}}`, `{{> my.badge.}}` and `{{> my/badge}}` are errors where they are, and a theme can not define an inline partial with a name of a plugin, `{{#*inline "my.badge"}}`. Partials of no plugin are left to Handlebars.
|
|
153
|
+
|
|
154
|
+
Every hook gets the plugin context: `root`, `entry`, `mode`, `watch`, `resolve(specifier, importer?)` with the aliases, `addWatchFile(file)`, `warn(message, loc?)` and `error(message, loc?)`. `enforce: 'pre'` or `'post'` runs a plugin before or after the others.
|
|
155
|
+
|
|
156
|
+
The runtime halves of all the plugins are bundled together, a module they share (a dependency of two plugins) is in `compiled.js` once and is the same instance for both. The runtime half is set up with `api` (`SafeString`, `escapeExpression`, `createFrame`, `HelperError`) and the options, which are JSON. Its helpers also work with `renderTemplate`, `prepare` and `transform` need `createRenderer`.
|
package/dist/cli/index.js
CHANGED
|
@@ -2,13 +2,10 @@ import { CompileError } from "../compiler/errors.js";
|
|
|
2
2
|
import { didYouMean } from "../utils/suggest.js";
|
|
3
3
|
import { COMPILE_OPTION_KEYS, assertCompileOptions, assertDirectories, assertEntry, resolvePaths } from "../compiler/options.js";
|
|
4
4
|
import { formatXml, validateXml } from "../xml/format.js";
|
|
5
|
-
import "../xml/index.js";
|
|
6
5
|
import { compileTemplate } from "../compiler/compile.js";
|
|
7
6
|
import { watchTemplate } from "../compiler/watch.js";
|
|
8
|
-
import "../compiler/index.js";
|
|
9
7
|
import { loadConfig } from "../config.js";
|
|
10
|
-
import {
|
|
11
|
-
import "../runtime/index.js";
|
|
8
|
+
import { createRenderer } from "../runtime/render.js";
|
|
12
9
|
import * as path from "node:path";
|
|
13
10
|
import * as fs from "node:fs/promises";
|
|
14
11
|
import { createRequire } from "node:module";
|
|
@@ -20,6 +17,7 @@ const { version } = createRequire(import.meta.url)("../../package.json");
|
|
|
20
17
|
const log = {
|
|
21
18
|
success: (message) => console.log(`${styleText("green", "✓")} ${message}`),
|
|
22
19
|
info: (message) => console.log(`${styleText("cyan", "●")} ${message}`),
|
|
20
|
+
warn: (message) => console.warn(`${styleText("yellow", "⚠")} ${message}`),
|
|
23
21
|
error: (error) => console.error(`${styleText("red", "✗")} ${formatError(error)}`)
|
|
24
22
|
};
|
|
25
23
|
const cli = cac("blogger-hbs");
|
|
@@ -69,6 +67,7 @@ async function setup(root, flags) {
|
|
|
69
67
|
for (const key of COMPILE_OPTION_KEYS) if (key in config) Object.assign(options, { [key]: config[key] });
|
|
70
68
|
options.root = resolvedRoot;
|
|
71
69
|
options.entry = flags.entry ?? config.entry;
|
|
70
|
+
options.onWarn ??= log.warn;
|
|
72
71
|
assertCompileOptions(options);
|
|
73
72
|
await assertDirectories(options);
|
|
74
73
|
const outDir = path.resolve(resolvedRoot, flags.outDir ?? output.dir ?? "dist");
|
|
@@ -219,7 +218,7 @@ async function watchBuild(root, flags) {
|
|
|
219
218
|
const stop = async () => {
|
|
220
219
|
stopWatchers();
|
|
221
220
|
await watcher?.close();
|
|
222
|
-
process.exit(0);
|
|
221
|
+
setTimeout(() => process.exit(0), 3e3).unref();
|
|
223
222
|
};
|
|
224
223
|
process.once("SIGINT", stop);
|
|
225
224
|
process.once("SIGTERM", stop);
|
|
@@ -264,17 +263,13 @@ async function prepareOutDir({ outDir, clean }) {
|
|
|
264
263
|
* formatted after rendering when enabled.
|
|
265
264
|
*/
|
|
266
265
|
async function write(current, compiled) {
|
|
267
|
-
const { outDir, moduleFile, templateFile, context
|
|
266
|
+
const { outDir, moduleFile, templateFile, context } = current;
|
|
268
267
|
if (moduleFile) await fs.writeFile(path.join(outDir, moduleFile), compiled.source);
|
|
269
268
|
if (!templateFile) return;
|
|
270
269
|
const file = path.join(outDir, templateFile);
|
|
271
270
|
const shown = path.relative(process.cwd(), file);
|
|
272
271
|
let start = performance.now();
|
|
273
|
-
const rendered =
|
|
274
|
-
...compiled,
|
|
275
|
-
context,
|
|
276
|
-
plugins: loaded.config.runtimePlugins
|
|
277
|
-
});
|
|
272
|
+
const rendered = await createRenderer(compiled).render(context);
|
|
278
273
|
log.success(`Rendered in ${elapsed(start)}`);
|
|
279
274
|
start = performance.now();
|
|
280
275
|
let output;
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["import { type FSWatcher, watch } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport * as path from 'node:path';\nimport { styleText } from 'node:util';\nimport { cac } from 'cac';\nimport {\n\ttype CompiledTemplate,\n\tCompileError,\n\ttype CompileTemplateOptions,\n\tcompileTemplate,\n\ttype TemplateWatcher,\n\twatchTemplate,\n} from '../compiler';\nimport {\n\tassertCompileOptions,\n\tassertDirectories,\n\tassertEntry,\n\tCOMPILE_OPTION_KEYS,\n\tresolvePaths,\n} from '../compiler/options';\nimport { type LoadedConfig, loadConfig } from '../config';\nimport { renderTemplate } from '../runtime';\nimport { didYouMean } from '../utils/suggest';\nimport { formatXml, validateXml } from '../xml';\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as {\n\tversion: string;\n};\n\ninterface BuildFlags {\n\tconfig?: string;\n\tentry?: string;\n\toutDir?: string;\n\tcontext?: string;\n\twatch?: boolean;\n\tclean?: boolean;\n\tvalidate?: boolean;\n\tformat?: boolean;\n}\n\n/** Everything a build needs, from the config file and the flags. */\ninterface BuildSetup {\n\toptions: CompileTemplateOptions;\n\tloaded: LoadedConfig;\n\toutDir: string;\n\tmoduleFile: string | false;\n\ttemplateFile: string | false;\n\tclean: boolean;\n\tcontext: unknown;\n\t/** Whether the rendered template is validated. */\n\tvalidate: boolean;\n\t/** Whether the rendered template is formatted, only when it is validated. */\n\tformat: boolean;\n\t/** Absolute path of the public directory, undefined when nothing is copied. */\n\tpublicDir?: string;\n}\n\nconst log = {\n\tsuccess: (message: string) =>\n\t\tconsole.log(`${styleText('green', '✓')} ${message}`),\n\tinfo: (message: string) =>\n\t\tconsole.log(`${styleText('cyan', '●')} ${message}`),\n\terror: (error: unknown) =>\n\t\tconsole.error(`${styleText('red', '✗')} ${formatError(error)}`),\n};\n\nconst cli = cac('blogger-hbs');\n\ncli\n\t.command('[root]', 'Compile the template and render it')\n\t.alias('build')\n\t.option('-c, --config <file>', 'Path of the config file')\n\t.option('--entry <file>', 'Entry template, relative to the root')\n\t.option('-o, --out-dir <dir>', 'Output directory, relative to the root')\n\t.option('--context <file>', 'JSON file with the context to render with')\n\t.option('-w, --watch', 'Build again when a file changes')\n\t.option('--no-clean', 'Keep the files in the output directory')\n\t.option(\n\t\t'--no-validate',\n\t\t'Do not validate the rendered template as XML, nor format it (templates are always validated when compiling)',\n\t)\n\t.option('--no-format', 'Do not format the rendered template')\n\t.example('blogger-hbs')\n\t.example('blogger-hbs --watch')\n\t.example('blogger-hbs ./theme --entry src/index.hbs --context context.json')\n\t.action(async (rootArg: unknown, rawFlags: BuildFlags) => {\n\t\t// cac turns values that look like numbers into numbers, `-o 2024`\n\t\tconst text = (value: unknown) =>\n\t\t\tvalue === undefined ? undefined : String(value);\n\t\tconst root = text(rootArg);\n\t\tconst flags: BuildFlags = {\n\t\t\t...rawFlags,\n\t\t\tconfig: text(rawFlags.config),\n\t\t\tentry: text(rawFlags.entry),\n\t\t\toutDir: text(rawFlags.outDir),\n\t\t\tcontext: text(rawFlags.context),\n\t\t};\n\n\t\tif (flags.watch) {\n\t\t\tawait watchBuild(root, flags);\n\t\t} else {\n\t\t\tawait build(root, flags);\n\t\t}\n\t});\n\ncli.help();\ncli.version(version);\n\ntry {\n\tcli.parse(process.argv, { run: false });\n\tawait cli.runMatchedCommand();\n} catch (error) {\n\tlog.error(describeCliError(error));\n\tprocess.exitCode = 1;\n}\n\n/** Adds a suggestion and a pointer to `--help` to the errors of the arguments. */\nfunction describeCliError(error: unknown): unknown {\n\tif (!(error instanceof Error) || error.name !== 'CACError') {\n\t\treturn error;\n\t}\n\n\tconst flags = [...cli.commands, cli.globalCommand].flatMap((command) =>\n\t\tcommand.options.flatMap(\n\t\t\t(option) => option.rawName.match(/--[\\w-]+/g) ?? [],\n\t\t),\n\t);\n\tconst unknown = /^Unknown option `(.+)`$/.exec(error.message)?.[1];\n\tconst hint = unknown ? didYouMean(unknown, flags, '`') : '';\n\n\treturn new Error(\n\t\t`${error.message}${hint}\\n Run 'blogger-hbs --help' to see the available options`,\n\t);\n}\n\nasync function setup(\n\troot: string | undefined,\n\tflags: BuildFlags,\n): Promise<BuildSetup> {\n\tconst cwd = process.cwd();\n\tconst rootFlag = root === undefined ? undefined : path.resolve(cwd, root);\n\n\t// the config is looked for in the root, which has to exist for that\n\tif (rootFlag !== undefined && !(await isDirectory(rootFlag))) {\n\t\tthrow new CompileError(\n\t\t\t(await exists(rootFlag))\n\t\t\t\t? `Root '${root}' is not a directory`\n\t\t\t\t: `Root directory '${root}' does not exist`,\n\t\t);\n\t}\n\n\tconst loaded = await loadConfig({\n\t\tcwd: rootFlag ?? cwd,\n\t\tconfigFile: flags.config && path.resolve(cwd, flags.config),\n\t\twatch: flags.watch,\n\t});\n\tconst { config } = loaded;\n\n\tconst resolvedRoot = rootFlag ?? config.root ?? cwd;\n\tconst output = config.output ?? {};\n\n\t// the config has more than the compile options\n\tconst options: CompileTemplateOptions = {};\n\tfor (const key of COMPILE_OPTION_KEYS) {\n\t\tif (key in config) {\n\t\t\tObject.assign(options, { [key]: config[key as keyof typeof config] });\n\t\t}\n\t}\n\toptions.root = resolvedRoot;\n\toptions.entry = flags.entry ?? config.entry;\n\n\tassertCompileOptions(options);\n\tawait assertDirectories(options);\n\n\tconst outDir = path.resolve(\n\t\tresolvedRoot,\n\t\tflags.outDir ?? output.dir ?? 'dist',\n\t);\n\tconst clean = flags.clean === false ? false : (output.clean ?? true);\n\tif (clean) {\n\t\tassertCleanable(outDir, options);\n\t}\n\n\treturn {\n\t\toptions,\n\t\tloaded,\n\t\toutDir,\n\t\tmoduleFile: output.module ?? 'compiled.js',\n\t\ttemplateFile: output.template ?? 'theme.xml',\n\t\tvalidate: flags.validate === false ? false : (output.validate ?? true),\n\t\tformat: flags.format === false ? false : (output.format ?? true),\n\t\tclean,\n\t\tcontext: flags.context\n\t\t\t? await readContext(path.resolve(cwd, flags.context), flags.context)\n\t\t\t: (config.context ?? {}),\n\t\tpublicDir: await resolvePublicDir(resolvedRoot, config.publicDir, outDir),\n\t};\n}\n\n/** The public directory, `public` when it exists unless set otherwise. */\nasync function resolvePublicDir(\n\troot: string,\n\tvalue: string | false | undefined,\n\toutDir: string,\n): Promise<string | undefined> {\n\tif (value === false) {\n\t\treturn;\n\t}\n\n\tconst directory = path.resolve(root, value ?? 'public');\n\tconst shown = path.relative(process.cwd(), directory) || '.';\n\n\tif (!(await isDirectory(directory))) {\n\t\tif (value === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new CompileError(\n\t\t\t(await exists(directory))\n\t\t\t\t? `Public directory '${shown}' is not a directory`\n\t\t\t\t: `Public directory '${shown}' does not exist, set 'publicDir: false' to not copy anything`,\n\t\t);\n\t}\n\n\tif (isInside(outDir, directory) || isInside(directory, outDir)) {\n\t\tthrow new CompileError(\n\t\t\t`Public directory '${shown}' and output directory '${path.relative(process.cwd(), outDir) || '.'}' overlap, they must be separate`,\n\t\t);\n\t}\n\n\treturn directory;\n}\n\n/** Copies the public directory into the output directory. */\nasync function copyPublic({\n\tpublicDir,\n\toutDir,\n\tmoduleFile,\n\ttemplateFile,\n}: BuildSetup): Promise<void> {\n\tif (publicDir === undefined) {\n\t\treturn;\n\t}\n\n\tfor (const name of await fs.readdir(publicDir)) {\n\t\tif (name === moduleFile || name === templateFile) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`'${name}' in the public directory has the same name as the ${name === moduleFile ? 'compiled module' : 'rendered template'} and would be overwritten, rename it or set 'output.${name === moduleFile ? 'module' : 'template'}'`,\n\t\t\t);\n\t\t}\n\t}\n\n\tawait fs.cp(publicDir, outDir, { recursive: true, force: true });\n}\n\n/**\n * Cleaning removes everything in the output directory, which must not hold the\n * project: the root, the working directory, the templates or an alias target.\n */\nfunction assertCleanable(outDir: string, options: CompileTemplateOptions) {\n\tconst { root, entry } = resolvePaths(options);\n\tconst shown = path.relative(process.cwd(), outDir) || '.';\n\tconst kept: [string, string][] = [\n\t\t[root, 'the root'],\n\t\t[process.cwd(), 'the working directory'],\n\t\t[path.dirname(entry), 'the entry template'],\n\t\t...Object.entries(options.alias ?? {}).map(\n\t\t\t([name, target]): [string, string] => [\n\t\t\t\tpath.resolve(root, target),\n\t\t\t\t`the target of the alias '${name}'`,\n\t\t\t],\n\t\t),\n\t];\n\n\tfor (const [directory, what] of kept) {\n\t\tif (isInside(directory, outDir)) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`Refusing to clean the output directory '${shown}', it contains ${what}. Use another output directory, or --no-clean / 'output.clean: false' to keep its files`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function readContext(file: string, shown: string): Promise<unknown> {\n\tlet text: string;\n\ttry {\n\t\ttext = await fs.readFile(file, 'utf-8');\n\t} catch (cause) {\n\t\tif ((cause as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\tthrow new CompileError(`Context file '${shown}' not found`);\n\t\t}\n\t\tthrow new CompileError(`Failed to read the context file '${shown}'`, {\n\t\t\tcause,\n\t\t});\n\t}\n\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch (cause) {\n\t\tthrow new CompileError(\n\t\t\t`Context file '${shown}' is not valid JSON: ${(cause as Error).message}`,\n\t\t);\n\t}\n}\n\n/** Whether `child` is `parent` or inside of it. */\nfunction isInside(child: string, parent: string): boolean {\n\tconst relative = path.relative(parent, child);\n\treturn (\n\t\trelative === '' ||\n\t\t(!relative.startsWith('..') && !path.isAbsolute(relative))\n\t);\n}\n\nasync function exists(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t() => true,\n\t\t() => false,\n\t);\n}\n\nasync function isDirectory(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t(stats) => stats.isDirectory(),\n\t\t() => false,\n\t);\n}\n\nasync function build(root: string | undefined, flags: BuildFlags) {\n\tconst current = await setup(root, flags);\n\t// nothing is written when there is nothing to build, the watcher waits\n\t// for the entry instead\n\tawait assertEntry(current.options);\n\tawait prepareOutDir(current);\n\tawait copyPublic(current);\n\n\tconst start = performance.now();\n\tconst compiled = await compileTemplate(current.options);\n\tlog.success(`Compiled in ${elapsed(start)}`);\n\n\tawait write(current, compiled);\n}\n\n/** Watches the template and restarts when the config file changes. */\nasync function watchBuild(root: string | undefined, flags: BuildFlags) {\n\tlet current = await setup(root, flags);\n\tawait prepareOutDir(current);\n\n\tlet watcher: TemplateWatcher | undefined;\n\tlet watchers: FSWatcher[] = [];\n\tlet restarting = false;\n\n\tconst start = async () => {\n\t\tlet begin = performance.now();\n\t\tlet first = true;\n\n\t\ttry {\n\t\t\tawait copyPublic(current);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\n\t\twatcher = await watchTemplate({\n\t\t\t...current.options,\n\t\t\tonStart() {\n\t\t\t\tbegin = performance.now();\n\t\t\t},\n\t\t\tasync onCompile(compiled) {\n\t\t\t\tlog.success(`${first ? 'Compiled' : 'Rebuilt'} in ${elapsed(begin)}`);\n\t\t\t\tfirst = false;\n\t\t\t\tawait write(current, compiled);\n\t\t\t},\n\t\t\tonError: log.error,\n\t\t});\n\n\t\tfor (const file of current.loaded.dependencies) {\n\t\t\ttry {\n\t\t\t\tconst configWatcher = watch(file, () => {\n\t\t\t\t\t// saving fires more than once, a restart is enough\n\t\t\t\t\tif (!restarting) {\n\t\t\t\t\t\trestarting = true;\n\t\t\t\t\t\trestart().finally(() => {\n\t\t\t\t\t\t\trestarting = false;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconfigWatcher.on('error', () => configWatcher.close());\n\t\t\t\twatchers.push(configWatcher);\n\t\t\t} catch {\n\t\t\t\t// a dependency that can not be watched does not restart\n\t\t\t}\n\t\t}\n\n\t\tif (current.publicDir !== undefined) {\n\t\t\twatchers.push(watchPublic(current));\n\t\t}\n\t};\n\n\tconst stopWatchers = () => {\n\t\tfor (const fileWatcher of watchers) {\n\t\t\tfileWatcher.close();\n\t\t}\n\t\twatchers = [];\n\t};\n\n\tconst restart = async () => {\n\t\tlog.info(\n\t\t\t`Config changed, restarting ${styleText('dim', path.relative(process.cwd(), current.loaded.file ?? ''))}`,\n\t\t);\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\ttry {\n\t\t\tcurrent = await setup(root, flags);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t\tawait start();\n\t};\n\n\tawait start();\n\tlog.info(\n\t\t`Watching for changes ${styleText('dim', '(press Ctrl+C to stop)')}`,\n\t);\n\n\tconst stop = async () => {\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\tprocess.exit(0);\n\t};\n\tprocess.once('SIGINT', stop);\n\tprocess.once('SIGTERM', stop);\n}\n\n/** Copies the public directory again when something in it changes. */\nfunction watchPublic(current: BuildSetup): FSWatcher {\n\tconst publicDir = current.publicDir as string;\n\tconst changed = new Set<string>();\n\tlet timer: NodeJS.Timeout | undefined;\n\n\tconst sync = async () => {\n\t\tconst names = [...changed];\n\t\tchanged.clear();\n\t\ttry {\n\t\t\t// what was deleted from the public directory goes from the output too\n\t\t\tfor (const name of names) {\n\t\t\t\tif (!(await exists(path.join(publicDir, name)))) {\n\t\t\t\t\tawait fs.rm(path.join(current.outDir, name), {\n\t\t\t\t\t\trecursive: true,\n\t\t\t\t\t\tforce: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait copyPublic(current);\n\t\t\tlog.success(\n\t\t\t\t`Copied ${styleText('dim', path.relative(process.cwd(), publicDir))}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t};\n\n\tconst publicWatcher = watch(\n\t\tpublicDir,\n\t\t{ recursive: true },\n\t\t(_event, name) => {\n\t\t\tif (name) {\n\t\t\t\tchanged.add(name);\n\t\t\t}\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = setTimeout(sync, 50);\n\t\t},\n\t);\n\tpublicWatcher.on('error', () => publicWatcher.close());\n\tpublicWatcher.on('close', () => clearTimeout(timer));\n\treturn publicWatcher;\n}\n\nasync function prepareOutDir({ outDir, clean }: BuildSetup) {\n\tif (clean) {\n\t\tawait fs.rm(outDir, { recursive: true, force: true });\n\t}\n\tawait fs.mkdir(outDir, { recursive: true });\n}\n\n/**\n * Writes the compiled module and the rendered template, validated and\n * formatted after rendering when enabled.\n */\nasync function write(current: BuildSetup, compiled: CompiledTemplate) {\n\tconst { outDir, moduleFile, templateFile, context, loaded } = current;\n\n\tif (moduleFile) {\n\t\tawait fs.writeFile(path.join(outDir, moduleFile), compiled.source);\n\t}\n\tif (!templateFile) {\n\t\treturn;\n\t}\n\n\tconst file = path.join(outDir, templateFile);\n\tconst shown = path.relative(process.cwd(), file);\n\n\tlet start = performance.now();\n\tconst rendered = renderTemplate({\n\t\t...compiled,\n\t\tcontext,\n\t\tplugins: loaded.config.runtimePlugins,\n\t});\n\tlog.success(`Rendered in ${elapsed(start)}`);\n\n\tstart = performance.now();\n\tlet output: string;\n\ttry {\n\t\toutput = finalizeXml(current, rendered);\n\t} catch (error) {\n\t\t// written anyway, to look at where it is invalid\n\t\tawait fs.writeFile(file, rendered);\n\t\tthrow new CompileError(\n\t\t\t`The rendered template ${shown} is not valid XML: ${(error as Error).message}`,\n\t\t);\n\t}\n\tif (current.validate) {\n\t\tlog.success(\n\t\t\t`${current.format ? 'Formatted' : 'Validated'} in ${elapsed(start)}`,\n\t\t);\n\t}\n\n\tawait fs.writeFile(file, output);\n\tlog.success(`Written ${styleText('dim', shown)}`);\n}\n\n/** Validates and formats the rendered template, as enabled. */\nfunction finalizeXml({ validate, format }: BuildSetup, xml: string): string {\n\tif (!validate) {\n\t\treturn xml;\n\t}\n\tif (!format) {\n\t\tvalidateXml(xml);\n\t\treturn xml;\n\t}\n\treturn formatXml(xml);\n}\n\nfunction elapsed(start: number): string {\n\treturn styleText('yellow', `${Math.round(performance.now() - start)}ms`);\n}\n\n/** The message of the error and of everything that caused it. */\nfunction formatError(error: unknown): string {\n\tconst lines: string[] = [];\n\tlet current: unknown = error;\n\n\twhile (current !== undefined) {\n\t\tlines.push(\n\t\t\tcurrent instanceof Error\n\t\t\t\t? `${lines.length === 0 ? '' : 'Caused by: '}${current.message}`\n\t\t\t\t: String(current),\n\t\t);\n\t\tcurrent = current instanceof Error ? current.cause : undefined;\n\t}\n\n\treturn lines.join('\\n ');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["import { type FSWatcher, watch } from 'node:fs';\nimport * as fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport * as path from 'node:path';\nimport { styleText } from 'node:util';\nimport { cac } from 'cac';\nimport {\n\ttype CompiledTemplate,\n\tCompileError,\n\ttype CompileTemplateOptions,\n\tcompileTemplate,\n\ttype TemplateWatcher,\n\twatchTemplate,\n} from '../compiler';\nimport {\n\tassertCompileOptions,\n\tassertDirectories,\n\tassertEntry,\n\tCOMPILE_OPTION_KEYS,\n\tresolvePaths,\n} from '../compiler/options';\nimport { type LoadedConfig, loadConfig } from '../config';\nimport { createRenderer } from '../runtime';\nimport { didYouMean } from '../utils/suggest';\nimport { formatXml, validateXml } from '../xml';\n\nconst { version } = createRequire(import.meta.url)('../../package.json') as {\n\tversion: string;\n};\n\ninterface BuildFlags {\n\tconfig?: string;\n\tentry?: string;\n\toutDir?: string;\n\tcontext?: string;\n\twatch?: boolean;\n\tclean?: boolean;\n\tvalidate?: boolean;\n\tformat?: boolean;\n}\n\n/** Everything a build needs, from the config file and the flags. */\ninterface BuildSetup {\n\toptions: CompileTemplateOptions;\n\tloaded: LoadedConfig;\n\toutDir: string;\n\tmoduleFile: string | false;\n\ttemplateFile: string | false;\n\tclean: boolean;\n\tcontext: unknown;\n\t/** Whether the rendered template is validated. */\n\tvalidate: boolean;\n\t/** Whether the rendered template is formatted, only when it is validated. */\n\tformat: boolean;\n\t/** Absolute path of the public directory, undefined when nothing is copied. */\n\tpublicDir?: string;\n}\n\nconst log = {\n\tsuccess: (message: string) =>\n\t\tconsole.log(`${styleText('green', '✓')} ${message}`),\n\tinfo: (message: string) =>\n\t\tconsole.log(`${styleText('cyan', '●')} ${message}`),\n\twarn: (message: string) =>\n\t\tconsole.warn(`${styleText('yellow', '⚠')} ${message}`),\n\terror: (error: unknown) =>\n\t\tconsole.error(`${styleText('red', '✗')} ${formatError(error)}`),\n};\n\nconst cli = cac('blogger-hbs');\n\ncli\n\t.command('[root]', 'Compile the template and render it')\n\t.alias('build')\n\t.option('-c, --config <file>', 'Path of the config file')\n\t.option('--entry <file>', 'Entry template, relative to the root')\n\t.option('-o, --out-dir <dir>', 'Output directory, relative to the root')\n\t.option('--context <file>', 'JSON file with the context to render with')\n\t.option('-w, --watch', 'Build again when a file changes')\n\t.option('--no-clean', 'Keep the files in the output directory')\n\t.option(\n\t\t'--no-validate',\n\t\t'Do not validate the rendered template as XML, nor format it (templates are always validated when compiling)',\n\t)\n\t.option('--no-format', 'Do not format the rendered template')\n\t.example('blogger-hbs')\n\t.example('blogger-hbs --watch')\n\t.example('blogger-hbs ./theme --entry src/index.hbs --context context.json')\n\t.action(async (rootArg: unknown, rawFlags: BuildFlags) => {\n\t\t// cac turns values that look like numbers into numbers, `-o 2024`\n\t\tconst text = (value: unknown) =>\n\t\t\tvalue === undefined ? undefined : String(value);\n\t\tconst root = text(rootArg);\n\t\tconst flags: BuildFlags = {\n\t\t\t...rawFlags,\n\t\t\tconfig: text(rawFlags.config),\n\t\t\tentry: text(rawFlags.entry),\n\t\t\toutDir: text(rawFlags.outDir),\n\t\t\tcontext: text(rawFlags.context),\n\t\t};\n\n\t\tif (flags.watch) {\n\t\t\tawait watchBuild(root, flags);\n\t\t} else {\n\t\t\tawait build(root, flags);\n\t\t}\n\t});\n\ncli.help();\ncli.version(version);\n\ntry {\n\tcli.parse(process.argv, { run: false });\n\tawait cli.runMatchedCommand();\n} catch (error) {\n\tlog.error(describeCliError(error));\n\tprocess.exitCode = 1;\n}\n\n/** Adds a suggestion and a pointer to `--help` to the errors of the arguments. */\nfunction describeCliError(error: unknown): unknown {\n\tif (!(error instanceof Error) || error.name !== 'CACError') {\n\t\treturn error;\n\t}\n\n\tconst flags = [...cli.commands, cli.globalCommand].flatMap((command) =>\n\t\tcommand.options.flatMap(\n\t\t\t(option) => option.rawName.match(/--[\\w-]+/g) ?? [],\n\t\t),\n\t);\n\tconst unknown = /^Unknown option `(.+)`$/.exec(error.message)?.[1];\n\tconst hint = unknown ? didYouMean(unknown, flags, '`') : '';\n\n\treturn new Error(\n\t\t`${error.message}${hint}\\n Run 'blogger-hbs --help' to see the available options`,\n\t);\n}\n\nasync function setup(\n\troot: string | undefined,\n\tflags: BuildFlags,\n): Promise<BuildSetup> {\n\tconst cwd = process.cwd();\n\tconst rootFlag = root === undefined ? undefined : path.resolve(cwd, root);\n\n\t// the config is looked for in the root, which has to exist for that\n\tif (rootFlag !== undefined && !(await isDirectory(rootFlag))) {\n\t\tthrow new CompileError(\n\t\t\t(await exists(rootFlag))\n\t\t\t\t? `Root '${root}' is not a directory`\n\t\t\t\t: `Root directory '${root}' does not exist`,\n\t\t);\n\t}\n\n\tconst loaded = await loadConfig({\n\t\tcwd: rootFlag ?? cwd,\n\t\tconfigFile: flags.config && path.resolve(cwd, flags.config),\n\t\twatch: flags.watch,\n\t});\n\tconst { config } = loaded;\n\n\tconst resolvedRoot = rootFlag ?? config.root ?? cwd;\n\tconst output = config.output ?? {};\n\n\t// the config has more than the compile options\n\tconst options: CompileTemplateOptions = {};\n\tfor (const key of COMPILE_OPTION_KEYS) {\n\t\tif (key in config) {\n\t\t\tObject.assign(options, { [key]: config[key as keyof typeof config] });\n\t\t}\n\t}\n\toptions.root = resolvedRoot;\n\toptions.entry = flags.entry ?? config.entry;\n\toptions.onWarn ??= log.warn;\n\n\tassertCompileOptions(options);\n\tawait assertDirectories(options);\n\n\tconst outDir = path.resolve(\n\t\tresolvedRoot,\n\t\tflags.outDir ?? output.dir ?? 'dist',\n\t);\n\tconst clean = flags.clean === false ? false : (output.clean ?? true);\n\tif (clean) {\n\t\tassertCleanable(outDir, options);\n\t}\n\n\treturn {\n\t\toptions,\n\t\tloaded,\n\t\toutDir,\n\t\tmoduleFile: output.module ?? 'compiled.js',\n\t\ttemplateFile: output.template ?? 'theme.xml',\n\t\tvalidate: flags.validate === false ? false : (output.validate ?? true),\n\t\tformat: flags.format === false ? false : (output.format ?? true),\n\t\tclean,\n\t\tcontext: flags.context\n\t\t\t? await readContext(path.resolve(cwd, flags.context), flags.context)\n\t\t\t: (config.context ?? {}),\n\t\tpublicDir: await resolvePublicDir(resolvedRoot, config.publicDir, outDir),\n\t};\n}\n\n/** The public directory, `public` when it exists unless set otherwise. */\nasync function resolvePublicDir(\n\troot: string,\n\tvalue: string | false | undefined,\n\toutDir: string,\n): Promise<string | undefined> {\n\tif (value === false) {\n\t\treturn;\n\t}\n\n\tconst directory = path.resolve(root, value ?? 'public');\n\tconst shown = path.relative(process.cwd(), directory) || '.';\n\n\tif (!(await isDirectory(directory))) {\n\t\tif (value === undefined) {\n\t\t\treturn;\n\t\t}\n\t\tthrow new CompileError(\n\t\t\t(await exists(directory))\n\t\t\t\t? `Public directory '${shown}' is not a directory`\n\t\t\t\t: `Public directory '${shown}' does not exist, set 'publicDir: false' to not copy anything`,\n\t\t);\n\t}\n\n\tif (isInside(outDir, directory) || isInside(directory, outDir)) {\n\t\tthrow new CompileError(\n\t\t\t`Public directory '${shown}' and output directory '${path.relative(process.cwd(), outDir) || '.'}' overlap, they must be separate`,\n\t\t);\n\t}\n\n\treturn directory;\n}\n\n/** Copies the public directory into the output directory. */\nasync function copyPublic({\n\tpublicDir,\n\toutDir,\n\tmoduleFile,\n\ttemplateFile,\n}: BuildSetup): Promise<void> {\n\tif (publicDir === undefined) {\n\t\treturn;\n\t}\n\n\tfor (const name of await fs.readdir(publicDir)) {\n\t\tif (name === moduleFile || name === templateFile) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`'${name}' in the public directory has the same name as the ${name === moduleFile ? 'compiled module' : 'rendered template'} and would be overwritten, rename it or set 'output.${name === moduleFile ? 'module' : 'template'}'`,\n\t\t\t);\n\t\t}\n\t}\n\n\tawait fs.cp(publicDir, outDir, { recursive: true, force: true });\n}\n\n/**\n * Cleaning removes everything in the output directory, which must not hold the\n * project: the root, the working directory, the templates or an alias target.\n */\nfunction assertCleanable(outDir: string, options: CompileTemplateOptions) {\n\tconst { root, entry } = resolvePaths(options);\n\tconst shown = path.relative(process.cwd(), outDir) || '.';\n\tconst kept: [string, string][] = [\n\t\t[root, 'the root'],\n\t\t[process.cwd(), 'the working directory'],\n\t\t[path.dirname(entry), 'the entry template'],\n\t\t...Object.entries(options.alias ?? {}).map(\n\t\t\t([name, target]): [string, string] => [\n\t\t\t\tpath.resolve(root, target),\n\t\t\t\t`the target of the alias '${name}'`,\n\t\t\t],\n\t\t),\n\t];\n\n\tfor (const [directory, what] of kept) {\n\t\tif (isInside(directory, outDir)) {\n\t\t\tthrow new CompileError(\n\t\t\t\t`Refusing to clean the output directory '${shown}', it contains ${what}. Use another output directory, or --no-clean / 'output.clean: false' to keep its files`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nasync function readContext(file: string, shown: string): Promise<unknown> {\n\tlet text: string;\n\ttry {\n\t\ttext = await fs.readFile(file, 'utf-8');\n\t} catch (cause) {\n\t\tif ((cause as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\tthrow new CompileError(`Context file '${shown}' not found`);\n\t\t}\n\t\tthrow new CompileError(`Failed to read the context file '${shown}'`, {\n\t\t\tcause,\n\t\t});\n\t}\n\n\ttry {\n\t\treturn JSON.parse(text);\n\t} catch (cause) {\n\t\tthrow new CompileError(\n\t\t\t`Context file '${shown}' is not valid JSON: ${(cause as Error).message}`,\n\t\t);\n\t}\n}\n\n/** Whether `child` is `parent` or inside of it. */\nfunction isInside(child: string, parent: string): boolean {\n\tconst relative = path.relative(parent, child);\n\treturn (\n\t\trelative === '' ||\n\t\t(!relative.startsWith('..') && !path.isAbsolute(relative))\n\t);\n}\n\nasync function exists(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t() => true,\n\t\t() => false,\n\t);\n}\n\nasync function isDirectory(file: string): Promise<boolean> {\n\treturn fs.stat(file).then(\n\t\t(stats) => stats.isDirectory(),\n\t\t() => false,\n\t);\n}\n\nasync function build(root: string | undefined, flags: BuildFlags) {\n\tconst current = await setup(root, flags);\n\t// nothing is written when there is nothing to build, the watcher waits\n\t// for the entry instead\n\tawait assertEntry(current.options);\n\tawait prepareOutDir(current);\n\tawait copyPublic(current);\n\n\tconst start = performance.now();\n\tconst compiled = await compileTemplate(current.options);\n\tlog.success(`Compiled in ${elapsed(start)}`);\n\n\tawait write(current, compiled);\n}\n\n/** Watches the template and restarts when the config file changes. */\nasync function watchBuild(root: string | undefined, flags: BuildFlags) {\n\tlet current = await setup(root, flags);\n\tawait prepareOutDir(current);\n\n\tlet watcher: TemplateWatcher | undefined;\n\tlet watchers: FSWatcher[] = [];\n\tlet restarting = false;\n\n\tconst start = async () => {\n\t\tlet begin = performance.now();\n\t\tlet first = true;\n\n\t\ttry {\n\t\t\tawait copyPublic(current);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\n\t\twatcher = await watchTemplate({\n\t\t\t...current.options,\n\t\t\tonStart() {\n\t\t\t\tbegin = performance.now();\n\t\t\t},\n\t\t\tasync onCompile(compiled) {\n\t\t\t\tlog.success(`${first ? 'Compiled' : 'Rebuilt'} in ${elapsed(begin)}`);\n\t\t\t\tfirst = false;\n\t\t\t\tawait write(current, compiled);\n\t\t\t},\n\t\t\tonError: log.error,\n\t\t});\n\n\t\tfor (const file of current.loaded.dependencies) {\n\t\t\ttry {\n\t\t\t\tconst configWatcher = watch(file, () => {\n\t\t\t\t\t// saving fires more than once, a restart is enough\n\t\t\t\t\tif (!restarting) {\n\t\t\t\t\t\trestarting = true;\n\t\t\t\t\t\trestart().finally(() => {\n\t\t\t\t\t\t\trestarting = false;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tconfigWatcher.on('error', () => configWatcher.close());\n\t\t\t\twatchers.push(configWatcher);\n\t\t\t} catch {\n\t\t\t\t// a dependency that can not be watched does not restart\n\t\t\t}\n\t\t}\n\n\t\tif (current.publicDir !== undefined) {\n\t\t\twatchers.push(watchPublic(current));\n\t\t}\n\t};\n\n\tconst stopWatchers = () => {\n\t\tfor (const fileWatcher of watchers) {\n\t\t\tfileWatcher.close();\n\t\t}\n\t\twatchers = [];\n\t};\n\n\tconst restart = async () => {\n\t\tlog.info(\n\t\t\t`Config changed, restarting ${styleText('dim', path.relative(process.cwd(), current.loaded.file ?? ''))}`,\n\t\t);\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\ttry {\n\t\t\tcurrent = await setup(root, flags);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t\tawait start();\n\t};\n\n\tawait start();\n\tlog.info(\n\t\t`Watching for changes ${styleText('dim', '(press Ctrl+C to stop)')}`,\n\t);\n\n\tconst stop = async () => {\n\t\tstopWatchers();\n\t\tawait watcher?.close();\n\t\t// the process ends once nothing is left running: `process.exit` would skip\n\t\t// the cleanup of rspack's native bindings, which then warn about leaks,\n\t\t// so it is only a fallback for something left running, a plugin's timer\n\t\tsetTimeout(() => process.exit(0), 3000).unref();\n\t};\n\tprocess.once('SIGINT', stop);\n\tprocess.once('SIGTERM', stop);\n}\n\n/** Copies the public directory again when something in it changes. */\nfunction watchPublic(current: BuildSetup): FSWatcher {\n\tconst publicDir = current.publicDir as string;\n\tconst changed = new Set<string>();\n\tlet timer: NodeJS.Timeout | undefined;\n\n\tconst sync = async () => {\n\t\tconst names = [...changed];\n\t\tchanged.clear();\n\t\ttry {\n\t\t\t// what was deleted from the public directory goes from the output too\n\t\t\tfor (const name of names) {\n\t\t\t\tif (!(await exists(path.join(publicDir, name)))) {\n\t\t\t\t\tawait fs.rm(path.join(current.outDir, name), {\n\t\t\t\t\t\trecursive: true,\n\t\t\t\t\t\tforce: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait copyPublic(current);\n\t\t\tlog.success(\n\t\t\t\t`Copied ${styleText('dim', path.relative(process.cwd(), publicDir))}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tlog.error(error);\n\t\t}\n\t};\n\n\tconst publicWatcher = watch(\n\t\tpublicDir,\n\t\t{ recursive: true },\n\t\t(_event, name) => {\n\t\t\tif (name) {\n\t\t\t\tchanged.add(name);\n\t\t\t}\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = setTimeout(sync, 50);\n\t\t},\n\t);\n\tpublicWatcher.on('error', () => publicWatcher.close());\n\tpublicWatcher.on('close', () => clearTimeout(timer));\n\treturn publicWatcher;\n}\n\nasync function prepareOutDir({ outDir, clean }: BuildSetup) {\n\tif (clean) {\n\t\tawait fs.rm(outDir, { recursive: true, force: true });\n\t}\n\tawait fs.mkdir(outDir, { recursive: true });\n}\n\n/**\n * Writes the compiled module and the rendered template, validated and\n * formatted after rendering when enabled.\n */\nasync function write(current: BuildSetup, compiled: CompiledTemplate) {\n\tconst { outDir, moduleFile, templateFile, context } = current;\n\n\tif (moduleFile) {\n\t\tawait fs.writeFile(path.join(outDir, moduleFile), compiled.source);\n\t}\n\tif (!templateFile) {\n\t\treturn;\n\t}\n\n\tconst file = path.join(outDir, templateFile);\n\tconst shown = path.relative(process.cwd(), file);\n\n\t// the same as anywhere else the compiled module is rendered\n\tlet start = performance.now();\n\tconst renderer = createRenderer(compiled);\n\tconst rendered = await renderer.render(context);\n\tlog.success(`Rendered in ${elapsed(start)}`);\n\n\tstart = performance.now();\n\tlet output: string;\n\ttry {\n\t\toutput = finalizeXml(current, rendered);\n\t} catch (error) {\n\t\t// written anyway, to look at where it is invalid\n\t\tawait fs.writeFile(file, rendered);\n\t\tthrow new CompileError(\n\t\t\t`The rendered template ${shown} is not valid XML: ${(error as Error).message}`,\n\t\t);\n\t}\n\tif (current.validate) {\n\t\tlog.success(\n\t\t\t`${current.format ? 'Formatted' : 'Validated'} in ${elapsed(start)}`,\n\t\t);\n\t}\n\n\tawait fs.writeFile(file, output);\n\tlog.success(`Written ${styleText('dim', shown)}`);\n}\n\n/** Validates and formats the rendered template, as enabled. */\nfunction finalizeXml({ validate, format }: BuildSetup, xml: string): string {\n\tif (!validate) {\n\t\treturn xml;\n\t}\n\tif (!format) {\n\t\tvalidateXml(xml);\n\t\treturn xml;\n\t}\n\treturn formatXml(xml);\n}\n\nfunction elapsed(start: number): string {\n\treturn styleText('yellow', `${Math.round(performance.now() - start)}ms`);\n}\n\n/** The message of the error and of everything that caused it. */\nfunction formatError(error: unknown): string {\n\tconst lines: string[] = [];\n\tlet current: unknown = error;\n\n\twhile (current !== undefined) {\n\t\tlines.push(\n\t\t\tcurrent instanceof Error\n\t\t\t\t? `${lines.length === 0 ? '' : 'Caused by: '}${current.message}`\n\t\t\t\t: String(current),\n\t\t);\n\t\tcurrent = current instanceof Error ? current.cause : undefined;\n\t}\n\n\treturn lines.join('\\n ');\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BlockStatement, BooleanLiteral, CommentStatement, ContentStatement, Decorator, DecoratorBlock, Expression, Hash, HashPair, Literal, MustacheStatement, Node, NullLiteral, NumberLiteral, PartialBlockStatement, PartialStatement, PathExpression, Position, Program, SourceLocation, Statement, StringLiteral, StripFlags, SubExpression, UndefinedLiteral } from "./types.js";
|
|
2
2
|
import { setHashString } from "./hash.js";
|
|
3
3
|
import { Import, ImportNode, InvalidImport, ValidImport, extractImports, isImportNode } from "./imports.js";
|
|
4
|
-
import { findPartials } from "./partials.js";
|
|
4
|
+
import { HashLiteral, InlinePartial, PartialUsageNode, findInlinePartials, findPartialUsages, findPartials } from "./partials.js";
|
|
5
5
|
import { walk } from "./walk.js";
|
|
6
|
-
export { BlockStatement, BooleanLiteral, CommentStatement, ContentStatement, Decorator, DecoratorBlock, Expression, Hash, HashPair, Import, ImportNode, InvalidImport, Literal, MustacheStatement, Node, NullLiteral, NumberLiteral, PartialBlockStatement, PartialStatement, PathExpression, Position, Program, SourceLocation, Statement, StringLiteral, StripFlags, SubExpression, UndefinedLiteral, ValidImport, extractImports, findPartials, isImportNode, setHashString, walk };
|
|
6
|
+
export { BlockStatement, BooleanLiteral, CommentStatement, ContentStatement, Decorator, DecoratorBlock, Expression, Hash, HashLiteral, HashPair, Import, ImportNode, InlinePartial, InvalidImport, Literal, MustacheStatement, Node, NullLiteral, NumberLiteral, PartialBlockStatement, PartialStatement, PartialUsageNode, PathExpression, Position, Program, SourceLocation, Statement, StringLiteral, StripFlags, SubExpression, UndefinedLiteral, ValidImport, extractImports, findInlinePartials, findPartialUsages, findPartials, isImportNode, setHashString, walk };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { setHashString } from "./hash.js";
|
|
2
2
|
import { walk } from "./walk.js";
|
|
3
3
|
import { extractImports, isImportNode } from "./imports.js";
|
|
4
|
-
import { findPartials } from "./partials.js";
|
|
5
|
-
export { extractImports, findPartials, isImportNode, setHashString, walk };
|
|
4
|
+
import { findInlinePartials, findPartialUsages, findPartials } from "./partials.js";
|
|
5
|
+
export { extractImports, findInlinePartials, findPartialUsages, findPartials, isImportNode, setHashString, walk };
|
|
@@ -2,5 +2,36 @@ import { Program } from "./types.js";
|
|
|
2
2
|
//#region src/compiler/ast/partials.d.ts
|
|
3
3
|
/** Names of the partials used by name, `{{> name}}` and `{{#> name}}`. */
|
|
4
4
|
export declare function findPartials(root: Program): string[];
|
|
5
|
+
export type HashLiteral = string | number | boolean | null | undefined;
|
|
6
|
+
export interface PartialUsageNode {
|
|
7
|
+
/** The name as Handlebars reads it. */
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* The name as it is written, when the source is given, `name` otherwise:
|
|
11
|
+
* Handlebars reads `p.` and `p..a` as `p`, and `p.a.` as `p.a`.
|
|
12
|
+
*/
|
|
13
|
+
raw: string;
|
|
14
|
+
/** 1-based, undefined when the node has no location, made by a plugin. */
|
|
15
|
+
line?: number;
|
|
16
|
+
/** 1-based. */
|
|
17
|
+
column?: number;
|
|
18
|
+
hash: Record<string, HashLiteral | {
|
|
19
|
+
expression: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every use of a partial by name, in the order they are written. With the
|
|
24
|
+
* `source` of the template, as it was parsed, the names are as written.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findPartialUsages(root: Program, source?: string): PartialUsageNode[];
|
|
27
|
+
export interface InlinePartial {
|
|
28
|
+
name: string;
|
|
29
|
+
/** 1-based, undefined when the node has no location. */
|
|
30
|
+
line?: number;
|
|
31
|
+
/** 1-based. */
|
|
32
|
+
column?: number;
|
|
33
|
+
}
|
|
34
|
+
/** The partials defined in the template, `{{#*inline "name"}}`. */
|
|
35
|
+
export declare function findInlinePartials(root: Program): InlinePartial[];
|
|
5
36
|
//#endregion
|
|
6
37
|
//# sourceMappingURL=partials.d.ts.map
|
|
@@ -2,13 +2,91 @@ import { walk } from "./walk.js";
|
|
|
2
2
|
//#region src/compiler/ast/partials.ts
|
|
3
3
|
/** Names of the partials used by name, `{{> name}}` and `{{#> name}}`. */
|
|
4
4
|
function findPartials(root) {
|
|
5
|
-
|
|
5
|
+
return [...new Set(findPartialUsages(root).map(({ name }) => name))];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Every use of a partial by name, in the order they are written. With the
|
|
9
|
+
* `source` of the template, as it was parsed, the names are as written.
|
|
10
|
+
*/
|
|
11
|
+
function findPartialUsages(root, source) {
|
|
12
|
+
const usages = [];
|
|
13
|
+
const offsetOf = source === void 0 ? void 0 : createOffsets(source);
|
|
6
14
|
walk(root, (node) => {
|
|
7
|
-
if ((node.type === "PartialStatement" || node.type === "PartialBlockStatement") && node.name.type === "PathExpression" && !node.name.original.startsWith("@"))
|
|
15
|
+
if ((node.type === "PartialStatement" || node.type === "PartialBlockStatement") && node.name.type === "PathExpression" && !node.name.original.startsWith("@")) {
|
|
16
|
+
const { original, loc } = node.name;
|
|
17
|
+
const written = loc && offsetOf && source !== void 0 ? /^(?:\[[^\]]*\]|[^\s}~()[])+/.exec(source.slice(offsetOf(loc.start.line, loc.start.column)))?.[0] : void 0;
|
|
18
|
+
usages.push(toUsage(node, original, written ?? original));
|
|
19
|
+
}
|
|
8
20
|
});
|
|
9
|
-
return
|
|
21
|
+
return usages;
|
|
22
|
+
}
|
|
23
|
+
/** The partials defined in the template, `{{#*inline "name"}}`. */
|
|
24
|
+
function findInlinePartials(root) {
|
|
25
|
+
const inline = [];
|
|
26
|
+
walk(root, (node) => {
|
|
27
|
+
if (node.type !== "DecoratorBlock" || node.path.original !== "inline") return;
|
|
28
|
+
const [name] = node.params;
|
|
29
|
+
if (name?.type === "StringLiteral") {
|
|
30
|
+
const start = node.loc?.start;
|
|
31
|
+
inline.push({
|
|
32
|
+
name: name.value,
|
|
33
|
+
line: start?.line,
|
|
34
|
+
column: start === void 0 ? void 0 : start.column + 1
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return inline;
|
|
39
|
+
}
|
|
40
|
+
/** The offset in `source` of a line (1-based) and a column (0-based). */
|
|
41
|
+
function createOffsets(source) {
|
|
42
|
+
const starts = [0];
|
|
43
|
+
for (let i = source.indexOf("\n"); i !== -1; i = source.indexOf("\n", i + 1)) starts.push(i + 1);
|
|
44
|
+
return (line, column) => (starts[line - 1] ?? 0) + column;
|
|
45
|
+
}
|
|
46
|
+
function toUsage(node, name, raw) {
|
|
47
|
+
const start = node.loc?.start;
|
|
48
|
+
return {
|
|
49
|
+
name,
|
|
50
|
+
raw,
|
|
51
|
+
line: start?.line,
|
|
52
|
+
column: start === void 0 ? void 0 : start.column + 1,
|
|
53
|
+
hash: hashValues(node.hash)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function hashValues(hash) {
|
|
57
|
+
const values = Object.create(null);
|
|
58
|
+
for (const { key, value } of hash?.pairs ?? []) values[key] = isLiteral(value) ? literalValue(value) : { expression: print(value) };
|
|
59
|
+
return values;
|
|
60
|
+
}
|
|
61
|
+
function isLiteral(node) {
|
|
62
|
+
return node.type !== "PathExpression" && node.type !== "SubExpression";
|
|
63
|
+
}
|
|
64
|
+
function literalValue(node) {
|
|
65
|
+
switch (node.type) {
|
|
66
|
+
case "StringLiteral":
|
|
67
|
+
case "NumberLiteral":
|
|
68
|
+
case "BooleanLiteral": return node.value;
|
|
69
|
+
case "NullLiteral": return null;
|
|
70
|
+
default: return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The expression as it can be written in a template. */
|
|
74
|
+
function print(node) {
|
|
75
|
+
switch (node.type) {
|
|
76
|
+
case "PathExpression": return node.original;
|
|
77
|
+
case "StringLiteral": return JSON.stringify(node.value);
|
|
78
|
+
case "NumberLiteral":
|
|
79
|
+
case "BooleanLiteral": return String(node.value);
|
|
80
|
+
case "NullLiteral": return "null";
|
|
81
|
+
case "UndefinedLiteral": return "undefined";
|
|
82
|
+
case "SubExpression": {
|
|
83
|
+
const parts = [node.path.original, ...node.params.map(print)];
|
|
84
|
+
for (const { key, value } of node.hash?.pairs ?? []) parts.push(`${key}=${print(value)}`);
|
|
85
|
+
return `(${parts.join(" ")})`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
10
88
|
}
|
|
11
89
|
//#endregion
|
|
12
|
-
export { findPartials };
|
|
90
|
+
export { findInlinePartials, findPartialUsages, findPartials };
|
|
13
91
|
|
|
14
92
|
//# sourceMappingURL=partials.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partials.js","names":[],"sources":["../../../src/compiler/ast/partials.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"partials.js","names":[],"sources":["../../../src/compiler/ast/partials.ts"],"sourcesContent":["import type {\n\tExpression,\n\tHash,\n\tPartialBlockStatement,\n\tPartialStatement,\n\tProgram,\n} from './types';\nimport { walk } from './walk';\n\n/** Names of the partials used by name, `{{> name}}` and `{{#> name}}`. */\nexport function findPartials(root: Program): string[] {\n\treturn [...new Set(findPartialUsages(root).map(({ name }) => name))];\n}\n\nexport type HashLiteral = string | number | boolean | null | undefined;\n\nexport interface PartialUsageNode {\n\t/** The name as Handlebars reads it. */\n\tname: string;\n\t/**\n\t * The name as it is written, when the source is given, `name` otherwise:\n\t * Handlebars reads `p.` and `p..a` as `p`, and `p.a.` as `p.a`.\n\t */\n\traw: string;\n\t/** 1-based, undefined when the node has no location, made by a plugin. */\n\tline?: number;\n\t/** 1-based. */\n\tcolumn?: number;\n\thash: Record<string, HashLiteral | { expression: string }>;\n}\n\n/**\n * Every use of a partial by name, in the order they are written. With the\n * `source` of the template, as it was parsed, the names are as written.\n */\nexport function findPartialUsages(\n\troot: Program,\n\tsource?: string,\n): PartialUsageNode[] {\n\tconst usages: PartialUsageNode[] = [];\n\tconst offsetOf = source === undefined ? undefined : createOffsets(source);\n\n\twalk(root, (node) => {\n\t\tif (\n\t\t\t(node.type === 'PartialStatement' ||\n\t\t\t\tnode.type === 'PartialBlockStatement') &&\n\t\t\tnode.name.type === 'PathExpression' &&\n\t\t\t!node.name.original.startsWith('@')\n\t\t) {\n\t\t\tconst { original, loc } = node.name;\n\t\t\t// a name ends at what can not be in one, a space, `}`, `~` or `(`,\n\t\t\t// unless it is in a `[segment]`\n\t\t\tconst written =\n\t\t\t\tloc && offsetOf && source !== undefined\n\t\t\t\t\t? /^(?:\\[[^\\]]*\\]|[^\\s}~()[])+/.exec(\n\t\t\t\t\t\t\tsource.slice(offsetOf(loc.start.line, loc.start.column)),\n\t\t\t\t\t\t)?.[0]\n\t\t\t\t\t: undefined;\n\t\t\tusages.push(toUsage(node, original, written ?? original));\n\t\t}\n\t});\n\n\treturn usages;\n}\n\nexport interface InlinePartial {\n\tname: string;\n\t/** 1-based, undefined when the node has no location. */\n\tline?: number;\n\t/** 1-based. */\n\tcolumn?: number;\n}\n\n/** The partials defined in the template, `{{#*inline \"name\"}}`. */\nexport function findInlinePartials(root: Program): InlinePartial[] {\n\tconst inline: InlinePartial[] = [];\n\n\twalk(root, (node) => {\n\t\tif (node.type !== 'DecoratorBlock' || node.path.original !== 'inline') {\n\t\t\treturn;\n\t\t}\n\t\tconst [name] = node.params;\n\t\tif (name?.type === 'StringLiteral') {\n\t\t\tconst start = node.loc?.start;\n\t\t\tinline.push({\n\t\t\t\tname: name.value,\n\t\t\t\tline: start?.line,\n\t\t\t\tcolumn: start === undefined ? undefined : start.column + 1,\n\t\t\t});\n\t\t}\n\t});\n\n\treturn inline;\n}\n\n/** The offset in `source` of a line (1-based) and a column (0-based). */\nfunction createOffsets(\n\tsource: string,\n): (line: number, column: number) => number {\n\tconst starts = [0];\n\tfor (\n\t\tlet i = source.indexOf('\\n');\n\t\ti !== -1;\n\t\ti = source.indexOf('\\n', i + 1)\n\t) {\n\t\tstarts.push(i + 1);\n\t}\n\treturn (line, column) => (starts[line - 1] ?? 0) + column;\n}\n\nfunction toUsage(\n\tnode: PartialStatement | PartialBlockStatement,\n\tname: string,\n\traw: string,\n): PartialUsageNode {\n\tconst start = node.loc?.start;\n\treturn {\n\t\tname,\n\t\traw,\n\t\tline: start?.line,\n\t\t// Handlebars counts columns from 0\n\t\tcolumn: start === undefined ? undefined : start.column + 1,\n\t\thash: hashValues(node.hash),\n\t};\n}\n\nfunction hashValues(\n\thash: Hash | undefined,\n): Record<string, HashLiteral | { expression: string }> {\n\tconst values: Record<string, HashLiteral | { expression: string }> =\n\t\tObject.create(null);\n\tfor (const { key, value } of hash?.pairs ?? []) {\n\t\tvalues[key] = isLiteral(value)\n\t\t\t? literalValue(value)\n\t\t\t: { expression: print(value) };\n\t}\n\treturn values;\n}\n\nfunction isLiteral(node: Expression): boolean {\n\treturn node.type !== 'PathExpression' && node.type !== 'SubExpression';\n}\n\nfunction literalValue(node: Expression): HashLiteral {\n\tswitch (node.type) {\n\t\tcase 'StringLiteral':\n\t\tcase 'NumberLiteral':\n\t\tcase 'BooleanLiteral':\n\t\t\treturn node.value;\n\t\tcase 'NullLiteral':\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/** The expression as it can be written in a template. */\nfunction print(node: Expression): string {\n\tswitch (node.type) {\n\t\tcase 'PathExpression':\n\t\t\treturn node.original;\n\t\tcase 'StringLiteral':\n\t\t\treturn JSON.stringify(node.value);\n\t\tcase 'NumberLiteral':\n\t\tcase 'BooleanLiteral':\n\t\t\treturn String(node.value);\n\t\tcase 'NullLiteral':\n\t\t\treturn 'null';\n\t\tcase 'UndefinedLiteral':\n\t\t\treturn 'undefined';\n\t\tcase 'SubExpression': {\n\t\t\tconst parts = [node.path.original, ...node.params.map(print)];\n\t\t\tfor (const { key, value } of node.hash?.pairs ?? []) {\n\t\t\t\tparts.push(`${key}=${print(value)}`);\n\t\t\t}\n\t\t\treturn `(${parts.join(' ')})`;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -25,7 +25,10 @@ export interface MustacheStatement extends BaseStatement {
|
|
|
25
25
|
escaped: boolean;
|
|
26
26
|
strip: StripFlags;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
/** `{{* decorator}}` */
|
|
29
|
+
export interface Decorator extends Omit<MustacheStatement, 'type'> {
|
|
30
|
+
type: 'Decorator';
|
|
31
|
+
}
|
|
29
32
|
export interface BlockStatement extends BaseStatement {
|
|
30
33
|
type: 'BlockStatement';
|
|
31
34
|
path: PathExpression;
|
|
@@ -37,7 +40,10 @@ export interface BlockStatement extends BaseStatement {
|
|
|
37
40
|
inverseStrip: StripFlags;
|
|
38
41
|
closeStrip: StripFlags;
|
|
39
42
|
}
|
|
40
|
-
|
|
43
|
+
/** `{{#*inline "name"}}...{{/inline}}` */
|
|
44
|
+
export interface DecoratorBlock extends Omit<BlockStatement, 'type'> {
|
|
45
|
+
type: 'DecoratorBlock';
|
|
46
|
+
}
|
|
41
47
|
export interface PartialStatement extends BaseStatement {
|
|
42
48
|
type: 'PartialStatement';
|
|
43
49
|
name: PathExpression | SubExpression;
|
|
@@ -65,7 +71,7 @@ export interface CommentStatement extends BaseStatement {
|
|
|
65
71
|
value: string;
|
|
66
72
|
strip: StripFlags;
|
|
67
73
|
}
|
|
68
|
-
export type Statement = MustacheStatement | BlockStatement | PartialStatement | PartialBlockStatement | ContentStatement | CommentStatement;
|
|
74
|
+
export type Statement = MustacheStatement | BlockStatement | Decorator | DecoratorBlock | PartialStatement | PartialBlockStatement | ContentStatement | CommentStatement;
|
|
69
75
|
interface BaseExpression extends BaseNode {}
|
|
70
76
|
export interface SubExpression extends BaseExpression {
|
|
71
77
|
type: 'SubExpression';
|
|
@@ -6,11 +6,13 @@ function walk(node, callback) {
|
|
|
6
6
|
for (const statement of node.body) walk(statement, callback);
|
|
7
7
|
break;
|
|
8
8
|
case "MustacheStatement":
|
|
9
|
+
case "Decorator":
|
|
9
10
|
walk(node.path, callback);
|
|
10
11
|
for (const param of node.params) walk(param, callback);
|
|
11
12
|
if (node.hash) walk(node.hash, callback);
|
|
12
13
|
break;
|
|
13
14
|
case "BlockStatement":
|
|
15
|
+
case "DecoratorBlock":
|
|
14
16
|
walk(node.path, callback);
|
|
15
17
|
for (const param of node.params) walk(param, callback);
|
|
16
18
|
if (node.hash) walk(node.hash, callback);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk.js","names":[],"sources":["../../../src/compiler/ast/walk.ts"],"sourcesContent":["import type { Node } from './types';\n\nexport function walk(node: Node, callback: (node: Node) => void): void {\n\tcallback(node);\n\n\tswitch (node.type) {\n\t\tcase 'Program':\n\t\t\tfor (const statement of node.body) {\n\t\t\t\twalk(statement, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'MustacheStatement':\n\t\t\twalk(node.path, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'BlockStatement':\n\t\t\twalk(node.path, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\twalk(node.program, callback);\n\n\t\t\tif (node.inverse) {\n\t\t\t\twalk(node.inverse, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'PartialStatement':\n\t\t\twalk(node.name, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'PartialBlockStatement':\n\t\t\twalk(node.name, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\twalk(node.program, callback);\n\t\t\tbreak;\n\n\t\tcase 'SubExpression':\n\t\t\twalk(node.path, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'Hash':\n\t\t\tfor (const pair of node.pairs) {\n\t\t\t\twalk(pair, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'HashPair':\n\t\t\twalk(node.value, callback);\n\t\t\tbreak;\n\t}\n}\n\nexport * from './types';\n"],"mappings":";AAEA,SAAgB,KAAK,MAAY,UAAsC;CACtE,SAAS,IAAI;CAEb,QAAQ,KAAK,MAAb;EACC,KAAK;GACJ,KAAK,MAAM,aAAa,KAAK,MAC5B,KAAK,WAAW,QAAQ;GAEzB;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB,KAAK,KAAK,SAAS,QAAQ;GAE3B,IAAI,KAAK,SACR,KAAK,KAAK,SAAS,QAAQ;GAE5B;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB,KAAK,KAAK,SAAS,QAAQ;GAC3B;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB;EAED,KAAK;GACJ,KAAK,MAAM,QAAQ,KAAK,OACvB,KAAK,MAAM,QAAQ;GAEpB;EAED,KAAK,YACJ,KAAK,KAAK,OAAO,QAAQ;CAE3B;AACD"}
|
|
1
|
+
{"version":3,"file":"walk.js","names":[],"sources":["../../../src/compiler/ast/walk.ts"],"sourcesContent":["import type { Node } from './types';\n\nexport function walk(node: Node, callback: (node: Node) => void): void {\n\tcallback(node);\n\n\tswitch (node.type) {\n\t\tcase 'Program':\n\t\t\tfor (const statement of node.body) {\n\t\t\t\twalk(statement, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'MustacheStatement':\n\t\tcase 'Decorator':\n\t\t\twalk(node.path, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'BlockStatement':\n\t\tcase 'DecoratorBlock':\n\t\t\twalk(node.path, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\twalk(node.program, callback);\n\n\t\t\tif (node.inverse) {\n\t\t\t\twalk(node.inverse, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'PartialStatement':\n\t\t\twalk(node.name, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'PartialBlockStatement':\n\t\t\twalk(node.name, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\twalk(node.program, callback);\n\t\t\tbreak;\n\n\t\tcase 'SubExpression':\n\t\t\twalk(node.path, callback);\n\t\t\tfor (const param of node.params) {\n\t\t\t\twalk(param, callback);\n\t\t\t}\n\t\t\tif (node.hash) {\n\t\t\t\twalk(node.hash, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'Hash':\n\t\t\tfor (const pair of node.pairs) {\n\t\t\t\twalk(pair, callback);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'HashPair':\n\t\t\twalk(node.value, callback);\n\t\t\tbreak;\n\t}\n}\n\nexport * from './types';\n"],"mappings":";AAEA,SAAgB,KAAK,MAAY,UAAsC;CACtE,SAAS,IAAI;CAEb,QAAQ,KAAK,MAAb;EACC,KAAK;GACJ,KAAK,MAAM,aAAa,KAAK,MAC5B,KAAK,WAAW,QAAQ;GAEzB;EAED,KAAK;EACL,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB;EAED,KAAK;EACL,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB,KAAK,KAAK,SAAS,QAAQ;GAE3B,IAAI,KAAK,SACR,KAAK,KAAK,SAAS,QAAQ;GAE5B;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB,KAAK,KAAK,SAAS,QAAQ;GAC3B;EAED,KAAK;GACJ,KAAK,KAAK,MAAM,QAAQ;GACxB,KAAK,MAAM,SAAS,KAAK,QACxB,KAAK,OAAO,QAAQ;GAErB,IAAI,KAAK,MACR,KAAK,KAAK,MAAM,QAAQ;GAEzB;EAED,KAAK;GACJ,KAAK,MAAM,QAAQ,KAAK,OACvB,KAAK,MAAM,QAAQ;GAEpB;EAED,KAAK,YACJ,KAAK,KAAK,OAAO,QAAQ;CAE3B;AACD"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { CompilePlugin } from "../plugins/index.js";
|
|
1
2
|
import { PluginData } from "../runtime/chunks.js";
|
|
2
3
|
import { Chunks, PartialChunk } from "./chunks.js";
|
|
3
4
|
import { RsbuildOptions } from "./rsbuild.js";
|
|
4
|
-
import {
|
|
5
|
+
import { RuntimeRequest } from "./plugins.js";
|
|
6
|
+
import { BundledRuntime, RuntimeBundle } from "./runtimes.js";
|
|
5
7
|
//#region src/compiler/compile.d.ts
|
|
6
8
|
export interface CompileTemplateOptions {
|
|
7
9
|
root?: string;
|
|
@@ -10,22 +12,29 @@ export interface CompileTemplateOptions {
|
|
|
10
12
|
plugins?: CompilePlugin[];
|
|
11
13
|
/** Rsbuild config for bundling scripts and styles, merged over the defaults. */
|
|
12
14
|
rsbuild?: RsbuildOptions;
|
|
15
|
+
/** Called with the warnings of plugins, `console.warn` by default. */
|
|
16
|
+
onWarn?: (message: string) => void;
|
|
13
17
|
}
|
|
14
18
|
export interface CompiledTemplate {
|
|
19
|
+
/** Version of the compiled output, checked by the runtime. */
|
|
20
|
+
format: number;
|
|
15
21
|
chunks: Chunks;
|
|
16
22
|
/** Templates provided by plugins, by partial name. */
|
|
17
23
|
pluginPartials: Map<string, object>;
|
|
18
24
|
/** Data provided by plugins, by plugin name and data name. */
|
|
19
25
|
pluginData: PluginData;
|
|
26
|
+
/** Runtime halves of the plugins, bundled. */
|
|
27
|
+
bundledPlugins: BundledRuntime[];
|
|
20
28
|
entry: string;
|
|
29
|
+
/** The ES module to render the template with, it imports nothing. */
|
|
21
30
|
source: string;
|
|
22
31
|
}
|
|
23
32
|
/**
|
|
24
33
|
* scan imports -> compile plugins -> validate dependencies -> precompile
|
|
25
|
-
* templates -> bundle assets -> emit module source.
|
|
34
|
+
* templates -> bundle assets and plugin runtimes -> emit module source.
|
|
26
35
|
*/
|
|
27
36
|
export declare function compileTemplate(options?: CompileTemplateOptions): Promise<CompiledTemplate>;
|
|
28
|
-
/** A template compiled up to the bundling of its scripts and
|
|
37
|
+
/** A template compiled up to the bundling of its scripts, styles and plugin runtimes. */
|
|
29
38
|
export interface PreparedTemplate {
|
|
30
39
|
root: string;
|
|
31
40
|
/** Id of the entry template. */
|
|
@@ -33,16 +42,23 @@ export interface PreparedTemplate {
|
|
|
33
42
|
chunks: Chunks;
|
|
34
43
|
partials: PartialChunk[];
|
|
35
44
|
pluginData: PluginData;
|
|
45
|
+
/** The runtime halves of the plugins, to bundle. */
|
|
46
|
+
runtimes: RuntimeRequest[];
|
|
36
47
|
/** The entries each script entry depends on. */
|
|
37
48
|
dependencies: Map<string, string[]>;
|
|
38
49
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
|
|
50
|
+
export interface PrepareOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Called with every template and json file before it is read, and the files
|
|
53
|
+
* plugins add, even when compiling fails.
|
|
54
|
+
*/
|
|
55
|
+
onRead?: (file: string) => void;
|
|
56
|
+
/** Whether the template is compiled again on changes, told to plugins. */
|
|
57
|
+
watch?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** Everything but bundling the scripts, styles and plugin runtimes, for validated options. */
|
|
60
|
+
export declare function prepareTemplate(options?: CompileTemplateOptions, { onRead, watch }?: PrepareOptions): Promise<PreparedTemplate>;
|
|
61
|
+
/** Emits the module source of a template whose scripts, styles and runtimes are bundled. */
|
|
62
|
+
export declare function finishTemplate({ entry, chunks, partials, pluginData }: PreparedTemplate, bundle: RuntimeBundle | undefined): CompiledTemplate;
|
|
47
63
|
//#endregion
|
|
48
64
|
//# sourceMappingURL=compile.d.ts.map
|