forked from sveltejs/svelte
/
Renderer.ts
123 lines (101 loc) · 2.81 KB
/
Renderer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import AwaitBlock from './handlers/AwaitBlock';
import Comment from './handlers/Comment';
import DebugTag from './handlers/DebugTag';
import DynamicElement from './handlers/DynamicElement';
import EachBlock from './handlers/EachBlock';
import Element from './handlers/Element';
import Head from './handlers/Head';
import HtmlTag from './handlers/HtmlTag';
import IfBlock from './handlers/IfBlock';
import InlineComponent from './handlers/InlineComponent';
import KeyBlock from './handlers/KeyBlock';
import Slot from './handlers/Slot';
import Tag from './handlers/Tag';
import Text from './handlers/Text';
import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/interfaces';
import { Expression, TemplateLiteral, Identifier } from 'estree';
import { escape_template } from '../utils/stringify';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
function noop() {}
const handlers: Record<string, Handler> = {
AwaitBlock,
Body: noop,
Comment,
DebugTag,
DynamicElement,
EachBlock,
Element,
Head,
IfBlock,
InlineComponent,
KeyBlock,
MustacheTag: Tag, // TODO MustacheTag is an anachronism
Options: noop,
RawMustacheTag: HtmlTag,
Slot,
Text,
Title,
Window: noop
};
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number };
head_id?: string;
}
export default class Renderer {
has_bindings = false;
name: Identifier;
stack: Array<{ current: { value: string }; literal: TemplateLiteral }> = [];
current: { value: string }; // TODO can it just be `current: string`?
literal: TemplateLiteral;
targets: AppendTarget[] = [];
constructor({ name }) {
this.name = name;
this.push();
}
add_string(str: string) {
this.current.value += escape_template(str);
}
add_expression(node: Expression) {
this.literal.quasis.push({
type: 'TemplateElement',
value: { raw: this.current.value, cooked: null },
tail: false
});
this.literal.expressions.push(node);
this.current.value = '';
}
push() {
const current = this.current = { value: '' };
const literal = this.literal = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
this.stack.push({ current, literal });
}
pop() {
this.literal.quasis.push({
type: 'TemplateElement',
value: { raw: this.current.value, cooked: null },
tail: true
});
const popped = this.stack.pop();
const last = this.stack[this.stack.length - 1];
if (last) {
this.literal = last.literal;
this.current = last.current;
}
return popped.literal;
}
render(nodes: INode[], options: RenderOptions) {
nodes.forEach(node => {
const handler = handlers[node.type];
if (!handler) {
throw new Error(`No handler for '${node.type}' nodes`);
}
handler(node, this, options);
});
}
}