Skip to content

Commit

Permalink
codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
bulv0620 committed Jul 28, 2022
1 parent 736c38e commit dca6bf3
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 29 deletions.
84 changes: 71 additions & 13 deletions src/compiler/codegen.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { capitalize } from "../utils";
import { NodeTypes } from "./ast";
import { NodeTypes, ElementTypes } from "./ast";

export function generate(ast) {
const res = traverseNode(ast);
const code = `
with(ctx){
const {h, Text, Fragment} = VueLite;
const {renderList, h, Text, Fragment, withModel, resolveComponent} = VueLite;
return ${res};
}
`
return code;
}

function traverseNode(node) {
function traverseNode(node, parent) {
switch (node.type) {
case NodeTypes.ROOT:
if (node.children.length === 1) {
return traverseNode(node.children[0]);
return traverseNode(node.children[0], node);
}
const res = traverseChildren(node);
return res;
case NodeTypes.ELEMENT:
return createElementVNode(node);
return resolveElementASTNode(node, parent);
case NodeTypes.TEXT:
return createTextVNode(node);
case NodeTypes.INTERPOLATION:
Expand All @@ -43,45 +43,94 @@ function createText({ isStatic = true, content = '' } = {}) {
return isStatic ? JSON.stringify(content) : content;
}

function resolveElementASTNode(node, parent) {

const ifNode = pluck(node.directives, 'if') || pluck(node.directives, 'else-if');
if (ifNode) {
const { exp } = ifNode;
let consequent = resolveElementASTNode(node, parent);
let alternate;

const { children } = parent;
const i = children.findIndex(child => child === node) + 1;

for (; i < children.length; i++) {
const sibling = children[i];
if (sibling.type === NodeTypes.TEXT && !sibling.content.trim()) {
children.splice(i--, 1);
continue;
}

if (sibling.type === NodeTypes.ELEMENT) {
if (pluck(sibling.directives, 'else') || pluck(sibling.directives, 'else-if', false)) {
alternate = resolveElementASTNode(sibling, parent);
children.splice(i, 1);
}
}
break;
}

return `${exp.content} ? ${consequent} : ${alternate || createTextVNode()}`;
}

const forNode = pluck(node.directives, 'for');
if (forNode) {
const { exp } = forNode;
const [args, source] = exp.content.split(/\sin\s|\sof\s/);
return `h(Fragment, null, renderList(${source.trim()}, ${args.trim()} => ${resolveElementASTNode(node, parent)}))`;
}

return createElementVNode(node);
}

function createElementVNode(node) {
const { children } = node;
const tag = JSON.stringify(node.tag);
const tag = node.tagType === ElementTypes.ELEMENT ? JSON.stringify(node.tag) : `resolveComponent("${node.tag}")`;


const propArr = createPropArr(node);
const propStr = propArr.length ? `{${propArr.join(', ')}}` : 'null';
let propStr = propArr.length ? `{${propArr.join(', ')}}` : 'null';

const vmodelNode = pluck(node.directives, 'model');
if (vmodelNode) {
const {exp} = vmodelNode;
const getter = `() => ${createText(exp)}`;
const setter = `(value) => ${createText(exp)} = value`
propStr = `withModel(${tag}, ${propStr}, ${getter}, ${setter})`;
}

if (!children.length) {
if (propStr === 'null') {
return `h(${tag})`
}
return `h(${tag}, propStr)`;
return `h(${tag}, ${propStr})`;
}

let childrenStr = traverseChildren(node);
return `h(${tag}, ${propStr}, ${childrenStr})`
}

function createPropArr(node) {
const {props, directives} = node;
const { props, directives } = node;

return [
...props.map(prop => `${prop.name}: ${createText(prop.value)}`),
...directives.map(dir => {
switch(dir.name){
switch (dir.name) {
case 'bind':
return `${dir.arg.content}: ${createText(dir.exp)}`;
case 'on':
const eventName = `on${capitalize(dir.arg.content)}`
let exp = dir.exp.content;

if(/\([^)]*\)$/.test(exp) && !exp.includes('=>')){
if (/\([^)]*\)$/.test(exp) && !exp.includes('=>')) {
exp = `$event => (${exp})`;
}

return `${eventName}: ${exp}`;
case 'html':
return `innerHTML: ${createText(dir.exp)}`;
default:
default:
return `${dir.name}: ${createText(dir.exp)}`;
}
})
Expand All @@ -103,8 +152,17 @@ function traverseChildren(node) {
const list = [];
for (let i = 0; i < children.length; i++) {
const child = children[i];
list.push(traverseNode(child));
list.push(traverseNode(child, node));
}

return `[${list.join(', ')}]`
}

function pluck(directives, name, remove = true) {
const index = directives.findIndex(dir => dir.name === name);
const dir = directives[index];
if (remove && index > -1) {
directives.splice(index, 1);
}
return dir;
}
74 changes: 69 additions & 5 deletions src/examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,84 @@
</head>
<body>
<div id="app">
Counter
<div>{{count.value}}</div>
<button @click="add">click</button>
<h1>v-for:</h1>
<ul>
<li v-for="(item, index) in items" :key="item.id">{{index}} : {{item.name}}</li>
</ul>

<h1>v-if:</h1>
<div v-if="show.value">show</div>
<div v-else>hide</div>
<button @click="toggle">toggle</button>

<h1>reactivity:</h1>
<div class="counter">{{count.value}}</div>
<button @click="add">add</button>

<h1>v-model:</h1>
<ul>
<li v-for="(value, key, index) in formState" :key="key">{{index}} - {{key}} : {{value}}</li>
</ul>
<input type="text" v-model="formState.inputValue"> <br>
<input type="radio" v-model="formState.radioValue" value="radio">redio <br>
<input type="checkbox" v-model="formState.checkValue" value="check1">check1 <br>
<input type="checkbox" v-model="formState.checkValue" value="check2">check2 <br>
<input type="checkbox" v-model="formState.checkValue" value="check3">check3 <br>
<br>
<h1>component:</h1>
<my-component></my-component>

</div>
</body>
</html>
<script>
const {createApp, ref} = VueLite;
const {createApp, ref, reactive, h} = VueLite;

const myComponent = {
setup() {
const txt = 'hello world';
return{txt}
},

render(ctx){
return h('div', null, ctx.txt);
}
}

createApp({
components: {
myComponent
},
setup() {
const items = reactive([
{
id: 1,
name: 'foo'
},
{
id: 2,
name: 'bar'
},
{
id: 3,
name: 'baz'
}
])

const count = ref(0);
const add = () => count.value++;

const show = ref(true);
const toggle = () => show.value = !show.value;

const formState = reactive({
inputValue: '',
radioValue: '',
checkValue: []
})

return {
count, add
count, add, items, show, toggle, formState
}
}
}).mount('#app');
Expand Down
10 changes: 8 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
h,
Text,
Fragment,
nextTick
nextTick,
renderList,
withModel,
resolveComponent
} from './runtime';
import { reactive, ref, computed, effect } from './reactive';

Expand All @@ -20,5 +23,8 @@ export const VueLite = (window.VueLite = {
ref,
computed,
effect,
compile
compile,
renderList,
withModel,
resolveComponent
});
1 change: 0 additions & 1 deletion src/runtime/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function mountComponent(vnode, container, anchor, patch) {
}

instance.update = effect(() => {
// console.log('render')
if (instance.next) {
vnode = instance.next;
instance.next = null;
Expand Down
16 changes: 11 additions & 5 deletions src/runtime/createApp.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { isString } from "../utils";
import { camelize, capitalize, isString } from "../utils";
import { render } from "./render";
import { h } from "./vnode";

let components;
export function createApp(rootComponent) {
components = rootComponent.components || {};
const app = {
mount(el){
if(isString(el)){
mount(el) {
if (isString(el)) {
el = document.querySelector(el);
}

if(!rootComponent.render && !rootComponent.template){
if (!rootComponent.render && !rootComponent.template) {
rootComponent.template = el.innerHTML;
el.innerHTML = '';
}

render(h(rootComponent), el);
}
}
return app;
}

export function resolveComponent(name) {
return components && (components[name] || components[camelize(name)] || components[capitalize((camelize(name)))])
}
25 changes: 25 additions & 0 deletions src/runtime/helpers/renderList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { isArray, isNumber, isObject, isString } from "../../utils";

export function renderList(source, renderItem) {
const nodes = [];
if (isNumber(source)) {
for (let i = 0; i < source; i++) {
// (item, index) => {}
nodes.push(renderItem(i + 1, i));
}
}
else if (isString(source) || isArray(source)) {
for (let i = 0; i < source.length; i++) {
// (item, index) => {}
nodes.push(renderItem(source[i], i));
}
}
else if (isObject(source)) {
Object.keys(source).forEach((key, i) => {
// (value, key, index) => {}
nodes.push(renderItem(source[key], key, i));
})
}

return nodes;
}
43 changes: 43 additions & 0 deletions src/runtime/helpers/vModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { isArray } from "../../utils";

export function withModel(tag, props, getter, setter) {
props = props || {};
if (tag === 'input') {
switch(props.type){
case 'radio':
props.checked = props.value === getter();
props.onChange = (e) => setter(e.target.value);
break;
case 'checkbox':
const modelValue = getter();
if(isArray(modelValue)){
props.checked = modelValue.includes(props.value);
props.onChange = (e) => {
const {value} = e.target;
const values = new Set(getter());
if(values.has(value)){
values.delete(value);
}
else {
values.add(value);
}
props.checked = values.has(props.value);
setter([...values]);
}
}
else {
props.checked = modelValue;
props.onChange = (e) => {
props.checked = e.target.checked;
setter(e.target.checked);
};
}
break;
default:
props.value = getter();
props.onInput = (e) => setter(e.target.value);
break;
}
}
return props;
}
4 changes: 3 additions & 1 deletion src/runtime/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { h, Text, Fragment } from './vnode.js';
export { render } from './render.js';
export { nextTick } from './scheduler';
export {createApp} from './createApp';
export { createApp, resolveComponent } from './createApp';
export { renderList } from './helpers/renderList'
export { withModel } from './helpers/vModel'
Loading

0 comments on commit dca6bf3

Please sign in to comment.