Skip to content

Commit d5cd4f3

Browse files
committed
feat(runtime-core): base renderer
1 parent fb6d690 commit d5cd4f3

30 files changed

+555
-7
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cSpell.ignoreWords": [
3+
"vnode"
4+
]
5+
}

examples/hello-world/App.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { h } from '../../lib/mini-vue.esm.js'
2+
3+
export const App = {
4+
setup() {},
5+
render() {
6+
return h(
7+
'div',
8+
{
9+
id: 'root',
10+
onClick: () => alert('onClick!'),
11+
},
12+
'hello vue.js'
13+
)
14+
},
15+
}

examples/hello-world/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Document</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="main.js"></script>
12+
</body>
13+
</html>

examples/hello-world/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { App } from './App.js'
2+
import { createApp } from '../../lib/mini-vue.esm.js'
3+
// TODO: change document.querySelector to renderer
4+
const container = document.querySelector('#app')
5+
createApp(App).mount(container)

lib/mini-vue.cjs.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
'use strict';
2+
3+
Object.defineProperty(exports, '__esModule', { value: true });
4+
5+
const isObject = (val) => val !== null && typeof val === 'object';
6+
const isString = (val) => typeof val === 'string';
7+
const isArray = (val) => Array.isArray(val);
8+
9+
function createComponentInstance(vnode) {
10+
const component = {
11+
vnode,
12+
type: vnode.type,
13+
};
14+
return component;
15+
}
16+
function setupComponent(instance) {
17+
// TODO: initProps
18+
// TODO: initSlots
19+
setupStatefulComponent(instance);
20+
// TODO: 函数组件(无状态)
21+
}
22+
// 1. setup?
23+
// 2. handleSetupResult
24+
function setupStatefulComponent(instance) {
25+
// instance -> vnode -> type === component -> setupResult = setup()
26+
// instance: {vnode, type}
27+
// instance -> type === component -> setupResult = setup()
28+
const Component = instance.type;
29+
const { setup } = Component;
30+
if (setup) {
31+
setup();
32+
handleSetupResult(instance);
33+
}
34+
}
35+
// 1. setupResult 是 function
36+
// 2. setupResult 是 object
37+
// 3. finishComponentSetup
38+
function handleSetupResult(instance, setupResult) {
39+
// TODO: function
40+
// TODO: object 响应式代理
41+
// instance.setupState = proxyRefs(setupResult)
42+
finishComponentSetup(instance);
43+
}
44+
function finishComponentSetup(instance) {
45+
const Component = instance.type;
46+
// 如果 instance 还没有 render
47+
if (!instance.render) {
48+
instance.render = Component.render;
49+
}
50+
}
51+
52+
function render(vnode, rootContainer) {
53+
// patch 递归
54+
patch(vnode, rootContainer);
55+
}
56+
function patch(vnode, container) {
57+
const { type } = vnode;
58+
if (isString(type)) {
59+
// isString -> processElement
60+
processElement(vnode, container);
61+
}
62+
else if (isObject(type)) {
63+
// isObj ->processComponent
64+
processComponent(vnode, container);
65+
}
66+
}
67+
function processElement(vnode, container) {
68+
// 判断是 mount 还是 update
69+
mountElement(vnode, container);
70+
// TODO: updateElement
71+
}
72+
// 1. 创建 type === tag 的 el
73+
// 2. el.props 是 attribute 还是 event
74+
// 3. children 是否为 string 或者 array
75+
// 4. 挂载 container.append
76+
function mountElement(vnode, container) {
77+
const { type, props, children } = vnode;
78+
const el = document.createElement(type);
79+
// TODO: refactor to function handle props
80+
if (props) {
81+
for (const key in props) {
82+
el.setAttribute(key, props[key]);
83+
// TODO: event
84+
}
85+
}
86+
if (isString(children)) {
87+
el.innerText = children;
88+
}
89+
else if (isArray(children)) {
90+
mountChildren(children, el);
91+
}
92+
container.append(el);
93+
}
94+
function mountChildren(children, container) {
95+
children.forEach((child) => {
96+
patch(child, container);
97+
});
98+
}
99+
function processComponent(vnode, container) {
100+
// 判断是 mount 还是 update
101+
mountComponent(vnode, container);
102+
// TODO: updateComponent
103+
}
104+
function mountComponent(vnode, container) {
105+
// 1. 创建 componentInstance
106+
// 数据类型: vnode -> component
107+
// component: {vnode, type}
108+
const instance = createComponentInstance(vnode);
109+
// 2. setupComponent(instance)
110+
setupComponent(instance);
111+
// 3. setupRenderEffect(instance)
112+
// 此时 instance 通过 setupComponent 拿到了 render
113+
setupRenderEffect(instance, container);
114+
}
115+
function setupRenderEffect(instance, container) {
116+
const subTree = instance.render();
117+
patch(subTree, container);
118+
}
119+
120+
function createVNode(type, props, children) {
121+
const vnode = {
122+
type,
123+
props,
124+
children,
125+
};
126+
return vnode;
127+
}
128+
129+
function createApp(rootComponent) {
130+
return {
131+
mount(rootContainer) {
132+
// 1. 创建 vnode: rootComponent -> vnode
133+
// vnode: {type, props?, children?}
134+
const vnode = createVNode(rootComponent);
135+
// 2. 渲染 vnode: render(vnode, rootContainer)
136+
render(vnode, rootContainer);
137+
},
138+
};
139+
}
140+
141+
function h(type, props, children) {
142+
return createVNode(type, props, children);
143+
}
144+
145+
exports.createApp = createApp;
146+
exports.h = h;

lib/mini-vue.esm.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const isObject = (val) => val !== null && typeof val === 'object';
2+
const isString = (val) => typeof val === 'string';
3+
const isArray = (val) => Array.isArray(val);
4+
5+
function createComponentInstance(vnode) {
6+
const component = {
7+
vnode,
8+
type: vnode.type,
9+
};
10+
return component;
11+
}
12+
function setupComponent(instance) {
13+
// TODO: initProps
14+
// TODO: initSlots
15+
setupStatefulComponent(instance);
16+
// TODO: 函数组件(无状态)
17+
}
18+
// 1. setup?
19+
// 2. handleSetupResult
20+
function setupStatefulComponent(instance) {
21+
// instance -> vnode -> type === component -> setupResult = setup()
22+
// instance: {vnode, type}
23+
// instance -> type === component -> setupResult = setup()
24+
const Component = instance.type;
25+
const { setup } = Component;
26+
if (setup) {
27+
setup();
28+
handleSetupResult(instance);
29+
}
30+
}
31+
// 1. setupResult 是 function
32+
// 2. setupResult 是 object
33+
// 3. finishComponentSetup
34+
function handleSetupResult(instance, setupResult) {
35+
// TODO: function
36+
// TODO: object 响应式代理
37+
// instance.setupState = proxyRefs(setupResult)
38+
finishComponentSetup(instance);
39+
}
40+
function finishComponentSetup(instance) {
41+
const Component = instance.type;
42+
// 如果 instance 还没有 render
43+
if (!instance.render) {
44+
instance.render = Component.render;
45+
}
46+
}
47+
48+
function render(vnode, rootContainer) {
49+
// patch 递归
50+
patch(vnode, rootContainer);
51+
}
52+
function patch(vnode, container) {
53+
const { type } = vnode;
54+
if (isString(type)) {
55+
// isString -> processElement
56+
processElement(vnode, container);
57+
}
58+
else if (isObject(type)) {
59+
// isObj ->processComponent
60+
processComponent(vnode, container);
61+
}
62+
}
63+
function processElement(vnode, container) {
64+
// 判断是 mount 还是 update
65+
mountElement(vnode, container);
66+
// TODO: updateElement
67+
}
68+
// 1. 创建 type === tag 的 el
69+
// 2. el.props 是 attribute 还是 event
70+
// 3. children 是否为 string 或者 array
71+
// 4. 挂载 container.append
72+
function mountElement(vnode, container) {
73+
const { type, props, children } = vnode;
74+
const el = document.createElement(type);
75+
// TODO: refactor to function handle props
76+
if (props) {
77+
for (const key in props) {
78+
el.setAttribute(key, props[key]);
79+
// TODO: event
80+
}
81+
}
82+
if (isString(children)) {
83+
el.innerText = children;
84+
}
85+
else if (isArray(children)) {
86+
mountChildren(children, el);
87+
}
88+
container.append(el);
89+
}
90+
function mountChildren(children, container) {
91+
children.forEach((child) => {
92+
patch(child, container);
93+
});
94+
}
95+
function processComponent(vnode, container) {
96+
// 判断是 mount 还是 update
97+
mountComponent(vnode, container);
98+
// TODO: updateComponent
99+
}
100+
function mountComponent(vnode, container) {
101+
// 1. 创建 componentInstance
102+
// 数据类型: vnode -> component
103+
// component: {vnode, type}
104+
const instance = createComponentInstance(vnode);
105+
// 2. setupComponent(instance)
106+
setupComponent(instance);
107+
// 3. setupRenderEffect(instance)
108+
// 此时 instance 通过 setupComponent 拿到了 render
109+
setupRenderEffect(instance, container);
110+
}
111+
function setupRenderEffect(instance, container) {
112+
const subTree = instance.render();
113+
patch(subTree, container);
114+
}
115+
116+
function createVNode(type, props, children) {
117+
const vnode = {
118+
type,
119+
props,
120+
children,
121+
};
122+
return vnode;
123+
}
124+
125+
function createApp(rootComponent) {
126+
return {
127+
mount(rootContainer) {
128+
// 1. 创建 vnode: rootComponent -> vnode
129+
// vnode: {type, props?, children?}
130+
const vnode = createVNode(rootComponent);
131+
// 2. 渲染 vnode: render(vnode, rootContainer)
132+
render(vnode, rootContainer);
133+
},
134+
};
135+
}
136+
137+
function h(type, props, children) {
138+
return createVNode(type, props, children);
139+
}
140+
141+
export { createApp, h };

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44
"main": "index.js",
55
"license": "MIT",
66
"scripts": {
7-
"test": "jest"
7+
"test": "jest",
8+
"build": "rollup -c rollup.config.js"
89
},
910
"devDependencies": {
1011
"@babel/core": "^7.17.8",
1112
"@babel/preset-env": "^7.16.11",
1213
"@babel/preset-typescript": "^7.16.7",
14+
"@rollup/plugin-typescript": "^8.3.1",
1315
"@types/jest": "^27.4.1",
1416
"babel-jest": "^27.5.1",
15-
"jest": "^27.5.1"
16-
}
17+
"jest": "^27.5.1",
18+
"rollup": "^2.70.1",
19+
"tslib": "^2.3.1",
20+
"typescript": "^4.6.3"
21+
},
22+
"dependencies": {}
1723
}

rollup.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import typescript from '@rollup/plugin-typescript'
2+
export default {
3+
input: './src/index.ts',
4+
output: [
5+
// cjs
6+
{
7+
format: 'cjs',
8+
file: 'lib/mini-vue.cjs.js',
9+
},
10+
// esm
11+
{
12+
format: 'es',
13+
file: 'lib/mini-vue.esm.js',
14+
},
15+
],
16+
plugins: [typescript()],
17+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './runtime-core'

0 commit comments

Comments
 (0)