@katabatic/compiler 1.0.0 → 1.1.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@katabatic/compiler",
3
3
  "license": "MIT",
4
- "version": "1.0.0",
4
+ "version": "1.1.1",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
package/src/builders.js CHANGED
@@ -179,7 +179,7 @@ export function thisExp() {
179
179
  return { type: 'ThisExpression' }
180
180
  }
181
181
 
182
- export function exportDec(declaration) {
182
+ export function exp(declaration) {
183
183
  return { type: 'ExportNamedDeclaration', declaration }
184
184
  }
185
185
 
@@ -192,6 +192,10 @@ export function program(body = []) {
192
192
  }
193
193
 
194
194
  export function func(id, body = []) {
195
+ if (typeof id === 'string') {
196
+ id = { type: 'Identifier', name: id }
197
+ }
198
+
195
199
  return {
196
200
  type: 'FunctionDeclaration',
197
201
  id,
@@ -532,28 +536,6 @@ export function $name(object) {
532
536
  }
533
537
  }
534
538
 
535
- export function $nameDecl(value) {
536
- return {
537
- type: 'ExportNamedDeclaration',
538
- declaration: {
539
- type: 'VariableDeclaration',
540
- declarations: [
541
- {
542
- type: 'VariableDeclarator',
543
- id: {
544
- type: 'Identifier',
545
- name: '$name'
546
- },
547
- init: { type: 'Literal', value }
548
- }
549
- ],
550
- kind: 'const'
551
- },
552
- specifiers: [],
553
- source: null
554
- }
555
- }
556
-
557
539
  export function $set(object, nodeId, attribute, value) {
558
540
  return {
559
541
  type: 'CallExpression',
@@ -933,54 +915,6 @@ export function appendChild(object, node) {
933
915
  }
934
916
  }
935
917
 
936
- export function queueMicrotask(body) {
937
- return {
938
- type: 'ExpressionStatement',
939
- expression: {
940
- type: 'CallExpression',
941
- callee: { type: 'Identifier', name: 'queueMicrotask' },
942
- arguments: [
943
- {
944
- type: 'ArrowFunctionExpression',
945
- id: null,
946
- expression: false,
947
- generator: false,
948
- async: false,
949
- params: [],
950
- body: {
951
- type: 'BlockStatement',
952
- body
953
- }
954
- }
955
- ],
956
- optional: false
957
- }
958
- }
959
- }
960
-
961
- export function connectedMoveCallback() {
962
- return {
963
- type: 'ExpressionStatement',
964
- expression: {
965
- type: 'CallExpression',
966
- callee: {
967
- type: 'MemberExpression',
968
- object: {
969
- type: 'ThisExpression'
970
- },
971
- property: {
972
- type: 'Identifier',
973
- name: 'connectedMoveCallback'
974
- },
975
- computed: false,
976
- optional: false
977
- },
978
- arguments: [],
979
- optional: true
980
- }
981
- }
982
- }
983
-
984
918
  export function $dispose() {
985
919
  return {
986
920
  type: 'CallExpression',
@@ -1010,7 +944,7 @@ export function $dispose() {
1010
944
  }
1011
945
  }
1012
946
 
1013
- export function $lifecycle(value) {
947
+ export function $lifecycle(value, body) {
1014
948
  return {
1015
949
  type: 'CallExpression',
1016
950
  callee: {
@@ -1034,7 +968,21 @@ export function $lifecycle(value) {
1034
968
  computed: false,
1035
969
  optional: false
1036
970
  },
1037
- arguments: [{ type: 'Literal', value }],
971
+ arguments: [
972
+ { type: 'Literal', value },
973
+ {
974
+ type: 'ArrowFunctionExpression',
975
+ id: null,
976
+ expression: false,
977
+ generator: false,
978
+ async: false,
979
+ params: [],
980
+ body: {
981
+ type: 'BlockStatement',
982
+ body
983
+ }
984
+ }
985
+ ],
1038
986
  optional: false
1039
987
  }
1040
988
  }
package/src/checkers.js CHANGED
@@ -6,6 +6,30 @@ export function thisMember(node) {
6
6
  return node.type === 'MemberExpression' && node.object.type === 'ThisExpression'
7
7
  }
8
8
 
9
+ export function thisAssignment(node) {
10
+ if (node.type === 'ExpressionStatement') {
11
+ node = node.expression
12
+ }
13
+
14
+ return (
15
+ node.type === 'AssignmentExpression' &&
16
+ node.left.type === 'MemberExpression' &&
17
+ node.left.object.type === 'ThisExpression'
18
+ )
19
+ }
20
+
21
+ export function expression(node) {
22
+ return node.type === 'ExpressionStatement'
23
+ }
24
+
25
+ export function superCall(node) {
26
+ if (node.type === 'ExpressionStatement') {
27
+ node = node.expression
28
+ }
29
+
30
+ return node.type === 'CallExpression' && node.callee.type === 'Super'
31
+ }
32
+
9
33
  export function privateId(node) {
10
34
  return node.type === 'PrivateIdentifier'
11
35
  }
@@ -117,4 +141,4 @@ export function shadowRootModeAttribute(node) {
117
141
 
118
142
  export function ifBlock(node) {
119
143
  return node.type === 'IfBlock'
120
- }
144
+ }
@@ -36,65 +36,69 @@ function matchSelectors(selectors, template) {
36
36
  if (selectors.length === 0) return false
37
37
 
38
38
  let result = false
39
- walk(template, undefined, {
40
- Element: (node, ctx) => {
41
- ctx.next()
42
39
 
43
- let scopedIdAttribute
44
- let scopedClassAttribute
45
- let scopedElement
40
+ function Visitor(node, ctx) {
41
+ ctx.next()
42
+
43
+ let scopedIdAttribute
44
+ let scopedClassAttribute
45
+ let scopedElement
46
46
 
47
- let classAttribute
47
+ let classAttribute
48
48
 
49
- for (const selector of selectors) {
50
- switch (selector.type) {
51
- case 'TypeSelector':
52
- if (selector.name === node.name) {
53
- classAttribute ??= node.attributes.find((a) => a.name === 'class')
54
- scopedElement = node
55
- scopedClassAttribute = classAttribute
56
- break
57
- }
58
- return
59
- case 'PseudoClassSelector':
49
+ for (const selector of selectors) {
50
+ switch (selector.type) {
51
+ case 'TypeSelector':
52
+ if (selector.name === node.name) {
60
53
  classAttribute ??= node.attributes.find((a) => a.name === 'class')
61
54
  scopedElement = node
62
55
  scopedClassAttribute = classAttribute
63
56
  break
64
- case 'IdSelector':
65
- const idAttribute = node.attributes.find((a) => a.name === 'id')
66
- if (idAttribute?.value[0].data === selector.name) {
67
- scopedIdAttribute = idAttribute
68
- break
69
- }
70
- return
71
- case 'ClassSelector':
72
- classAttribute ??= node.attributes.find((a) => a.name === 'class')
73
- const classes = classAttribute?.value[0].data.split(/\s+/)
74
-
75
- if (classes?.includes(selector.name)) {
76
- scopedClassAttribute = classAttribute
77
- break
78
- }
79
- return
80
- }
81
- }
57
+ }
58
+ return
59
+ case 'PseudoClassSelector':
60
+ classAttribute ??= node.attributes.find((a) => a.name === 'class')
61
+ scopedElement = node
62
+ scopedClassAttribute = classAttribute
63
+ break
64
+ case 'IdSelector':
65
+ const idAttribute = node.attributes.find((a) => a.name === 'id')
66
+ if (idAttribute?.value[0].data === selector.name) {
67
+ scopedIdAttribute = idAttribute
68
+ break
69
+ }
70
+ return
71
+ case 'ClassSelector':
72
+ classAttribute ??= node.attributes.find((a) => a.name === 'class')
73
+ const classes = classAttribute?.value[0].data.split(/\s+/)
82
74
 
83
- if (scopedIdAttribute) {
84
- scopedIdAttribute.metadata ??= {}
85
- scopedIdAttribute.metadata.isScoped = true
86
- }
87
- if (scopedClassAttribute) {
88
- scopedClassAttribute.metadata ??= {}
89
- scopedClassAttribute.metadata.isScoped = true
90
- }
91
- if (scopedElement) {
92
- scopedElement.metadata ??= {}
93
- scopedElement.metadata.isScoped = true
75
+ if (classes?.includes(selector.name)) {
76
+ scopedClassAttribute = classAttribute
77
+ break
78
+ }
79
+ return
94
80
  }
81
+ }
95
82
 
96
- result = true
83
+ if (scopedIdAttribute) {
84
+ scopedIdAttribute.metadata ??= {}
85
+ scopedIdAttribute.metadata.isScoped = true
86
+ }
87
+ if (scopedClassAttribute) {
88
+ scopedClassAttribute.metadata ??= {}
89
+ scopedClassAttribute.metadata.isScoped = true
90
+ }
91
+ if (scopedElement) {
92
+ scopedElement.metadata ??= {}
93
+ scopedElement.metadata.isScoped = true
97
94
  }
95
+
96
+ result = true
97
+ }
98
+
99
+ walk(template, undefined, {
100
+ Element: Visitor,
101
+ CustomElement: Visitor
98
102
  })
99
103
  return result
100
104
  }
package/src/hot.js ADDED
@@ -0,0 +1,37 @@
1
+ import { walk } from 'zimmerframe'
2
+ import * as b from './builders.js'
3
+ import * as is from './checkers.js'
4
+
5
+ export function $hot(program) {
6
+ const stmts1 = []
7
+ const stmts2 = []
8
+
9
+ walk(program, undefined, {
10
+ PropertyDefinition: (node) => {
11
+ if (!node.static) {
12
+ const stmt = b.assignment(b.thisMember(node.key), node.value, '??=')
13
+ stmts1.push(stmt)
14
+ }
15
+ },
16
+ MethodDefinition: (node) => {
17
+ if (node.key.name === 'constructor') {
18
+ for (let _node of node.value.body.body) {
19
+ if (is.superCall(_node)) {
20
+ continue
21
+ }
22
+
23
+ if (is.thisAssignment(_node)) {
24
+ _node = is.expression(_node) ? _node.expression : _node
25
+
26
+ const stmt = { ..._node, operator: '??=' }
27
+ stmts2.push(stmt)
28
+ continue
29
+ }
30
+ stmts2.push(_node)
31
+ }
32
+ }
33
+ }
34
+ })
35
+
36
+ return b.exp(b.func('$hot', [...stmts1, ...stmts2]))
37
+ }
@@ -11,7 +11,29 @@ export function matchModule(name, index, template) {
11
11
 
12
12
  result = true
13
13
  }
14
- }
14
+ },
15
+ TypeSelector: (node, ctx) => {
16
+ if (node.name === name) {
17
+ node.metadata ??= {}
18
+ node.metadata.isModule = true
19
+ node.metadata.index = index
20
+
21
+ result = true
22
+ }
23
+ },
24
+ ...CssTree
15
25
  })
16
26
  return result
17
27
  }
28
+
29
+ export const CssTree = {
30
+ StyleSheet: CssTreeNodeFix,
31
+ SelectorList: CssTreeNodeFix,
32
+ Selector: CssTreeNodeFix,
33
+ PseudoClassSelector: CssTreeNodeFix
34
+ }
35
+
36
+ function CssTreeNodeFix(node, ctx) {
37
+ const children = node.children?.map((c) => ctx.visit(c)) ?? null
38
+ return { ...node, children }
39
+ }
@@ -28,8 +28,6 @@ export function pathStmt(ctx, nodes) {
28
28
  subPath.push(...(Array.isArray(nodes) ? nodes : [nodes]))
29
29
  }
30
30
 
31
- console.log(subPath)
32
-
33
31
  let stmt = b.member('template', 'content')
34
32
  let fragment
35
33
  for (const node of subPath) {
@@ -18,6 +18,8 @@ import { EachBlock } from './visitors/EachBlock.js'
18
18
  import { CustomElement } from './visitors/CustomElement.js'
19
19
  import { AssignmentExpression } from './visitors/AssignmentExpression.js'
20
20
  import { PropertyDefinition } from './visitors/PropertyDefinition.js'
21
+ import { PrivateIdentifier } from './visitors/PrivateIdentifier.js'
22
+ import { TypeSelector } from './visitors/TypeSelector.js'
21
23
 
22
24
  const templateVisitors = {
23
25
  Template,
@@ -33,6 +35,7 @@ const templateVisitors = {
33
35
  IfBlock,
34
36
  EachBlock,
35
37
  Selector,
38
+ TypeSelector,
36
39
  ...CssTree
37
40
  }
38
41
 
@@ -43,7 +46,8 @@ const scriptVisitors = {
43
46
  ImportDeclaration,
44
47
  CallExpression,
45
48
  AssignmentExpression,
46
- PropertyDefinition
49
+ PropertyDefinition,
50
+ PrivateIdentifier
47
51
  }
48
52
 
49
53
  export function transform(ast, analysis, context) {
@@ -3,7 +3,7 @@ import * as b from '../../../builders.js'
3
3
  export function Program(node, ctx) {
4
4
  node = ctx.next() ?? node
5
5
 
6
- const stmt = b.exportDec(
6
+ const stmt = b.exp(
7
7
  b.render([...ctx.state.template.blocks, b.returnStmt(ctx.state.template.template)])
8
8
  )
9
9
  return { ...node, body: [...node.body, stmt] }
@@ -1,6 +1,8 @@
1
1
  import * as b from '../../builders.js'
2
2
 
3
- export function AssignmentExpression(node) {
3
+ export function AssignmentExpression(node, ctx) {
4
+ node = ctx.next() ?? node
5
+
4
6
  if (node.metadata?.isProperty && !node.metadata?.isPrivate) {
5
7
  return { ...node, right: b.$$init(node.left.property.name, node.right) }
6
8
  }
@@ -2,7 +2,7 @@ import * as b from '../../builders.js'
2
2
  import { transformQuerySelector } from '../../css-transform.js'
3
3
 
4
4
  export function CallExpression(node, ctx) {
5
- ctx.next()
5
+ node = ctx.next() ?? node
6
6
 
7
7
  if (node.metadata?.isGetElementById && node.metadata?.isScoped) {
8
8
  const id = node.arguments[0].value
@@ -30,7 +30,7 @@ export function ClassBody(node, ctx) {
30
30
  stmts2.push(stmt)
31
31
  }
32
32
 
33
- if (stmts2.length >= 0) {
33
+ if (stmts1.length >= 0 || stmts2.length >= 0) {
34
34
  return { ...node, body: [...stmts1, ...node.body, ...stmts2] }
35
35
  }
36
36
  }
@@ -14,7 +14,6 @@ export function Element(node, ctx) {
14
14
  function getElementId() {
15
15
  if (!elementId) {
16
16
  elementId = nextElementId(ctx)
17
- console.log('ELEM')
18
17
  const stmt = b.declaration(elementId, pathStmt(ctx))
19
18
  ctx.state.init.elem.push(stmt)
20
19
  }
@@ -11,7 +11,7 @@ export function Fragment(node, ctx) {
11
11
  case 'Text':
12
12
  case 'ExpressionTag':
13
13
  textNode = child
14
- ctx.visit(child, { template, analysis: ctx.state.analysis })
14
+ ctx.visit(child, { ...ctx.state, template })
15
15
  break
16
16
  default:
17
17
  finalize()
@@ -1,10 +1,13 @@
1
1
  import * as b from '../../builders.js'
2
2
 
3
- export function Identifier(node) {
3
+ export function Identifier(node, ctx) {
4
4
  if (node.metadata?.isBlockVar) {
5
5
  return b.call(node)
6
6
  }
7
7
  if (node.metadata?.isProperty || node.metadata?.isMethod) {
8
+ if (ctx.state.context.hot) {
9
+ return b.thisMember(node.metadata?.isPrivate ? b.id(`___${node.name}`) : node)
10
+ }
8
11
  return b.thisMember(b.id(node, node.metadata?.isPrivate))
9
12
  }
10
13
  }
@@ -35,17 +35,13 @@ export function MethodDefinition(node, ctx) {
35
35
  const stmts1 = []
36
36
  const stmts2 = []
37
37
  if (shadowRootMode) {
38
- stmts1.push(b.assignment(b.shadow(), b.attachShadow(shadowRootMode)))
38
+ stmts1.push(b.assignment(b.shadow(), b.attachShadow(shadowRootMode), '??='))
39
39
  }
40
40
  if (node.value.body.body.length > 0) {
41
41
  stmts2.push(b.$boundary(node.value.body.body))
42
42
  }
43
-
44
- const stmt = b.ifStmt(b.$lifecycle('connected'), [
45
- ...stmts1,
46
- ctx.state.template.block,
47
- ...stmts2
48
- ])
43
+
44
+ const stmt = b.$lifecycle('connected', [...stmts1, ctx.state.template.block, ...stmts2])
49
45
 
50
46
  return {
51
47
  ...node,
@@ -60,14 +56,7 @@ export function MethodDefinition(node, ctx) {
60
56
  }
61
57
 
62
58
  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
- ])
59
+ const stmt = b.$lifecycle('disconnected', [b.$dispose(), ...node.value.body.body])
71
60
 
72
61
  return {
73
62
  ...node,
@@ -75,7 +64,7 @@ export function MethodDefinition(node, ctx) {
75
64
  ...node.value,
76
65
  body: {
77
66
  ...node.value.body,
78
- body: [stmt1, stmt2]
67
+ body: [stmt]
79
68
  }
80
69
  }
81
70
  }
@@ -0,0 +1,7 @@
1
+ import * as b from '../../builders.js'
2
+
3
+ export function PrivateIdentifier(node, ctx) {
4
+ if (ctx.state.context.hot) {
5
+ return b.id(`___${node.name}`)
6
+ }
7
+ }
@@ -1,4 +1,5 @@
1
1
  import * as b from '../../builders.js'
2
+ import { $hot } from '../../hot.js'
2
3
 
3
4
  export function Program(node, ctx) {
4
5
  node = ctx.next() ?? node
@@ -17,13 +18,27 @@ export function Program(node, ctx) {
17
18
  stmts1.push(stmt)
18
19
 
19
20
  // 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))
21
+ const style = ctx.state.template.style
22
+ stmt = b.declaration('STYLE', b.template(style))
23
23
  stmts1.push(stmt)
24
24
 
25
25
  // $name
26
- stmt = b.$nameDecl(node.metadata?.customElement.name ?? ctx.state.context.customElementName)
26
+ stmt = b.exp(
27
+ b.declaration(
28
+ '$name',
29
+ b.literal(node.metadata?.customElement.name ?? ctx.state.context.customElementName)
30
+ )
31
+ )
32
+ stmts1.push(stmt)
33
+
34
+ // $class
35
+ stmt = b.exp(b.declaration('$class', b.id(node.metadata?.customElement.className)))
36
+ stmts2.push(stmt)
37
+
38
+ // $shadowRootMode
39
+ stmt = b.exp(
40
+ b.declaration('$shadowRootMode', b.literal(ctx.state.template.metadata?.shadowRootMode))
41
+ )
27
42
  stmts1.push(stmt)
28
43
 
29
44
  // $set
@@ -44,6 +59,11 @@ export function Program(node, ctx) {
44
59
  }
45
60
  stmts1.push(stmt)
46
61
 
62
+ //$hot
63
+ if (ctx.state.context.hot) {
64
+ stmts1.push($hot(node))
65
+ }
66
+
47
67
  // defineCustomElement
48
68
  if (!node.metadata?.hasDefineCustomElement) {
49
69
  stmt = b.defineCustomElement(
@@ -1,6 +1,8 @@
1
1
  import * as b from '../../builders.js'
2
2
 
3
- export function PropertyDefinition(node) {
3
+ export function PropertyDefinition(node, ctx) {
4
+ node = ctx.next() ?? node
5
+
4
6
  if (!node.static && !node.metadata?.isPrivate) {
5
7
  return { ...node, value: b.$$init(node.key.name, node.value ?? b.undefined()) }
6
8
  }
@@ -1,11 +1,20 @@
1
1
  import { generate } from 'css-tree'
2
- import { appendText } from '../../utils/template.js'
2
+ import * as b from '../../builders.js'
3
+ import { appendExpression, appendText } from '../../utils/template.js'
3
4
 
4
5
  export function Style(node, ctx) {
5
6
  node = ctx.next() ?? node
6
7
 
7
8
  const css = generate(node.content)
9
+ const style = css.length > 0 ? `<style>${css}</style>` : ''
10
+
11
+ // handle modules
12
+ const tokens = style.split(/(\$Module_\d+)/)
13
+ for (const token of tokens) {
14
+ token.startsWith('$Module_')
15
+ ? appendExpression(ctx.state.style, b.$name(token))
16
+ : appendText(ctx.state.style, token)
17
+ }
8
18
 
9
- ctx.state.css.push(css)
10
19
  appendText(ctx.state.template, '<!-- -->')
11
20
  }
@@ -1,7 +1,7 @@
1
1
  import * as b from '../../builders.js'
2
2
 
3
3
  export function Template(node, ctx) {
4
- const css = []
4
+ const style = { text: [''], expressions: [] }
5
5
  const template = { text: [''], expressions: [] }
6
6
  const init = { elem: [], text: [], binding: [] }
7
7
  const effects = []
@@ -11,7 +11,7 @@ export function Template(node, ctx) {
11
11
 
12
12
  ctx.visit(node.fragment, {
13
13
  ...ctx.state,
14
- css,
14
+ style,
15
15
  template,
16
16
  init,
17
17
  effects,
@@ -40,5 +40,5 @@ export function Template(node, ctx) {
40
40
  const bodyStmt = [...stmts1, ...stmts2, stmt3]
41
41
  const block = b.$block(bodyStmt)
42
42
 
43
- return { type: 'TemplateMod', metadata: node.metadata, css, template, block }
43
+ return { type: 'TemplateMod', metadata: node.metadata, style, template, block }
44
44
  }
@@ -0,0 +1,6 @@
1
+ export function TypeSelector(node) {
2
+ if (node.metadata?.isModule) {
3
+ const name = `$Module_${node.metadata.index + 1}`
4
+ return { ...node, name }
5
+ }
6
+ }