@katabatic/compiler 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/package.json +32 -0
- package/src/analyse/context.js +31 -0
- package/src/analyse/index.js +47 -0
- package/src/analyse/visitors/AssignmentExpression.js +14 -0
- package/src/analyse/visitors/Attribute.js +11 -0
- package/src/analyse/visitors/CallExpression.js +36 -0
- package/src/analyse/visitors/ClassBody.js +20 -0
- package/src/analyse/visitors/ClassDeclaration.js +5 -0
- package/src/analyse/visitors/CustomElement.js +10 -0
- package/src/analyse/visitors/EachBlock.js +10 -0
- package/src/analyse/visitors/Element.js +16 -0
- package/src/analyse/visitors/ExpressionTag.js +9 -0
- package/src/analyse/visitors/IfBlock.js +16 -0
- package/src/analyse/visitors/ImportDeclaration.js +35 -0
- package/src/analyse/visitors/MethodDefinition.js +17 -0
- package/src/analyse/visitors/Program.js +24 -0
- package/src/analyse/visitors/PropertyDefinition.js +12 -0
- package/src/analyse/visitors/Selector.js +23 -0
- package/src/analyse/visitors/Template.js +14 -0
- package/src/builders.js +1663 -0
- package/src/checkers.js +120 -0
- package/src/css-matcher.js +100 -0
- package/src/css-transform.js +21 -0
- package/src/css.js +65 -0
- package/src/exp-matcher.js +65 -0
- package/src/id-matcher.js +17 -0
- package/src/index.js +19 -0
- package/src/module-matcher.js +17 -0
- package/src/parser/attributes.js +66 -0
- package/src/parser/each-block.js +73 -0
- package/src/parser/element.js +115 -0
- package/src/parser/expression.js +44 -0
- package/src/parser/if-block.js +71 -0
- package/src/parser/index.js +10 -0
- package/src/parser/parser.js +259 -0
- package/src/parser/root.js +33 -0
- package/src/parser/script.js +59 -0
- package/src/parser/style.js +57 -0
- package/src/parser/template.js +30 -0
- package/src/parser/tokentype.js +75 -0
- package/src/router/html.js +18 -0
- package/src/router/index.js +130 -0
- package/src/transform/context.js +74 -0
- package/src/transform/index.js +52 -0
- package/src/transform/static/index.js +40 -0
- package/src/transform/static/visitors/Attribute.js +27 -0
- package/src/transform/static/visitors/EachBlock.js +23 -0
- package/src/transform/static/visitors/Element.js +17 -0
- package/src/transform/static/visitors/ExpressionTag.js +6 -0
- package/src/transform/static/visitors/IfBlock.js +28 -0
- package/src/transform/static/visitors/Program.js +10 -0
- package/src/transform/static/visitors/Script.js +9 -0
- package/src/transform/static/visitors/SlotElement.js +8 -0
- package/src/transform/static/visitors/Style.js +9 -0
- package/src/transform/static/visitors/Template.js +12 -0
- package/src/transform/static/visitors/Text.js +5 -0
- package/src/transform/visitors/AssignmentExpression.js +7 -0
- package/src/transform/visitors/Attribute.js +79 -0
- package/src/transform/visitors/CallExpression.js +17 -0
- package/src/transform/visitors/ClassBody.js +36 -0
- package/src/transform/visitors/CustomElement.js +43 -0
- package/src/transform/visitors/EachBlock.js +39 -0
- package/src/transform/visitors/Element.js +49 -0
- package/src/transform/visitors/ExpressionTag.js +6 -0
- package/src/transform/visitors/Fragment.js +42 -0
- package/src/transform/visitors/Identifier.js +10 -0
- package/src/transform/visitors/IfBlock.js +55 -0
- package/src/transform/visitors/ImportDeclaration.js +21 -0
- package/src/transform/visitors/MethodDefinition.js +115 -0
- package/src/transform/visitors/Program.js +65 -0
- package/src/transform/visitors/PropertyDefinition.js +7 -0
- package/src/transform/visitors/Selector.js +41 -0
- package/src/transform/visitors/Style.js +11 -0
- package/src/transform/visitors/Template.js +44 -0
- package/src/transform/visitors/Text.js +5 -0
- package/src/utils/misc.js +9 -0
- package/src/utils/template.js +15 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { print } from 'esrap'
|
|
2
|
+
import * as b from '../builders.js'
|
|
3
|
+
|
|
4
|
+
export * from './html.js'
|
|
5
|
+
|
|
6
|
+
export function createRouter(registry) {
|
|
7
|
+
const routes = b.object()
|
|
8
|
+
const imports = { page: [], layout: [] }
|
|
9
|
+
|
|
10
|
+
for (const entry of Object.values(registry)) {
|
|
11
|
+
if (entry.isPage) {
|
|
12
|
+
const node = makeNode(routes, entry.route)
|
|
13
|
+
const pageId = b.id(`page_${imports.page.length + 1}`)
|
|
14
|
+
imports.page.push(b.importNamespace(pageId, entry.routerImport))
|
|
15
|
+
node.properties.push(b.property('_page', pageId))
|
|
16
|
+
continue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (entry.isLayout) {
|
|
20
|
+
const node = makeNode(routes, entry.route)
|
|
21
|
+
const layoutId = b.id(`layout_${imports.layout.length + 1}`)
|
|
22
|
+
imports.layout.push(b.importNamespace(layoutId, entry.routerImport))
|
|
23
|
+
node.properties.push(b.property('_layout', layoutId))
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (entry.isIndex) {
|
|
28
|
+
imports.index = b.importSpecifier('renderIndex', entry.routerImport)
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return printRouter({
|
|
34
|
+
imports: print(b.program([...imports.layout, ...imports.page, imports.index])),
|
|
35
|
+
routes: print(b.program([routes]))
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function makeNode(node, path) {
|
|
40
|
+
const tokens = path.split('/').filter((t) => t !== '')
|
|
41
|
+
|
|
42
|
+
for (let token of tokens) {
|
|
43
|
+
const param = token.startsWith('[') && token.endsWith(']') ? token.slice(1, -1) : undefined
|
|
44
|
+
token = param ? '*' : token
|
|
45
|
+
|
|
46
|
+
const prop = node.properties.find((p) => p.key.value === token)
|
|
47
|
+
if (prop) {
|
|
48
|
+
node = prop.value
|
|
49
|
+
} else {
|
|
50
|
+
const child = b.object()
|
|
51
|
+
if (param) {
|
|
52
|
+
child.properties.push(b.property('_param', b.literal(param)))
|
|
53
|
+
}
|
|
54
|
+
node.properties.push(b.property(b.literal(token), child))
|
|
55
|
+
node = child
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return node
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printRouter({ imports, routes }) {
|
|
62
|
+
return {
|
|
63
|
+
code: `${imports.code}
|
|
64
|
+
|
|
65
|
+
const ROUTES = ${routes.code}
|
|
66
|
+
|
|
67
|
+
export async function route(path) {
|
|
68
|
+
const route = resolve(path)
|
|
69
|
+
|
|
70
|
+
if (route) {
|
|
71
|
+
const { modules, ...context } = route
|
|
72
|
+
const data = await load(modules, context)
|
|
73
|
+
const result = render(modules, data)
|
|
74
|
+
|
|
75
|
+
return renderIndex({ body: result })
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function load(modules, context) {
|
|
80
|
+
const data = []
|
|
81
|
+
for (let i = 0; i < modules.length; i++) {
|
|
82
|
+
data.push(modules[i].load(context, data[i - 1]))
|
|
83
|
+
}
|
|
84
|
+
return Promise.all(data)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function render(modules, data) {
|
|
88
|
+
let result
|
|
89
|
+
for (let i = modules.length - 1; i >= 0; i--) {
|
|
90
|
+
result = modules[i].render(data[i], { default: result })
|
|
91
|
+
}
|
|
92
|
+
return result
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolve(path) {
|
|
96
|
+
const modules = []
|
|
97
|
+
const params = {}
|
|
98
|
+
const route = []
|
|
99
|
+
const tokens = path.split('/').filter((t) => t !== '')
|
|
100
|
+
|
|
101
|
+
let node = ROUTES
|
|
102
|
+
|
|
103
|
+
if (node._layout) {
|
|
104
|
+
modules.push(node._layout)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const token of tokens) {
|
|
108
|
+
node = node[token] ?? node['*']
|
|
109
|
+
if (!node) break
|
|
110
|
+
|
|
111
|
+
if (node._layout) {
|
|
112
|
+
modules.push(node._layout)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (node._param) {
|
|
116
|
+
params[node._param] = token
|
|
117
|
+
route.push(\`[\${node._param}]\`)
|
|
118
|
+
} else{
|
|
119
|
+
route.push(token)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (node?._page) {
|
|
124
|
+
modules.push(node._page)
|
|
125
|
+
return { modules, route: \`/\${route.join('/')}\`, params }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
`
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as b from '../builders.js'
|
|
2
|
+
|
|
3
|
+
export function nextElementId(ctx) {
|
|
4
|
+
return b.id(`elem_${ctx.state.init.elem.length + 1}`)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function nextTextId(ctx) {
|
|
8
|
+
return b.id(`text_${ctx.state.init.text.length + 1}`)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function nextBlockId(ctx) {
|
|
12
|
+
return b.id(`block_${ctx.state.blocks.length + 1}`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function nextBindingId(ctx) {
|
|
16
|
+
return b.id(`binding_${ctx.state.init.binding.length + 1}`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function pathStmt(ctx, nodes) {
|
|
20
|
+
const subPath = []
|
|
21
|
+
|
|
22
|
+
for (const node of ctx.path.toReversed()) {
|
|
23
|
+
if (node.type === 'IfBlock' || node.type === 'EachBlock' || node.type === 'Template') break
|
|
24
|
+
subPath.unshift(node)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (nodes) {
|
|
28
|
+
subPath.push(...(Array.isArray(nodes) ? nodes : [nodes]))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(subPath)
|
|
32
|
+
|
|
33
|
+
let stmt = b.member('template', 'content')
|
|
34
|
+
let fragment
|
|
35
|
+
for (const node of subPath) {
|
|
36
|
+
switch (node.type) {
|
|
37
|
+
case 'Fragment':
|
|
38
|
+
fragment = node
|
|
39
|
+
break
|
|
40
|
+
case 'Text':
|
|
41
|
+
case 'ExpressionTag':
|
|
42
|
+
case 'Element':
|
|
43
|
+
case 'CustomElement':
|
|
44
|
+
case 'IfBlock':
|
|
45
|
+
case 'EachBlock':
|
|
46
|
+
stmt = b.sibling(b.child(stmt), position(fragment, node))
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return stmt
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function position(fragment, node) {
|
|
54
|
+
let position = -1
|
|
55
|
+
|
|
56
|
+
const nodes = fragment.nodes
|
|
57
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
58
|
+
if (
|
|
59
|
+
(nodes[i].type === 'ExpressionTag' || nodes[i].type === 'Text') &&
|
|
60
|
+
(nodes[i - 1]?.type === 'ExpressionTag' || nodes[i - 1]?.type === 'Text')
|
|
61
|
+
) {
|
|
62
|
+
// Text and ExpressionTag siblings are collapsed in one node in the html template
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
position++
|
|
67
|
+
if (nodes[i] === node) break
|
|
68
|
+
}
|
|
69
|
+
return position
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getProgram(ctx) {
|
|
73
|
+
return ctx.path[0]
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { walk } from 'zimmerframe'
|
|
2
|
+
import { Program } from './visitors/Program.js'
|
|
3
|
+
import { Identifier } from './visitors/Identifier.js'
|
|
4
|
+
import { ExpressionTag } from './visitors/ExpressionTag.js'
|
|
5
|
+
import { Template } from './visitors/Template.js'
|
|
6
|
+
import { MethodDefinition } from './visitors/MethodDefinition.js'
|
|
7
|
+
import { Element } from './visitors/Element.js'
|
|
8
|
+
import { Text } from './visitors/Text.js'
|
|
9
|
+
import { Fragment } from './visitors/Fragment.js'
|
|
10
|
+
import { Style } from './visitors/Style.js'
|
|
11
|
+
import { Attribute } from './visitors/Attribute.js'
|
|
12
|
+
import { ClassBody } from './visitors/ClassBody.js'
|
|
13
|
+
import { ImportDeclaration } from './visitors/ImportDeclaration.js'
|
|
14
|
+
import { CssTree, Selector } from './visitors/Selector.js'
|
|
15
|
+
import { CallExpression } from './visitors/CallExpression.js'
|
|
16
|
+
import { IfBlock } from './visitors/IfBlock.js'
|
|
17
|
+
import { EachBlock } from './visitors/EachBlock.js'
|
|
18
|
+
import { CustomElement } from './visitors/CustomElement.js'
|
|
19
|
+
import { AssignmentExpression } from './visitors/AssignmentExpression.js'
|
|
20
|
+
import { PropertyDefinition } from './visitors/PropertyDefinition.js'
|
|
21
|
+
|
|
22
|
+
const templateVisitors = {
|
|
23
|
+
Template,
|
|
24
|
+
Identifier,
|
|
25
|
+
ExpressionTag,
|
|
26
|
+
Element,
|
|
27
|
+
CustomElement,
|
|
28
|
+
Text,
|
|
29
|
+
Fragment,
|
|
30
|
+
Style,
|
|
31
|
+
Attribute,
|
|
32
|
+
CallExpression,
|
|
33
|
+
IfBlock,
|
|
34
|
+
EachBlock,
|
|
35
|
+
Selector,
|
|
36
|
+
...CssTree
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const scriptVisitors = {
|
|
40
|
+
Program,
|
|
41
|
+
MethodDefinition,
|
|
42
|
+
ClassBody,
|
|
43
|
+
ImportDeclaration,
|
|
44
|
+
CallExpression,
|
|
45
|
+
AssignmentExpression,
|
|
46
|
+
PropertyDefinition
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function transform(ast, analysis, context) {
|
|
50
|
+
const template = walk(ast.template, { analysis, context }, templateVisitors)
|
|
51
|
+
return walk(ast.script.content, { analysis, template, context }, scriptVisitors)
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { walk } from 'zimmerframe'
|
|
2
|
+
import { Element } from './visitors/Element.js'
|
|
3
|
+
import { Text } from './visitors/Text.js'
|
|
4
|
+
import { Program } from './visitors/Program.js'
|
|
5
|
+
import { Template } from './visitors/Template.js'
|
|
6
|
+
import { ExpressionTag } from './visitors/ExpressionTag.js'
|
|
7
|
+
import { SlotElement } from './visitors/SlotElement.js'
|
|
8
|
+
import { Attribute } from './visitors/Attribute.js'
|
|
9
|
+
import { Style } from './visitors/Style.js'
|
|
10
|
+
import { Script } from './visitors/Script.js'
|
|
11
|
+
import { CssTree, Selector } from '../visitors/Selector.js'
|
|
12
|
+
import { CallExpression } from '../visitors/CallExpression.js'
|
|
13
|
+
import { IfBlock } from './visitors/IfBlock.js'
|
|
14
|
+
import { EachBlock } from './visitors/EachBlock.js'
|
|
15
|
+
|
|
16
|
+
const templateVisitors = {
|
|
17
|
+
Attribute,
|
|
18
|
+
Element,
|
|
19
|
+
SlotElement,
|
|
20
|
+
Text,
|
|
21
|
+
ExpressionTag,
|
|
22
|
+
Template,
|
|
23
|
+
Style,
|
|
24
|
+
Script,
|
|
25
|
+
CallExpression,
|
|
26
|
+
IfBlock,
|
|
27
|
+
EachBlock,
|
|
28
|
+
// common visitors
|
|
29
|
+
Selector,
|
|
30
|
+
...CssTree
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const scriptVisitors = {
|
|
34
|
+
Program
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function transform(ast, analysis, context) {
|
|
38
|
+
const template = walk(ast.template, { analysis, context }, templateVisitors)
|
|
39
|
+
return walk(ast.script.content, { analysis, template, context }, scriptVisitors)
|
|
40
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as b from '../../../builders.js'
|
|
2
|
+
import { append } from '../../../utils/misc.js'
|
|
3
|
+
import { clx } from '../../../css.js'
|
|
4
|
+
|
|
5
|
+
export function Attribute(node, ctx) {
|
|
6
|
+
let value = node.value
|
|
7
|
+
if (node.name === 'id' && node.metadata?.isScoped) {
|
|
8
|
+
if (value[0].type === 'Text') {
|
|
9
|
+
value = [b.text(`${value[0].data}-${ctx.state.context.hash}`)]
|
|
10
|
+
} else {
|
|
11
|
+
value = [...value, b.text(`-${ctx.state.context.hash}`)]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (node.name === 'class' && node.metadata?.isScoped) {
|
|
15
|
+
if (value[0].type === 'Text') {
|
|
16
|
+
value = [b.text(clx(value[0].data, `ktb-${ctx.state.context.hash}`))]
|
|
17
|
+
} else {
|
|
18
|
+
value = [...value, b.text(` ktb-${ctx.state.context.hash}`)]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
append(ctx.state.text, ` ${node.name}="`)
|
|
23
|
+
for (const val of value) {
|
|
24
|
+
ctx.visit(val)
|
|
25
|
+
}
|
|
26
|
+
append(ctx.state.text, `"`)
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as b from '../../../builders.js'
|
|
2
|
+
import { nextBlockId } from '../../context.js'
|
|
3
|
+
|
|
4
|
+
export function EachBlock(node, ctx) {
|
|
5
|
+
const text = ['']
|
|
6
|
+
const expressions = []
|
|
7
|
+
|
|
8
|
+
ctx.visit(node.body, { ...ctx.state, text, expressions })
|
|
9
|
+
|
|
10
|
+
const blockId = nextBlockId(ctx)
|
|
11
|
+
const resultId = b.id('result_')
|
|
12
|
+
const block = b.func(blockId, [
|
|
13
|
+
b.declaration(resultId, b.literal(''), 'let'),
|
|
14
|
+
b.forStmt(node.context, node.expression, [
|
|
15
|
+
b.assignment(resultId, b.template({ text, expressions }), '+=')
|
|
16
|
+
]),
|
|
17
|
+
b.returnStmt(resultId)
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
ctx.state.blocks.push(block)
|
|
21
|
+
ctx.state.text.push('')
|
|
22
|
+
ctx.state.expressions.push(b.call(blockId))
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as b from '../../../builders.js'
|
|
2
|
+
import { append } from '../../../utils/misc.js'
|
|
3
|
+
|
|
4
|
+
export function Element(node, ctx) {
|
|
5
|
+
let attributes = node.attributes
|
|
6
|
+
if (node.metadata?.isScoped && !node.metadata?.hasClass) {
|
|
7
|
+
attributes = [...attributes, b.attribute('class', '', { isScoped: true })]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
append(ctx.state.text, `<${node.name}`)
|
|
11
|
+
for (const attribute of attributes) {
|
|
12
|
+
ctx.visit(attribute)
|
|
13
|
+
}
|
|
14
|
+
append(ctx.state.text, '>')
|
|
15
|
+
ctx.visit(node.fragment)
|
|
16
|
+
append(ctx.state.text, `</${node.name}>`)
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as b from '../../../builders.js'
|
|
2
|
+
import { nextBlockId } from '../../context.js'
|
|
3
|
+
|
|
4
|
+
export function IfBlock(node, ctx) {
|
|
5
|
+
const text = ['']
|
|
6
|
+
const expressions = []
|
|
7
|
+
|
|
8
|
+
ctx.visit(node.consequent, { ...ctx.state, text, expressions })
|
|
9
|
+
const stmt1 = b.returnStmt(b.template({ text, expressions }))
|
|
10
|
+
|
|
11
|
+
let stmt2
|
|
12
|
+
if (node.alternate) {
|
|
13
|
+
const text = ['']
|
|
14
|
+
const expressions = []
|
|
15
|
+
|
|
16
|
+
ctx.visit(node.alternate, { ...ctx.state, text, expressions })
|
|
17
|
+
stmt2 = b.returnStmt(b.template({ text, expressions }))
|
|
18
|
+
} else {
|
|
19
|
+
stmt2 = b.returnStmt(b.literal(''))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const blockId = nextBlockId(ctx)
|
|
23
|
+
const block = b.func(blockId, [b.ifStmt(node.test, [stmt1]), stmt2])
|
|
24
|
+
|
|
25
|
+
ctx.state.blocks.push(block)
|
|
26
|
+
ctx.state.text.push('')
|
|
27
|
+
ctx.state.expressions.push(b.call(blockId))
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as b from '../../../builders.js'
|
|
2
|
+
|
|
3
|
+
export function Program(node, ctx) {
|
|
4
|
+
node = ctx.next() ?? node
|
|
5
|
+
|
|
6
|
+
const stmt = b.exportDec(
|
|
7
|
+
b.render([...ctx.state.template.blocks, b.returnStmt(ctx.state.template.template)])
|
|
8
|
+
)
|
|
9
|
+
return { ...node, body: [...node.body, stmt] }
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as b from '../../../builders.js'
|
|
2
|
+
|
|
3
|
+
export function Template(node, ctx) {
|
|
4
|
+
const text = ['']
|
|
5
|
+
const expressions = []
|
|
6
|
+
const blocks = []
|
|
7
|
+
|
|
8
|
+
ctx.visit(node.fragment, { ...ctx.state, text, expressions, blocks })
|
|
9
|
+
|
|
10
|
+
const template = b.template({ text, expressions })
|
|
11
|
+
return { type: 'TemplateMod', template, blocks }
|
|
12
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as b from '../../builders.js'
|
|
2
|
+
import { clx } from '../../css.js'
|
|
3
|
+
import { appendText, hasExpression } from '../../utils/template.js'
|
|
4
|
+
|
|
5
|
+
export function Attribute(node, ctx) {
|
|
6
|
+
let value = node.value
|
|
7
|
+
if (node.name === 'id' && node.metadata?.isScoped) {
|
|
8
|
+
if (value[0].type === 'Text') {
|
|
9
|
+
value = [b.text(`${value[0].data}-${ctx.state.context.hash}`)]
|
|
10
|
+
} else {
|
|
11
|
+
value = [...value, b.text(`-${ctx.state.context.hash}`)]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (node.name === 'class' && node.metadata?.isScoped) {
|
|
15
|
+
if (value[0].type === 'Text') {
|
|
16
|
+
value = [b.text(clx(value[0].data, `ktb-${ctx.state.context.hash}`))]
|
|
17
|
+
} else {
|
|
18
|
+
value = [...value, b.text(` ktb-${ctx.state.context.hash}`)]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const template = { text: [''], expressions: [] }
|
|
23
|
+
for (const val of value) {
|
|
24
|
+
ctx.visit(val, { ...ctx.state, template })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (hasExpression(template)) {
|
|
28
|
+
// expression attribute
|
|
29
|
+
const elementId = ctx.state.getElementId()
|
|
30
|
+
const moduleId = ctx.state.getModuleId?.()
|
|
31
|
+
const bindingId = ctx.state.getBindingId?.()
|
|
32
|
+
|
|
33
|
+
if (node.name === 'bind') {
|
|
34
|
+
// binding handled in element visitor
|
|
35
|
+
} else if (node.name === 'in' || node.name === 'out' || node.name === 'animate') {
|
|
36
|
+
const direction = node.name === 'animate' ? 'both' : node.name
|
|
37
|
+
const animateExpression = template.expressions[0]
|
|
38
|
+
const stmt = b.$animate(direction, {
|
|
39
|
+
...animateExpression,
|
|
40
|
+
arguments: [elementId, animateExpression.arguments[0] ?? b.object(), b.id('o')]
|
|
41
|
+
})
|
|
42
|
+
ctx.state.animates.push(stmt)
|
|
43
|
+
} else if (node.name.startsWith('on')) {
|
|
44
|
+
const stmt = b.assignment(
|
|
45
|
+
b.member(elementId, node.name),
|
|
46
|
+
b.eventListener(template.expressions[0])
|
|
47
|
+
)
|
|
48
|
+
ctx.state.handlers.push(stmt)
|
|
49
|
+
} else if (moduleId) {
|
|
50
|
+
const stmt = b.$effect([
|
|
51
|
+
b.$set(moduleId, elementId, node.name, template.expressions[0])
|
|
52
|
+
])
|
|
53
|
+
ctx.state.effects.push(stmt)
|
|
54
|
+
} else if (bindingId) {
|
|
55
|
+
const stmt = b.$effect([
|
|
56
|
+
b.logical(
|
|
57
|
+
'||',
|
|
58
|
+
b.$setBindingProp(bindingId, node.name, template.expressions[0]),
|
|
59
|
+
b.setAttribute(elementId, b.literal(node.name), template.expressions[0])
|
|
60
|
+
)
|
|
61
|
+
])
|
|
62
|
+
ctx.state.effects.push(stmt)
|
|
63
|
+
|
|
64
|
+
const stmt2 = b.$getBindingProp(
|
|
65
|
+
bindingId,
|
|
66
|
+
node.name,
|
|
67
|
+
b.assignment(template.expressions[0], b.id('v'))
|
|
68
|
+
)
|
|
69
|
+
ctx.state.changed.push(stmt2)
|
|
70
|
+
} else {
|
|
71
|
+
const stmt = b.$effect([
|
|
72
|
+
b.setAttribute(elementId, b.literal(node.name), template.expressions[0])
|
|
73
|
+
])
|
|
74
|
+
ctx.state.effects.push(stmt)
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
appendText(ctx.state.template, ` ${node.name}="${template.text[0] ?? 'true'}"`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as b from '../../builders.js'
|
|
2
|
+
import { transformQuerySelector } from '../../css-transform.js'
|
|
3
|
+
|
|
4
|
+
export function CallExpression(node, ctx) {
|
|
5
|
+
ctx.next()
|
|
6
|
+
|
|
7
|
+
if (node.metadata?.isGetElementById && node.metadata?.isScoped) {
|
|
8
|
+
const id = node.arguments[0].value
|
|
9
|
+
return { ...node, arguments: [b.literal(id + '-' + ctx.state.context.hash)] }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (node.metadata?.isQuerySelector && node.metadata?.isScoped) {
|
|
13
|
+
const selectorList = node.metadata.selectorList
|
|
14
|
+
const query = transformQuerySelector(selectorList, ctx.state.context)
|
|
15
|
+
return { ...node, arguments: [b.literal(query)] }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as b from '../../builders.js'
|
|
2
|
+
|
|
3
|
+
export function ClassBody(node, ctx) {
|
|
4
|
+
node = ctx.next() ?? node
|
|
5
|
+
|
|
6
|
+
const stmts1 = []
|
|
7
|
+
const stmts2 = []
|
|
8
|
+
if (!node.metadata?.hasConstructor) {
|
|
9
|
+
const stmt = ctx.visit(b.constructor())
|
|
10
|
+
stmts1.push(stmt)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!node.metadata?.hasConnectedCallbackMethod) {
|
|
14
|
+
const stmt = ctx.visit(b.connectedCallback())
|
|
15
|
+
stmts2.push(stmt)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!node.metadata?.hasDisconnectedCallbackMethod) {
|
|
19
|
+
const stmt = ctx.visit(b.disconnectedCallback())
|
|
20
|
+
stmts2.push(stmt)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!node.metadata?.hasAttributeChangedCallback && node.metadata?.hasObservedAttributes) {
|
|
24
|
+
const stmt = ctx.visit(b.attributeChangedCallback())
|
|
25
|
+
stmts2.push(stmt)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!node.metadata?.hasGetAttribute && node.metadata?.hasObservedAttributes) {
|
|
29
|
+
const stmt = ctx.visit(b.getAttribute())
|
|
30
|
+
stmts2.push(stmt)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (stmts2.length >= 0) {
|
|
34
|
+
return { ...node, body: [...stmts1, ...node.body, ...stmts2] }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as b from '../../builders.js'
|
|
2
|
+
import { appendExpression, appendText } from '../../utils/template.js'
|
|
3
|
+
import { nextElementId, pathStmt } from '../context.js'
|
|
4
|
+
|
|
5
|
+
export function CustomElement(node, ctx) {
|
|
6
|
+
let attributes = node.attributes
|
|
7
|
+
if (node.metadata?.isScoped && !node.metadata?.hasClass) {
|
|
8
|
+
attributes = [...attributes, b.attribute('class', '', { isScoped: true })]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let elementId
|
|
12
|
+
function getElementId() {
|
|
13
|
+
if (!elementId) {
|
|
14
|
+
elementId = nextElementId(ctx)
|
|
15
|
+
const stmt = b.declaration(elementId, pathStmt(ctx))
|
|
16
|
+
ctx.state.init.elem.push(stmt)
|
|
17
|
+
}
|
|
18
|
+
return elementId
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (node.metadata?.isModule) {
|
|
22
|
+
const moduleId = b.id(`$Module_${node.metadata.index + 1}`)
|
|
23
|
+
|
|
24
|
+
appendText(ctx.state.template, '<')
|
|
25
|
+
appendExpression(ctx.state.template, b.$name(moduleId))
|
|
26
|
+
for (const attribute of attributes) {
|
|
27
|
+
ctx.visit(attribute, { ...ctx.state, getElementId, getModuleId: () => moduleId })
|
|
28
|
+
}
|
|
29
|
+
appendText(ctx.state.template, '>')
|
|
30
|
+
ctx.visit(node.fragment)
|
|
31
|
+
appendText(ctx.state.template, '</')
|
|
32
|
+
appendExpression(ctx.state.template, b.$name(moduleId))
|
|
33
|
+
appendText(ctx.state.template, '>')
|
|
34
|
+
} else {
|
|
35
|
+
appendText(ctx.state.template, `<${node.name}`)
|
|
36
|
+
for (const attribute of attributes) {
|
|
37
|
+
ctx.visit(attribute, { ...ctx.state, getElementId })
|
|
38
|
+
}
|
|
39
|
+
appendText(ctx.state.template, '>')
|
|
40
|
+
ctx.visit(node.fragment)
|
|
41
|
+
appendText(ctx.state.template, `</${node.name}>`)
|
|
42
|
+
}
|
|
43
|
+
}
|