Skip to content

Commit

Permalink
feat: codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmaziyo committed Feb 21, 2023
1 parent 38956a3 commit 438f41b
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 8 deletions.
103 changes: 100 additions & 3 deletions src/compiler/codegen.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,102 @@
import { capitalize } from '../utils'
import { NodeTypes } from './ast'
export function generate(ast) {
return `
console.log('hello world')
`
const codegen = traverseNode(ast)
const exeCode = `
with(_ctx){
const {
createApp,
render,
h,
Text,
Fragment,
nextTick,
reactive,
ref,
computed,
effect,
compile
} = MiniVue
return ${codegen}
}
`
return exeCode
}

export function traverseNode(node) {
switch (node.type) {
case NodeTypes.ROOT:
if (node.children.length > 1) {
return traverseChildren(node.children)
} else if (node.children.length === 1) {
return traverseNode(node.children[0])
}
break
case NodeTypes.ELEMENT:
return createElement(node)
case NodeTypes.TEXT:
return createTextVNode(node)
case NodeTypes.INTERPOLATION:
return createTextVNode(node.content)
default:
break
}
}

function createTextVNode(node) {
const child = createText(node)
return `h(Text,null,${child})`
}

function createText({ isStatic = true, content = '' } = {}) {
return isStatic ? JSON.stringify(content) : content
}

function createElement(node) {
let result = traverseChildren(node.children)
const propsArr = createPropsArr(node)

const propsStr = propsArr.length ? `{${propsArr.join(', ')}}` : `null`

return result
? `h("${node.tag}",${propsStr},${result})`
: `h("${node.tag}",${propsStr})`
}

function createPropsArr({ props, directives }) {
return [
...props.map(({ name, value }) => `${name}:${createText(value)}`),
...directives.map(({ name, exp, arg }) => {
switch (name) {
case 'on':
const value = createText(exp)
if (/^\w+\(\w*\)/.test(value)) {
return `on${capitalize(arg.content)}:($event) => ${value}`
} else {
return `on${capitalize(arg.content)}:${value}`
}
case 'bind':
return `${arg.content}:${createText(exp)}`
case 'html':
return `innerHTML:${createText(exp)}`
default:
break
}
})
]
}

function traverseChildren(children) {
if (!children.length) {
return
}
if (children.length === 1) {
const child = children[0]
if (child.type === NodeTypes.TEXT) {
return createText(child)
} else if (child.type === NodeTypes.INTERPOLATION) {
return child.content.content
}
}
return `[${children.map(child => traverseNode(child)).join(', ')}]`
}
12 changes: 9 additions & 3 deletions src/compiler/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function parseTag(context) {
advanceSpaces(context)

// parse Attributes
let props = parseAttributes(context)
let { props, directives } = parseAttributes(context)

// 检测是否为SelfClose
let isSelfClosing = context.source.startsWith('/>')
Expand All @@ -153,6 +153,7 @@ function parseTag(context) {
props,
isSelfClosing,
children: [],
directives,
codegenNode: undefined // to be created during transform phase
}
}
Expand All @@ -174,17 +175,22 @@ function advanceSpaces(context) {

function parseAttributes(context) {
const props = []
const directives = []
while (
context.source.length &&
!context.source.startsWith('>') &&
// 如果碰到了/>
!context.source.startsWith('/>')
) {
const attr = parseAttribute(context)
props.push(attr)
if (attr.type === NodeTypes.DIRECTIVE) {
directives.push(attr)
} else {
props.push(attr)
}
advanceSpaces(context)
}
return props
return { directives, props }
}

function parseAttribute(context) {
Expand Down
46 changes: 46 additions & 0 deletions src/examples/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="/dist/mini-vue.js"></script>
<script type="text/template" id="template">
<div>{{counter.value}}</div>
<button @click="add">add</button>
</script>
<script>
const {
createApp,
render,
h,
Text,
Fragment,
nextTick,
reactive,
ref,
computed,
effect,
compile
} = MiniVue
const instance = {
template: '#template',
setup() {
const counter = ref(0)
const add = () => {
counter.value++
}
return {
counter,
add
}
}
}
createApp(instance).mount('#app')
</script>
</body>
</html>
10 changes: 10 additions & 0 deletions src/runtime/component.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { compile } from '../compiler/compile'
import { reactive, effect } from '../reactivity'
import { queueJob } from './scheduler'
import { normalizeVNode } from './vnode'
Expand Down Expand Up @@ -51,6 +52,15 @@ export function mountComponent(vnode, container, anchor, patch) {
...instance.props,
...instance.setupState
}
// 使用template的时候没有render,需要parse变成render函数
if (!originalComp.render && originalComp.template) {
let { template } = originalComp
if (template[0] === '#') {
const el = document.querySelector(template)
template = el ? el.innerHTML : ''
}
originalComp.render = new Function('_ctx', compile(template))
}

// 用于主动更新,即ctx里面的值发生变化,主动更新一次
instance.update = effect(
Expand Down
3 changes: 1 addition & 2 deletions src/runtime/createApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ export function createApp(rootComponent) {
rootContainer = document.querySelector(rootContainer)
}

// 当component为空时,直接继承rootContainer
// 当component为空时,直接读取template
if (!isFunction(rootComponent.render) && !rootComponent.template) {
rootComponent.template = rootContainer.innerHTML
}
rootContainer.innerHTML = ''

render(h(rootComponent), rootContainer)
}
Expand Down
3 changes: 3 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ export function isArray(value) {
export function isNumber(value) {
return typeof value === 'number'
}
export function capitalize(value) {
return value[0].toUpperCase() + value.slice(1)
}

0 comments on commit 438f41b

Please sign in to comment.