@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.
Files changed (78) hide show
  1. package/README.md +3 -0
  2. package/package.json +32 -0
  3. package/src/analyse/context.js +31 -0
  4. package/src/analyse/index.js +47 -0
  5. package/src/analyse/visitors/AssignmentExpression.js +14 -0
  6. package/src/analyse/visitors/Attribute.js +11 -0
  7. package/src/analyse/visitors/CallExpression.js +36 -0
  8. package/src/analyse/visitors/ClassBody.js +20 -0
  9. package/src/analyse/visitors/ClassDeclaration.js +5 -0
  10. package/src/analyse/visitors/CustomElement.js +10 -0
  11. package/src/analyse/visitors/EachBlock.js +10 -0
  12. package/src/analyse/visitors/Element.js +16 -0
  13. package/src/analyse/visitors/ExpressionTag.js +9 -0
  14. package/src/analyse/visitors/IfBlock.js +16 -0
  15. package/src/analyse/visitors/ImportDeclaration.js +35 -0
  16. package/src/analyse/visitors/MethodDefinition.js +17 -0
  17. package/src/analyse/visitors/Program.js +24 -0
  18. package/src/analyse/visitors/PropertyDefinition.js +12 -0
  19. package/src/analyse/visitors/Selector.js +23 -0
  20. package/src/analyse/visitors/Template.js +14 -0
  21. package/src/builders.js +1663 -0
  22. package/src/checkers.js +120 -0
  23. package/src/css-matcher.js +100 -0
  24. package/src/css-transform.js +21 -0
  25. package/src/css.js +65 -0
  26. package/src/exp-matcher.js +65 -0
  27. package/src/id-matcher.js +17 -0
  28. package/src/index.js +19 -0
  29. package/src/module-matcher.js +17 -0
  30. package/src/parser/attributes.js +66 -0
  31. package/src/parser/each-block.js +73 -0
  32. package/src/parser/element.js +115 -0
  33. package/src/parser/expression.js +44 -0
  34. package/src/parser/if-block.js +71 -0
  35. package/src/parser/index.js +10 -0
  36. package/src/parser/parser.js +259 -0
  37. package/src/parser/root.js +33 -0
  38. package/src/parser/script.js +59 -0
  39. package/src/parser/style.js +57 -0
  40. package/src/parser/template.js +30 -0
  41. package/src/parser/tokentype.js +75 -0
  42. package/src/router/html.js +18 -0
  43. package/src/router/index.js +130 -0
  44. package/src/transform/context.js +74 -0
  45. package/src/transform/index.js +52 -0
  46. package/src/transform/static/index.js +40 -0
  47. package/src/transform/static/visitors/Attribute.js +27 -0
  48. package/src/transform/static/visitors/EachBlock.js +23 -0
  49. package/src/transform/static/visitors/Element.js +17 -0
  50. package/src/transform/static/visitors/ExpressionTag.js +6 -0
  51. package/src/transform/static/visitors/IfBlock.js +28 -0
  52. package/src/transform/static/visitors/Program.js +10 -0
  53. package/src/transform/static/visitors/Script.js +9 -0
  54. package/src/transform/static/visitors/SlotElement.js +8 -0
  55. package/src/transform/static/visitors/Style.js +9 -0
  56. package/src/transform/static/visitors/Template.js +12 -0
  57. package/src/transform/static/visitors/Text.js +5 -0
  58. package/src/transform/visitors/AssignmentExpression.js +7 -0
  59. package/src/transform/visitors/Attribute.js +79 -0
  60. package/src/transform/visitors/CallExpression.js +17 -0
  61. package/src/transform/visitors/ClassBody.js +36 -0
  62. package/src/transform/visitors/CustomElement.js +43 -0
  63. package/src/transform/visitors/EachBlock.js +39 -0
  64. package/src/transform/visitors/Element.js +49 -0
  65. package/src/transform/visitors/ExpressionTag.js +6 -0
  66. package/src/transform/visitors/Fragment.js +42 -0
  67. package/src/transform/visitors/Identifier.js +10 -0
  68. package/src/transform/visitors/IfBlock.js +55 -0
  69. package/src/transform/visitors/ImportDeclaration.js +21 -0
  70. package/src/transform/visitors/MethodDefinition.js +115 -0
  71. package/src/transform/visitors/Program.js +65 -0
  72. package/src/transform/visitors/PropertyDefinition.js +7 -0
  73. package/src/transform/visitors/Selector.js +41 -0
  74. package/src/transform/visitors/Style.js +11 -0
  75. package/src/transform/visitors/Template.js +44 -0
  76. package/src/transform/visitors/Text.js +5 -0
  77. package/src/utils/misc.js +9 -0
  78. package/src/utils/template.js +15 -0
@@ -0,0 +1,39 @@
1
+ import * as b from '../../builders.js'
2
+ import { appendText } from '../../utils/template.js'
3
+ import { nextElementId, pathStmt } from '../context.js'
4
+
5
+ export function EachBlock(node, ctx) {
6
+ const template = { text: [''], expressions: [] }
7
+ const init = { elem: [], text: [], binding: [] }
8
+ const effects = []
9
+ const animates = []
10
+ const handlers = []
11
+ const blocks = []
12
+
13
+ ctx.visit(node.body, { ...ctx.state, template, init, effects, animates, handlers, blocks })
14
+
15
+ const stmts1 = [
16
+ b.declaration('template', b.createElement('template')),
17
+ b.assignment(b.innerHTML('template'), b.template(template))
18
+ ]
19
+ const stmts2 = [
20
+ ...init.elem,
21
+ ...init.text,
22
+ ...init.binding,
23
+ ...effects,
24
+ ...animates,
25
+ ...handlers,
26
+ ...blocks
27
+ ]
28
+ const stmt3 = b.insertBefore('anchor', b.member('template', 'content'))
29
+
30
+ const anchorId = nextElementId(ctx)
31
+ const bodyStmt = [...stmts1, ...stmts2, stmt3]
32
+ const expressionStmt = ctx.visit(node.expression)
33
+ const anchorStmt = b.declaration(anchorId, pathStmt(ctx, node))
34
+ const blockStmt = b.eachBlock(anchorId, expressionStmt, node.context, node.key, bodyStmt)
35
+
36
+ ctx.state.init.elem.push(anchorStmt)
37
+ ctx.state.blocks.push(blockStmt)
38
+ appendText(ctx.state.template, '<!-- -->')
39
+ }
@@ -0,0 +1,49 @@
1
+ import * as b from '../../builders.js'
2
+ import { appendText } from '../../utils/template.js'
3
+ import { nextBindingId, nextElementId, pathStmt } from '../context.js'
4
+
5
+ export function Element(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
+ const changed = []
12
+
13
+ let elementId
14
+ function getElementId() {
15
+ if (!elementId) {
16
+ elementId = nextElementId(ctx)
17
+ console.log('ELEM')
18
+ const stmt = b.declaration(elementId, pathStmt(ctx))
19
+ ctx.state.init.elem.push(stmt)
20
+ }
21
+ return elementId
22
+ }
23
+
24
+ let bindingId
25
+ function getBindingId() {
26
+ if (!bindingId && node.metadata?.hasBinding) {
27
+ bindingId = nextBindingId(ctx)
28
+ const stmt = b.declaration(bindingId, {
29
+ ...node.metadata?.bindExpression,
30
+ arguments: [getElementId(), ...node.metadata?.bindExpression.arguments]
31
+ })
32
+ ctx.state.init.binding.push(stmt)
33
+ }
34
+ return bindingId
35
+ }
36
+
37
+ appendText(ctx.state.template, `<${node.name}`)
38
+ for (const attribute of attributes) {
39
+ ctx.visit(attribute, { ...ctx.state, getElementId, getBindingId, changed })
40
+ }
41
+ appendText(ctx.state.template, '>')
42
+ ctx.visit(node.fragment, { ...ctx.state, getElementId })
43
+ appendText(ctx.state.template, `</${node.name}>`)
44
+
45
+ if (changed.length > 0) {
46
+ const stmt = b.addEventListener(bindingId, 'changed', changed)
47
+ ctx.state.handlers.push(stmt)
48
+ }
49
+ }
@@ -0,0 +1,6 @@
1
+ import { appendExpression } from "../../utils/template.js"
2
+
3
+ export function ExpressionTag(node, ctx) {
4
+ node = ctx.next() ?? node
5
+ appendExpression(ctx.state.template, node.expression)
6
+ }
@@ -0,0 +1,42 @@
1
+ import * as b from '../../builders.js'
2
+ import { appendText, hasExpression, isEmpty } from '../../utils/template.js'
3
+ import { nextTextId, pathStmt } from '../context.js'
4
+
5
+ export function Fragment(node, ctx) {
6
+ let template = { text: [''], expressions: [] }
7
+ let textNode
8
+
9
+ for (const child of node.nodes) {
10
+ switch (child.type) {
11
+ case 'Text':
12
+ case 'ExpressionTag':
13
+ textNode = child
14
+ ctx.visit(child, { template, analysis: ctx.state.analysis })
15
+ break
16
+ default:
17
+ finalize()
18
+ ctx.visit(child)
19
+ break
20
+ }
21
+ }
22
+ finalize()
23
+
24
+ function finalize() {
25
+ if (hasExpression(template)) {
26
+ const textId = nextTextId(ctx)
27
+ const textStmt = b.declaration(textId, pathStmt(ctx, [node, textNode]))
28
+ ctx.state.init.text.push(textStmt)
29
+
30
+ const effectStmt = b.$effect([
31
+ b.assignment(b.textContent(textId), b.template(template))
32
+ ])
33
+ ctx.state.effects.push(effectStmt)
34
+
35
+ appendText(ctx.state.template, ' ')
36
+ template = { text: [''], expressions: [] }
37
+ } else if (!isEmpty(template)) {
38
+ appendText(ctx.state.template, template.text.join(''))
39
+ template = { text: [''], expressions: [] }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,10 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function Identifier(node) {
4
+ if (node.metadata?.isBlockVar) {
5
+ return b.call(node)
6
+ }
7
+ if (node.metadata?.isProperty || node.metadata?.isMethod) {
8
+ return b.thisMember(b.id(node, node.metadata?.isPrivate))
9
+ }
10
+ }
@@ -0,0 +1,55 @@
1
+ import * as b from '../../builders.js'
2
+ import { appendText } from '../../utils/template.js'
3
+ import { nextElementId, pathStmt } from '../context.js'
4
+
5
+ export function IfBlock(node, ctx) {
6
+ function branchStmt(node, hasElseif = false) {
7
+ if (node) {
8
+ const template = { text: [''], expressions: [] }
9
+ const init = { elem: [], text: [], binding: [] }
10
+ const effects = []
11
+ const animates = []
12
+ const handlers = []
13
+ const blocks = []
14
+
15
+ ctx.visit(node, { ...ctx.state, template, init, effects, animates, handlers, blocks })
16
+
17
+ if (!hasElseif) {
18
+ const stmts1 = [
19
+ b.declaration('template', b.createElement('template')),
20
+ b.assignment(b.innerHTML('template'), b.template(template))
21
+ ]
22
+ const stmts2 = [
23
+ ...init.elem,
24
+ ...init.text,
25
+ ...init.binding,
26
+ ...effects,
27
+ ...animates,
28
+ ...handlers,
29
+ ...blocks
30
+ ]
31
+ const stmt3 = b.insertBefore('anchor', b.member('template', 'content'))
32
+
33
+ return [...stmts1, ...stmts2, stmt3]
34
+ }
35
+ return blocks
36
+ }
37
+ }
38
+
39
+ const testStmt = ctx.visit(node.test)
40
+ const consequentStmt = branchStmt(node.consequent)
41
+ const alternateStmt = branchStmt(node.alternate, node.metadata?.hasElseif)
42
+
43
+ if (!node.elseif) {
44
+ const anchorId = nextElementId(ctx)
45
+ const anchorStmt = b.declaration(anchorId, pathStmt(ctx, node))
46
+ const blockStmt = b.ifBlock(anchorId, testStmt, consequentStmt, alternateStmt)
47
+
48
+ ctx.state.init.elem.push(anchorStmt)
49
+ ctx.state.blocks.push(blockStmt)
50
+ appendText(ctx.state.template, '<!-- -->')
51
+ } else {
52
+ const blockStmt = b.ifBlock(b.id('anchor'), testStmt, consequentStmt, alternateStmt)
53
+ ctx.state.blocks.push(blockStmt)
54
+ }
55
+ }
@@ -0,0 +1,21 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function ImportDeclaration(node, ctx) {
4
+ if (node.metadata?.isModule) {
5
+ const moduleId = `$Module_${node.metadata.index + 1}`
6
+ const raw = undefined
7
+
8
+ let value
9
+ if (ctx.state.context.rewriteRelativeImportExtensions) {
10
+ value = `${node.metadata.dirname}/${node.metadata.filename}.js`
11
+ } else {
12
+ value = `${node.metadata.dirname}/${node.metadata.filename}.${node.metadata.extension}`
13
+ }
14
+
15
+ return {
16
+ ...node,
17
+ specifiers: [...node.specifiers, b.importNamespaceSpecifier(moduleId)],
18
+ source: { ...node.source, value, raw }
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,115 @@
1
+ import * as b from '../../builders.js'
2
+ import { getProgram } from '../context.js'
3
+
4
+ export function MethodDefinition(node, ctx) {
5
+ node = ctx.next() ?? node
6
+
7
+ if (node.key.name === 'constructor') {
8
+ const program = getProgram(ctx)
9
+ const { properties, setters } = program.metadata?.customElement
10
+
11
+ const stmt1 = b.assignment(b.$(), b.$$())
12
+ const stmts2 = []
13
+ for (const setter of setters) {
14
+ stmts2.push(b.$$init(setter))
15
+ }
16
+ for (const property of properties) {
17
+ stmts2.push(b.$instrument(property))
18
+ }
19
+
20
+ return {
21
+ ...node,
22
+ value: {
23
+ ...node.value,
24
+ body: {
25
+ ...node.value.body,
26
+ body: [...node.value.body.body, stmt1, ...stmts2]
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ if (node.key.name === 'connectedCallback') {
33
+ const shadowRootMode = ctx.state.template.metadata?.shadowRootMode
34
+
35
+ const stmts1 = []
36
+ const stmts2 = []
37
+ if (shadowRootMode) {
38
+ stmts1.push(b.assignment(b.shadow(), b.attachShadow(shadowRootMode)))
39
+ }
40
+ if (node.value.body.body.length > 0) {
41
+ stmts2.push(b.$boundary(node.value.body.body))
42
+ }
43
+
44
+ const stmt = b.ifStmt(b.$lifecycle('connected'), [
45
+ ...stmts1,
46
+ ctx.state.template.block,
47
+ ...stmts2
48
+ ])
49
+
50
+ return {
51
+ ...node,
52
+ value: {
53
+ ...node.value,
54
+ body: {
55
+ ...node.value.body,
56
+ body: [stmt]
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ if (node.key.name === 'disconnectedCallback') {
63
+ const stmt1 = b.$lifecycle('disconnected')
64
+ const stmt2 = b.queueMicrotask([
65
+ b.ifStmt(
66
+ b.$lifecycle('microtask'),
67
+ [b.$dispose(), ...node.value.body.body],
68
+ [b.connectedMoveCallback()]
69
+ )
70
+ ])
71
+
72
+ return {
73
+ ...node,
74
+ value: {
75
+ ...node.value,
76
+ body: {
77
+ ...node.value.body,
78
+ body: [stmt1, stmt2]
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ if (node.key.name === 'getAttribute') {
85
+ const stmt1 = b.$trackAttribute(node.value.params)
86
+
87
+ return {
88
+ ...node,
89
+ value: {
90
+ ...node.value,
91
+ params: stmt1.arguments,
92
+ body: {
93
+ ...node.value.body,
94
+ body: [stmt1, ...node.value.body.body]
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ if (node.key.name === 'attributeChangedCallback') {
101
+ const stmt1 = b.$attributeChanged(node.value.params)
102
+
103
+ return {
104
+ ...node,
105
+ value: {
106
+ ...node.value,
107
+ params: stmt1.arguments,
108
+ body: {
109
+ ...node.value.body,
110
+ body: [stmt1, ...node.value.body.body]
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,65 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function Program(node, ctx) {
4
+ node = ctx.next() ?? node
5
+
6
+ let stmt
7
+ const stmts1 = []
8
+ const stmts2 = []
9
+
10
+ // import
11
+ stmt = b.importSpecifier('$$', '@katabatic/runtime')
12
+ stmts1.push(stmt)
13
+
14
+ // html template
15
+ const template = ctx.state.template.template
16
+ stmt = b.declaration('TEMPLATE', b.template(template))
17
+ stmts1.push(stmt)
18
+
19
+ // style
20
+ const css = ctx.state.template.css
21
+ const style = css.length >= 0 ? `<style>${ctx.state.template.css.join('')}</style>` : ''
22
+ stmt = b.declaration('STYLE', b.literal(style))
23
+ stmts1.push(stmt)
24
+
25
+ // $name
26
+ stmt = b.$nameDecl(node.metadata?.customElement.name ?? ctx.state.context.customElementName)
27
+ stmts1.push(stmt)
28
+
29
+ // $set
30
+ const properties = [
31
+ ...node.metadata?.customElement.properties,
32
+ ...node.metadata?.customElement.setters
33
+ ]
34
+ if (properties.length > 0) {
35
+ stmt = b.$setDecl([
36
+ b.ifStmt(
37
+ b.includes(b.array(properties), 'attribute'),
38
+ [b.setProperty('node', 'attribute', 'value')],
39
+ [b.setAttribute('node', 'attribute', 'value')]
40
+ )
41
+ ])
42
+ } else {
43
+ stmt = b.$setDecl([b.setAttribute('node', 'attribute', 'value')])
44
+ }
45
+ stmts1.push(stmt)
46
+
47
+ // defineCustomElement
48
+ if (!node.metadata?.hasDefineCustomElement) {
49
+ stmt = b.defineCustomElement(
50
+ ctx.state.context.customElementName,
51
+ node.metadata?.customElement.className
52
+ )
53
+ stmts2.push(stmt)
54
+ }
55
+
56
+ return {
57
+ ...node,
58
+ body: [
59
+ ...node.body.filter((n) => n.type === 'ImportDeclaration'),
60
+ ...stmts1,
61
+ ...node.body.filter((n) => n.type !== 'ImportDeclaration'),
62
+ ...stmts2
63
+ ]
64
+ }
65
+ }
@@ -0,0 +1,7 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function PropertyDefinition(node) {
4
+ if (!node.static && !node.metadata?.isPrivate) {
5
+ return { ...node, value: b.$$init(node.key.name, node.value ?? b.undefined()) }
6
+ }
7
+ }
@@ -0,0 +1,41 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function Selector(node, ctx) {
4
+ node = CssTreeNodeFix(node, ctx)
5
+
6
+ const children = []
7
+ let unscoped = []
8
+ for (const child of node.children) {
9
+ switch (child.type) {
10
+ case 'Combinator':
11
+ scope()
12
+ children.push(child)
13
+ break
14
+ default:
15
+ unscoped.push(child)
16
+ break
17
+ }
18
+ }
19
+ scope()
20
+
21
+ function scope() {
22
+ if (unscoped.length > 0) {
23
+ unscoped.push(b.classSelector(`ktb-${ctx.state.context.hash}`))
24
+ children.push(...unscoped)
25
+ unscoped = []
26
+ }
27
+ }
28
+
29
+ return { type: 'Selector', children }
30
+ }
31
+
32
+ export const CssTree = {
33
+ StyleSheet: CssTreeNodeFix,
34
+ SelectorList: CssTreeNodeFix,
35
+ PseudoClassSelector: CssTreeNodeFix
36
+ }
37
+
38
+ function CssTreeNodeFix(node, ctx) {
39
+ const children = node.children?.map((c) => ctx.visit(c)) ?? null
40
+ return { ...node, children }
41
+ }
@@ -0,0 +1,11 @@
1
+ import { generate } from 'css-tree'
2
+ import { appendText } from '../../utils/template.js'
3
+
4
+ export function Style(node, ctx) {
5
+ node = ctx.next() ?? node
6
+
7
+ const css = generate(node.content)
8
+
9
+ ctx.state.css.push(css)
10
+ appendText(ctx.state.template, '<!-- -->')
11
+ }
@@ -0,0 +1,44 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function Template(node, ctx) {
4
+ const css = []
5
+ const template = { text: [''], expressions: [] }
6
+ const init = { elem: [], text: [], binding: [] }
7
+ const effects = []
8
+ const animates = []
9
+ const handlers = []
10
+ const blocks = []
11
+
12
+ ctx.visit(node.fragment, {
13
+ ...ctx.state,
14
+ css,
15
+ template,
16
+ init,
17
+ effects,
18
+ animates,
19
+ handlers,
20
+ blocks
21
+ })
22
+
23
+ const rootId = node.metadata?.shadowRootMode ? b.shadow() : b.thisExp()
24
+
25
+ const stmts1 = [
26
+ b.declaration('template', b.createElement('template')),
27
+ b.assignment(b.innerHTML('template'), b.binary('+', b.id('TEMPLATE'), b.id('STYLE')))
28
+ ]
29
+ const stmts2 = [
30
+ ...init.elem,
31
+ ...init.text,
32
+ ...init.binding,
33
+ ...effects,
34
+ ...animates,
35
+ ...handlers,
36
+ ...blocks
37
+ ]
38
+ const stmt3 = b.replaceChildren(rootId, b.member('template', 'content'))
39
+
40
+ const bodyStmt = [...stmts1, ...stmts2, stmt3]
41
+ const block = b.$block(bodyStmt)
42
+
43
+ return { type: 'TemplateMod', metadata: node.metadata, css, template, block }
44
+ }
@@ -0,0 +1,5 @@
1
+ import { appendText } from '../../utils/template.js'
2
+
3
+ export function Text(node, ctx) {
4
+ appendText(ctx.state.template, node.data)
5
+ }
@@ -0,0 +1,9 @@
1
+ export function append(array, value, options) {
2
+ if (options?.spaceWord) {
3
+ if (value && array.at(-1).at(-1) !== '"') {
4
+ array[array.length - 1] += ' '
5
+ }
6
+ }
7
+
8
+ array[array.length - 1] += value
9
+ }
@@ -0,0 +1,15 @@
1
+ export function appendText(template, value) {
2
+ template.text[template.text.length - 1] += value
3
+ }
4
+ export function appendExpression(template, value) {
5
+ template.expressions.push(value)
6
+ template.text.push('')
7
+ }
8
+
9
+ export function hasExpression(template) {
10
+ return template.expressions.length > 0
11
+ }
12
+
13
+ export function isEmpty(template) {
14
+ return template.expressions.length == 0 && template.text.length == 1 && template.text[0] === ''
15
+ }