From 438f41b55277ba9c4451b9c2a046f24c750d7ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=E5=9F=8E=E5=85=94Maziyo?= <247948681@qq.com> Date: Tue, 21 Feb 2023 16:31:37 +0800 Subject: [PATCH] feat: codegen --- src/compiler/codegen.js | 103 +++++++++++++++++++++++++++++++++++++-- src/compiler/parse.js | 12 +++-- src/examples/test.html | 46 +++++++++++++++++ src/runtime/component.js | 10 ++++ src/runtime/createApp.js | 3 +- src/utils/index.js | 3 ++ 6 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 src/examples/test.html diff --git a/src/compiler/codegen.js b/src/compiler/codegen.js index 0fd9ee0..b0df437 100644 --- a/src/compiler/codegen.js +++ b/src/compiler/codegen.js @@ -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(', ')}]` } diff --git a/src/compiler/parse.js b/src/compiler/parse.js index b7a677a..5151730 100644 --- a/src/compiler/parse.js +++ b/src/compiler/parse.js @@ -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('/>') @@ -153,6 +153,7 @@ function parseTag(context) { props, isSelfClosing, children: [], + directives, codegenNode: undefined // to be created during transform phase } } @@ -174,6 +175,7 @@ function advanceSpaces(context) { function parseAttributes(context) { const props = [] + const directives = [] while ( context.source.length && !context.source.startsWith('>') && @@ -181,10 +183,14 @@ function parseAttributes(context) { !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) { diff --git a/src/examples/test.html b/src/examples/test.html new file mode 100644 index 0000000..c637b91 --- /dev/null +++ b/src/examples/test.html @@ -0,0 +1,46 @@ + + + + + + + Document + + +
+ + + + + diff --git a/src/runtime/component.js b/src/runtime/component.js index 429ad22..3b47cbd 100644 --- a/src/runtime/component.js +++ b/src/runtime/component.js @@ -1,3 +1,4 @@ +import { compile } from '../compiler/compile' import { reactive, effect } from '../reactivity' import { queueJob } from './scheduler' import { normalizeVNode } from './vnode' @@ -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( diff --git a/src/runtime/createApp.js b/src/runtime/createApp.js index f2ded8e..7623e39 100644 --- a/src/runtime/createApp.js +++ b/src/runtime/createApp.js @@ -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) } diff --git a/src/utils/index.js b/src/utils/index.js index bdffae5..c9ce792 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -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) +}