vitempl 0.0.0 → 0.1.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Stepan Sotnikov
3
+ Copyright (c) 2026 Stepan Sotnikov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,15 +1,13 @@
1
1
  # vitempl
2
2
 
3
- Vitempl (словосочетание от vite и template engine) разработка шаблонов на vite. Основные задачи:
3
+ Vite-плагин: собирает шаблоны для серверного рендеринга (Handlebars, askama, Jinja) из компонентов Vue (SFC). Документация — в [docs](../../docs/index.md).
4
4
 
5
- - разработка шаблонов в компонентном стиле, например, разрабатывать в стиле vue, при билде получать шаблоны
6
- - типизация шаблонов
5
+ ## Скрипты
7
6
 
8
- ## Использование
9
-
10
- Для веб приложений, которые используют шаблонизаторы.
11
-
12
- > WIP
7
+ - `npm run dev` — сборка с пересборкой при изменении исходников (dev-режим)
8
+ - `npm run build` — проверка типов и сборка в `dist/`
9
+ - `npm run typecheck` проверка типов
10
+ - `npm run test` — запуск тестов
13
11
 
14
12
  ## Лицензия
15
13
 
@@ -0,0 +1,58 @@
1
+ import { TemplateChildNode } from "@vue/compiler-core";
2
+ //#region src/engine.d.ts
3
+ /**
4
+ * Шаблонизатор, для которого собираются шаблоны.
5
+ *
6
+ * Исходники шаблонов всегда пишутся на Vue SFC, а шаблоны для сервера
7
+ * генерирует адаптер шаблонизатора. Для другого шаблонизатора
8
+ * (Jinja, Tera и т.п.) пишется свой адаптер, исходники и ядро плагина
9
+ * при этом не меняются.
10
+ */
11
+ interface TemplateEngine {
12
+ /** Название шаблонизатора для сообщений */
13
+ name: string;
14
+ /** Расширение собранных шаблонов, например `.hbs` */
15
+ extension: string;
16
+ /**
17
+ * Разбирает шаблон до генерации. Нужен шаблонизаторам, где вызов
18
+ * компонента зависит от самого компонента: например, в Jinja и askama
19
+ * компонент становится макросом, и вызывающий должен знать его параметры.
20
+ */
21
+ analyze?(nodes: TemplateChildNode[], context: GenerateContext): TemplateInfo;
22
+ /** Генерирует шаблон из шаблона Vue SFC */
23
+ generate(nodes: TemplateChildNode[], context: GenerateContext): GenerateResult;
24
+ }
25
+ interface GenerateContext {
26
+ /** Импортированные компоненты: имя тега (`AppHeader`, `app-header`) → имя шаблона (`partials/AppHeader`) */
27
+ components: Map<string, string>;
28
+ /** Имя самого шаблона: путь от папки исходников без расширения */
29
+ name: string;
30
+ /** Шаблон — вход сборки (страница или фрагмент): его данные готовит сервер */
31
+ entry: boolean;
32
+ /**
33
+ * Результаты `analyze` по именам шаблонов. Во время `analyze` заполняется
34
+ * и полным становится только к генерации, а у шаблонизаторов без `analyze`
35
+ * и при проверке одного файла остаётся пустым.
36
+ */
37
+ templates: ReadonlyMap<string, TemplateInfo>;
38
+ }
39
+ interface TemplateInfo {
40
+ /** Данные, к которым обращается шаблон: props компонента, которые он использует */
41
+ params: string[];
42
+ /** В шаблоне есть слот */
43
+ hasSlot: boolean;
44
+ }
45
+ interface GenerateResult {
46
+ code: string;
47
+ errors: TemplateDiagnostic[];
48
+ }
49
+ interface TemplateLocation {
50
+ line: number;
51
+ column: number;
52
+ }
53
+ interface TemplateDiagnostic {
54
+ message: string;
55
+ loc: TemplateLocation;
56
+ }
57
+ //#endregion
58
+ export { TemplateInfo as a, TemplateEngine as i, GenerateResult as n, TemplateLocation as o, TemplateDiagnostic as r, GenerateContext as t };
@@ -0,0 +1,12 @@
1
+ import { i as TemplateEngine } from "../engine-BqkJbOwH.js";
2
+ //#region src/engines/askama.d.ts
3
+ /**
4
+ * askama: компилируемые в Rust шаблоны на основе Jinja.
5
+ *
6
+ * Шаблоны становятся частью бинарника при сборке сервера, данные приходят
7
+ * из структуры, поэтому выражения в них — выражения Rust: условие должно
8
+ * быть `bool`, а необязательное значение проверяется на null
9
+ * (`user != null` превращается в `{% if let Some(user) = user %}`).
10
+ */
11
+ export declare function askama(): TemplateEngine;
12
+ //#endregion
@@ -0,0 +1,28 @@
1
+ import { t as jinjaEngine } from "../jinja-like-BokTIprh.js";
2
+ //#region src/engines/askama.ts
3
+ /**
4
+ * askama: компилируемые в Rust шаблоны на основе Jinja.
5
+ *
6
+ * Шаблоны становятся частью бинарника при сборке сервера, данные приходят
7
+ * из структуры, поэтому выражения в них — выражения Rust: условие должно
8
+ * быть `bool`, а необязательное значение проверяется на null
9
+ * (`user != null` превращается в `{% if let Some(user) = user %}`).
10
+ */
11
+ function askama() {
12
+ return jinjaEngine({
13
+ name: "askama",
14
+ extension: ".html",
15
+ elseIf: "else if",
16
+ assign: "let",
17
+ namespace: "::",
18
+ not: (condition) => `!${condition}`,
19
+ and: "&&",
20
+ or: "||",
21
+ some: (path, alias) => `let Some(${alias}) = ${path}`,
22
+ none: (path) => `${path}.is_none()`,
23
+ binds: true,
24
+ recursion: false
25
+ });
26
+ }
27
+ //#endregion
28
+ export { askama };
@@ -0,0 +1,5 @@
1
+ import { i as TemplateEngine } from "../engine-BqkJbOwH.js";
2
+ //#region src/engines/handlebars.d.ts
3
+ /** Handlebars: компоненты становятся partials, слот по умолчанию — partial-блоком */
4
+ export declare function handlebars(): TemplateEngine;
5
+ //#endregion
@@ -0,0 +1,383 @@
1
+ import { camelize, escapeHtml, isBooleanAttr, isGloballyAllowed, isVoidTag } from "@vue/shared";
2
+ import { ElementTypes, NodeTypes } from "@vue/compiler-core";
3
+ //#region src/engines/handlebars.ts
4
+ /** Директивы, которые обрабатываются на уровне элемента */
5
+ const STRUCTURAL = /* @__PURE__ */ new Set([
6
+ "if",
7
+ "else-if",
8
+ "else",
9
+ "for"
10
+ ]);
11
+ /** Handlebars: компоненты становятся partials, слот по умолчанию — partial-блоком */
12
+ function handlebars() {
13
+ return {
14
+ name: "handlebars",
15
+ extension: ".hbs",
16
+ generate: generateHandlebars
17
+ };
18
+ }
19
+ /**
20
+ * Генерирует шаблон Handlebars из шаблона Vue SFC.
21
+ *
22
+ * Поддерживается подмножество Vue, которое однозначно переводится
23
+ * в Handlebars: пути к данным, v-if/v-else-if/v-else, v-for по массивам,
24
+ * атрибуты, компоненты (partials) и слот по умолчанию (partial-блок).
25
+ * Всё остальное — ошибка компиляции.
26
+ */
27
+ function generateHandlebars(nodes, { components }) {
28
+ const errors = [];
29
+ const error = (message, loc) => {
30
+ errors.push({
31
+ message,
32
+ loc: {
33
+ line: loc.start.line,
34
+ column: loc.start.column
35
+ }
36
+ });
37
+ return "";
38
+ };
39
+ function children(nodes, scope) {
40
+ let out = "";
41
+ for (let i = 0; i < nodes.length; i++) {
42
+ const node = nodes[i];
43
+ if (node.type !== NodeTypes.ELEMENT) {
44
+ out += child(node, scope);
45
+ continue;
46
+ }
47
+ const vIf = findDirective(node, "if");
48
+ if (!vIf) {
49
+ const vElse = findDirective(node, "else-if") ?? findDirective(node, "else");
50
+ out += vElse ? error("v-else без v-if", vElse.loc) : element(node, scope);
51
+ continue;
52
+ }
53
+ const chain = [{
54
+ node,
55
+ directive: vIf
56
+ }];
57
+ for (let j = i + 1; j < nodes.length; j++) {
58
+ const next = nodes[j];
59
+ if (isBlank(next)) continue;
60
+ const directive = next.type === NodeTypes.ELEMENT ? findDirective(next, "else-if") ?? findDirective(next, "else") : void 0;
61
+ if (next.type !== NodeTypes.ELEMENT || !directive) break;
62
+ chain.push({
63
+ node: next,
64
+ directive
65
+ });
66
+ i = j;
67
+ if (directive.name === "else") break;
68
+ }
69
+ out += ifChain(chain, scope);
70
+ }
71
+ return out;
72
+ }
73
+ function child(node, scope) {
74
+ switch (node.type) {
75
+ case NodeTypes.TEXT: return escapeMustache(node.loc.source);
76
+ case NodeTypes.COMMENT: return "";
77
+ case NodeTypes.INTERPOLATION: {
78
+ const path = toPath(node.content, scope, node.loc);
79
+ return path ? `{{${path}}}` : "";
80
+ }
81
+ default: return error("неподдерживаемый узел шаблона", node.loc);
82
+ }
83
+ }
84
+ function ifChain(chain, scope) {
85
+ let helper = "if";
86
+ let out = "";
87
+ chain.forEach(({ node, directive }, index) => {
88
+ const body = element(node, scope);
89
+ if (directive.name === "else") {
90
+ out += `{{else}}${body}`;
91
+ return;
92
+ }
93
+ const condition = toCondition(directive.exp, scope, directive.loc);
94
+ if (!condition) return;
95
+ if (index === 0) {
96
+ helper = condition.helper;
97
+ out += `{{#${condition.helper} ${condition.path}}}${body}`;
98
+ } else out += `{{else ${condition.helper} ${condition.path}}}${body}`;
99
+ });
100
+ return `${out}{{/${helper}}}`;
101
+ }
102
+ function element(node, scope) {
103
+ const vFor = findDirective(node, "for");
104
+ return vFor ? forLoop(node, vFor, scope) : elementBody(node, scope);
105
+ }
106
+ function forLoop(node, directive, scope) {
107
+ const result = directive.forParseResult;
108
+ if (!result) return error("не удалось разобрать v-for", directive.loc);
109
+ if (result.index) return error("v-for с тремя переменными (обход объекта) не поддерживается", directive.loc);
110
+ const params = [];
111
+ for (const param of [result.value, result.key]) {
112
+ if (!param) continue;
113
+ if (!isIdentifier(param)) return error("в v-for поддерживаются только простые имена переменных", directive.loc);
114
+ params.push(param.content);
115
+ }
116
+ const source = toPath(result.source, scope, directive.loc);
117
+ if (!source) return "";
118
+ const inner = {
119
+ ...scope,
120
+ locals: /* @__PURE__ */ new Set([...scope.locals, ...params]),
121
+ depth: scope.depth + 1
122
+ };
123
+ return `{{#each ${source}${params.length ? ` as |${params.join(" ")}|` : ""}}}${elementBody(node, inner)}{{/each}}`;
124
+ }
125
+ function elementBody(node, scope) {
126
+ switch (node.tagType) {
127
+ case ElementTypes.ELEMENT: {
128
+ const open = `<${node.tag}${attributes(node, scope)}>`;
129
+ return isVoidTag(node.tag) ? open : `${open}${children(node.children, scope)}</${node.tag}>`;
130
+ }
131
+ case ElementTypes.TEMPLATE:
132
+ checkDirectives(node);
133
+ return children(node.children, scope);
134
+ case ElementTypes.COMPONENT: return component(node, scope);
135
+ case ElementTypes.SLOT: return slot(node);
136
+ }
137
+ }
138
+ function checkDirectives(node) {
139
+ for (const prop of node.props) if (prop.type === NodeTypes.DIRECTIVE && !STRUCTURAL.has(prop.name)) error(`директива v-${prop.name} здесь не поддерживается`, prop.loc);
140
+ }
141
+ function attributes(node, scope) {
142
+ const staticClass = node.props.find((prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === "class");
143
+ const hasBoundClass = node.props.some((prop) => prop.type === NodeTypes.DIRECTIVE && bindName(prop) === "class");
144
+ let out = "";
145
+ for (const prop of node.props) {
146
+ if (prop.type === NodeTypes.ATTRIBUTE) {
147
+ if (prop !== staticClass || !hasBoundClass) out += ` ${escapeMustache(prop.loc.source)}`;
148
+ continue;
149
+ }
150
+ if (STRUCTURAL.has(prop.name)) continue;
151
+ const name = bindArgument(prop);
152
+ if (!name || name === "key") continue;
153
+ if (name === "class") {
154
+ const value = classValue(prop, scope);
155
+ const base = staticClass?.type === NodeTypes.ATTRIBUTE && staticClass.value ? `${escapeHtml(staticClass.value.content)} ` : "";
156
+ out += ` class="${base}${value}"`;
157
+ continue;
158
+ }
159
+ if (name === "style") {
160
+ error(":style не поддерживается", prop.loc);
161
+ continue;
162
+ }
163
+ out += boundAttribute(name, prop, scope);
164
+ }
165
+ return out;
166
+ }
167
+ /** Имя атрибута из v-bind, для остальных директив — ошибка */
168
+ function bindArgument(directive) {
169
+ if (directive.name !== "bind") {
170
+ error(`директива v-${directive.name} не поддерживается`, directive.loc);
171
+ return;
172
+ }
173
+ const name = bindName(directive);
174
+ if (!name) {
175
+ error("v-bind без имени атрибута и с вычисляемым именем не поддерживается", directive.loc);
176
+ return;
177
+ }
178
+ if (directive.modifiers.length) {
179
+ error("модификаторы v-bind не поддерживаются", directive.loc);
180
+ return;
181
+ }
182
+ return name;
183
+ }
184
+ function classValue(directive, scope) {
185
+ const exp = directive.exp;
186
+ const ast = exp?.ast;
187
+ if (ast && ast.type === "ObjectExpression") {
188
+ const parts = [];
189
+ for (const property of ast.properties) {
190
+ const key = property.type === "ObjectProperty" && !property.computed ? property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : void 0 : void 0;
191
+ const condition = key && property.type === "ObjectProperty" ? toConditionNode(property.value, scope) : void 0;
192
+ if (!key || !condition) {
193
+ error(":class поддерживает объект вида { имя: путь } или { имя: !путь }", directive.loc);
194
+ continue;
195
+ }
196
+ parts.push(`{{#${condition.helper} ${condition.path}}}${escapeHtml(key)}{{/${condition.helper}}}`);
197
+ }
198
+ return parts.join(" ");
199
+ }
200
+ if (ast && ast.type === "StringLiteral") return escapeHtml(ast.value);
201
+ const path = toPath(exp, scope, directive.loc);
202
+ return path ? `{{${path}}}` : "";
203
+ }
204
+ function boundAttribute(name, directive, scope) {
205
+ const exp = sameNameShorthand(directive, name);
206
+ const ast = exp.ast;
207
+ if (isBooleanAttr(name)) {
208
+ if (ast && ast.type === "BooleanLiteral") return ast.value ? ` ${name}` : "";
209
+ const condition = toCondition(exp, scope, directive.loc);
210
+ return condition ? `{{#${condition.helper} ${condition.path}}} ${name}{{/${condition.helper}}}` : "";
211
+ }
212
+ if (ast && (ast.type === "StringLiteral" || ast.type === "NumericLiteral" || ast.type === "BooleanLiteral")) return ` ${name}="${escapeMustache(escapeHtml(String(ast.value)))}"`;
213
+ if (ast && ast.type === "TemplateLiteral") {
214
+ let value = "";
215
+ ast.quasis.forEach((quasi, index) => {
216
+ value += escapeMustache(escapeHtml(quasi.value.cooked ?? ""));
217
+ const expression = ast.expressions[index];
218
+ if (expression) {
219
+ const path = toPathNode(expression, scope, directive.loc);
220
+ value += path ? `{{${path}}}` : "";
221
+ }
222
+ });
223
+ return ` ${name}="${value}"`;
224
+ }
225
+ const path = toPath(exp, scope, directive.loc);
226
+ return path ? ` ${name}="{{${path}}}"` : "";
227
+ }
228
+ function component(node, scope) {
229
+ const name = components.get(node.tag);
230
+ if (!name) return error(`компонент <${node.tag}> не импортирован в <script setup>`, node.loc);
231
+ const hash = [];
232
+ for (const prop of node.props) {
233
+ if (prop.type === NodeTypes.ATTRIBUTE) {
234
+ const key = camelize(prop.name);
235
+ hash.push(`${key}=${prop.value ? toStringLiteral(prop.value.content) : "true"}`);
236
+ continue;
237
+ }
238
+ if (STRUCTURAL.has(prop.name)) continue;
239
+ const argument = bindArgument(prop);
240
+ if (!argument || argument === "key") continue;
241
+ const key = camelize(argument);
242
+ const value = toHashValue(sameNameShorthand(prop, key), scope, prop.loc);
243
+ if (value) hash.push(`${key}=${value}`);
244
+ }
245
+ const call = [
246
+ name,
247
+ "null",
248
+ ...hash
249
+ ].join(" ");
250
+ const content = node.children.filter((child) => !isBlank(child));
251
+ for (const child of content) if (child.type === NodeTypes.ELEMENT && child.tagType === ElementTypes.TEMPLATE && findDirective(child, "slot")) error("именованные слоты не поддерживаются", child.loc);
252
+ return `{{#> ${call}}}${children(content.length ? node.children : [], {
253
+ ...scope,
254
+ depth: scope.depth + 1
255
+ })}{{/${name}}}`;
256
+ }
257
+ function slot(node) {
258
+ if (!node.props.every((prop) => prop.type === NodeTypes.ATTRIBUTE && prop.name === "name" && prop.value?.content === "default")) return error("поддерживается только слот по умолчанию без параметров", node.loc);
259
+ if (node.children.some((child) => !isBlank(child))) return error("запасное содержимое слота не поддерживается", node.loc);
260
+ return "{{#if @partial-block}}{{> @partial-block}}{{/if}}";
261
+ }
262
+ function toCondition(exp, scope, loc) {
263
+ const ast = exp?.ast;
264
+ if (ast) {
265
+ const condition = toConditionNode(ast, scope);
266
+ if (!condition) error(`условие «${exp?.loc.source}» не поддерживается: допустимы путь к данным или его отрицание (!путь)`, loc);
267
+ return condition;
268
+ }
269
+ const path = toPath(exp, scope, loc);
270
+ return path ? {
271
+ helper: "if",
272
+ path
273
+ } : void 0;
274
+ }
275
+ function toConditionNode(node, scope) {
276
+ if (node.type === "UnaryExpression" && node.operator === "!") {
277
+ const path = pathOf(node.argument, scope);
278
+ return path ? {
279
+ helper: "unless",
280
+ path
281
+ } : void 0;
282
+ }
283
+ const path = pathOf(node, scope);
284
+ return path ? {
285
+ helper: "if",
286
+ path
287
+ } : void 0;
288
+ }
289
+ function toHashValue(exp, scope, loc) {
290
+ const ast = exp.ast;
291
+ if (ast && ast.type === "StringLiteral") return toStringLiteral(ast.value);
292
+ if (ast && (ast.type === "NumericLiteral" || ast.type === "BooleanLiteral")) return String(ast.value);
293
+ if (ast && ast.type === "NullLiteral") return "null";
294
+ return toPath(exp, scope, loc);
295
+ }
296
+ function toPath(exp, scope, loc) {
297
+ if (!exp || exp.type !== NodeTypes.SIMPLE_EXPRESSION) {
298
+ error("не удалось разобрать выражение", loc);
299
+ return;
300
+ }
301
+ if (exp.ast === null) return root([exp.content.trim()], scope, loc);
302
+ if (exp.ast === false || exp.ast === void 0) {
303
+ error(`не удалось разобрать выражение «${exp.content}»`, loc);
304
+ return;
305
+ }
306
+ return toPathNode(exp.ast, scope, loc, exp.content);
307
+ }
308
+ function toPathNode(node, scope, loc, source) {
309
+ const segments = segmentsOf(node);
310
+ if (!segments) {
311
+ error(`выражение${source ? ` «${source}»` : ""} не поддерживается: допустимы только пути к данным (a.b.c)`, loc);
312
+ return;
313
+ }
314
+ return root(segments, scope, loc);
315
+ }
316
+ /** Путь Handlebars для выражения вида a.b.c, иначе undefined без ошибки */
317
+ function pathOf(node, scope) {
318
+ const segments = segmentsOf(node);
319
+ return segments && root(segments, scope);
320
+ }
321
+ function root(segments, scope, loc) {
322
+ const [head] = segments;
323
+ if (head.startsWith("$") || isGloballyAllowed(head)) {
324
+ if (loc) error(`"${head}" не поддерживается: допустимы только данные шаблона`, loc);
325
+ return;
326
+ }
327
+ if (scope.locals.has(head)) return segments.join(".");
328
+ return "../".repeat(scope.depth) + segments.join(".");
329
+ }
330
+ return {
331
+ code: children(nodes, {
332
+ locals: /* @__PURE__ */ new Set(),
333
+ depth: 0
334
+ }),
335
+ errors
336
+ };
337
+ }
338
+ function segmentsOf(node) {
339
+ switch (node.type) {
340
+ case "Identifier": return [node.name];
341
+ case "MemberExpression":
342
+ case "OptionalMemberExpression": {
343
+ const object = segmentsOf(node.object);
344
+ if (!object) return;
345
+ if (!node.computed && node.property.type === "Identifier") return [...object, node.property.name];
346
+ if (node.computed && node.property.type === "NumericLiteral") return [...object, `[${node.property.value}]`];
347
+ return;
348
+ }
349
+ }
350
+ }
351
+ function findDirective(node, name) {
352
+ return node.props.find((prop) => prop.type === NodeTypes.DIRECTIVE && prop.name === name);
353
+ }
354
+ function bindName(directive) {
355
+ const arg = directive.arg;
356
+ return arg?.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic ? arg.content : void 0;
357
+ }
358
+ /** `:title` без значения — сокращение для `:title="title"` */
359
+ function sameNameShorthand(directive, name) {
360
+ return directive.exp ?? {
361
+ type: NodeTypes.SIMPLE_EXPRESSION,
362
+ content: camelize(name),
363
+ isStatic: false,
364
+ constType: 0,
365
+ ast: null,
366
+ loc: directive.loc
367
+ };
368
+ }
369
+ function isIdentifier(exp) {
370
+ return exp.type === NodeTypes.SIMPLE_EXPRESSION && /^[A-Za-z_$][\w$]*$/.test(exp.content.trim());
371
+ }
372
+ function isBlank(node) {
373
+ return node.type === NodeTypes.COMMENT || node.type === NodeTypes.TEXT && !node.content.trim();
374
+ }
375
+ /** Экранирует `{{` в статическом тексте, чтобы Handlebars вывел его как есть */
376
+ function escapeMustache(text) {
377
+ return text.replaceAll("{{", "\\{{");
378
+ }
379
+ function toStringLiteral(value) {
380
+ return `"${value.replaceAll("\"", "\\\"")}"`;
381
+ }
382
+ //#endregion
383
+ export { handlebars };
@@ -0,0 +1,11 @@
1
+ import { i as TemplateEngine } from "../engine-BqkJbOwH.js";
2
+ //#region src/engines/jinja.d.ts
3
+ /**
4
+ * Jinja: шаблоны для Jinja2 (Python) и совместимых с ним движков.
5
+ *
6
+ * Шаблоны читаются во время работы сервера, данные — словарь, поэтому
7
+ * условие может быть любым значением, а экранирование включается
8
+ * на стороне сервера (`autoescape`).
9
+ */
10
+ export declare function jinja(): TemplateEngine;
11
+ //#endregion
@@ -0,0 +1,27 @@
1
+ import { t as jinjaEngine } from "../jinja-like-BokTIprh.js";
2
+ //#region src/engines/jinja.ts
3
+ /**
4
+ * Jinja: шаблоны для Jinja2 (Python) и совместимых с ним движков.
5
+ *
6
+ * Шаблоны читаются во время работы сервера, данные — словарь, поэтому
7
+ * условие может быть любым значением, а экранирование включается
8
+ * на стороне сервера (`autoescape`).
9
+ */
10
+ function jinja() {
11
+ return jinjaEngine({
12
+ name: "jinja",
13
+ extension: ".html",
14
+ elseIf: "elif",
15
+ assign: "set",
16
+ namespace: ".",
17
+ not: (condition) => `not ${condition}`,
18
+ and: "and",
19
+ or: "or",
20
+ some: (path) => `${path} is not none`,
21
+ none: (path) => `${path} is none`,
22
+ binds: false,
23
+ recursion: true
24
+ });
25
+ }
26
+ //#endregion
27
+ export { jinja };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,60 @@
1
- declare const _default: {};
2
-
3
- export { _default as default };
1
+ import { a as TemplateInfo, i as TemplateEngine, n as GenerateResult, o as TemplateLocation, r as TemplateDiagnostic, t as GenerateContext } from "./engine-BqkJbOwH.js";
2
+ import { Plugin } from "vite";
3
+ //#region src/check.d.ts
4
+ /**
5
+ * Проверяет исходник шаблона без сборки: ошибки разбора SFC и синтаксис,
6
+ * который не поддерживает шаблонизатор. Те же ошибки выдаёт сборка —
7
+ * функция нужна инструментам вроде линтера.
8
+ */
9
+ export declare function checkSource(source: string, engine: TemplateEngine): TemplateDiagnostic[];
10
+ //#endregion
11
+ //#region src/index.d.ts
12
+ export interface VitemplOptions {
13
+ /** Шаблонизатор, для которого собираются шаблоны */
14
+ engine: TemplateEngine;
15
+ /**
16
+ * Папка исходников шаблонов относительно root
17
+ * @default "src"
18
+ */
19
+ srcDir?: string;
20
+ /**
21
+ * Папка страниц относительно srcDir: страницы оборачиваются в shell и обрабатываются Vite как HTML
22
+ * @default "pages"
23
+ */
24
+ pagesDir?: string;
25
+ /**
26
+ * Папка фрагментов относительно srcDir: фрагменты рендерятся сервером отдельно, без shell
27
+ * @default "fragments"
28
+ */
29
+ fragmentsDir?: string;
30
+ /**
31
+ * HTML-документ относительно root, в который оборачивается каждая страница.
32
+ * Пишется на языке шаблонизатора. Ссылки на ассеты в нём должны быть
33
+ * от корня (`/src/main.ts`), так как после обёртки они разрешаются
34
+ * от папки страницы.
35
+ * @default "index.html"
36
+ */
37
+ shell?: string;
38
+ /**
39
+ * Место в shell, куда вставляется страница
40
+ * @default "<!--app-html-->"
41
+ */
42
+ placeholder?: string;
43
+ /**
44
+ * Папка собранных шаблонов относительно root
45
+ * @default "dist/templates"
46
+ */
47
+ outDir?: string;
48
+ }
49
+ /**
50
+ * Собирает шаблоны для SSR на сервере.
51
+ *
52
+ * Исходники шаблонов — Vue SFC, от страниц и фрагментов обходятся импорты
53
+ * компонентов, и в сборку попадают только используемые. Каждый шаблон
54
+ * генерируется для заданного шаблонизатора, а страницы дополнительно
55
+ * подаются в Vite как HTML-входы: подключения скриптов и стилей
56
+ * заменяются на собранные ассеты.
57
+ */
58
+ export default function vitempl(options: VitemplOptions): Plugin;
59
+ //#endregion
60
+ export type { GenerateContext, GenerateResult, TemplateDiagnostic, TemplateEngine, TemplateInfo, TemplateLocation };