happy path
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.red {
color: red;
}
.blue {
color: blue;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="main.js" type="module"></script>
</body>
</html>
// main.js
import { createApp } from "../../lib/5c24-mini-vue.esm.js";
import { App } from "./App.js";
const rootContainer = document.querySelector("#app");
createApp(App).mount(rootContainer);
// App.js
import { h } from "../../lib/5c24-mini-vue.esm.js";
export const App = {
render() {
return h(
"div",
{
id: "root",
class: ["red", "blue"],
},
// "hi, " + this.msg
// "hi, mini-vue"
[h("p", { class: "red" }, "hi,"), h("p", { class: "blue" }, "mini-vue")]
);
},
setup() {
return {
msg: "mini-vue",
};
},
};
以上是一个简单的 happy path
最终要实现的目标是在浏览器渲染出上图内容
runtime-core 初始化
分析:
createApp接收一个根组件,return一个对象包含mount方法mount接收一个Element实例,后续所有渲染出来的内部元素都添加到这个Element实例上。在happy path中就是这个id为app的根容器上- 在
vue3中所有的东西都会先转换成虚拟节点vnode,后续所有的操作都基于vnode
// createApp.ts
export function createApp(rootComponent) {
return {
mount(rootContainer) {
// component -> vnode
const vnode = createVNode(rootComponent);
// render 处理后续的 Component 开箱和 Element 渲染
render(vnode, rootContainer);
}
}
}
// vnode.ts
export function createVNode(type, props?, children?) {
const vnode = {
type,
props,
children
};
return vnode;
}
render
在render中调用了patch去处理Component和Element,之所以调用patch是为了后续的递归处理。
// renderder.ts
export function render(vnode, container) {
patch(vnode, container);
}
function patch(vnode, container) {
// 根据 vnode.type 的类型判断做哪个处理
if(typeof vnode.type === 'string') {
processElement(vnode, container);
} else if (isObject(vnode.type)) {
processComponent(vnode, container);
}
}
组件处理 processComponent
// renderer.ts
function processComponent(vnode, container) {
mountComponent(vnode, container);
}
function mountComponent(vnode, container) {
// 创建组件实例
const instance = createComponentInstance(vnode);
// 处理组件实例中的 props slots 以及处理调用 setup 的返回值,最后设置 render
setupComponent(instance);
// 调用组件实例上的 render
setupRenderEffect(instance, container);
}
setupComponent
// component.ts
export function createComponentInstance(vnode) {
const component = {
vnode,
type: vnode.type
};
return component;
}
export function setupComponent(instance) {
// 这里先只处理 setup 的返回值和设置 render
setupStatefulComponent(instance)
}
function setupStatefulComponent(instance) {
const component = instance.type;
const { setup } = component;
if(setup) {
const setupResult = setup();
// 处理 setup 返回值
handleSetupResult(instance, setupResult);
}
}
function handleSetupResult(instance, setupResult) {
if(typeof setupResult === "object") {
instance.setupState = setupResult;
}
// 把 render 存到 组件实例的上下文中
finishComponentSetup(instance);
}
function finishComponentSetup(instance) {
comst Component = instance.type;
instance.render = Component.render;
}
setupRenderEffect
renderder.ts
function setupRenderEffect(instance, container) {
const subTree = instance.render();
// 这里就是上面说到的递归处理
// subTree 是 Element 类型就挂载出来,是 Component ,继续进行开箱操作
patch(subTree, container);
}
Element处理 processElement
// renderder.ts
function processElement(vnode, container) {
mountElement(vnode, container);
}
function mountElement(vnode, container) {
// 创建元素
const el = document.createElement(vnode.type);
const { children, props } = vnode;
if(typeof children === 'string') {
el.textContent = children;
} else if(Array.isArray(children)) {
mountChildren(vnode, el)
}
// 设置元素属性
for(const key in props) {
el.setAttribute(key, props[key]);
}
container.appendChild(el);
}
function mountChildren(vnode, container) {
const { children } = vnode;
children.forEach(v => {
patch(v, container);
})
}
roll up打包库
安装rollup
yarn add rollup --dev
安装rollup能识别typescript的插件
yarn add @rollup/plugin-typescript --dev
创建mini-vue出口
// src/index.ts
export * from "./runtime-core";
创建配置文件rollup.config.js
import pkg from "./package.json";
import typescript from "@rollup/plugin-typescript";
export default {
input: "./src/index.ts",
output: [
{
format: "cjs",
file: pkg.main,
},
{
format: "es",
file: pkg.module,
},
],
plugins: [typescript()],
};
配置package.json
{
"main": "lib/5c24-mini-vue.cjs.js",
"module": "lib/5c24-mini-vue.esm.js",
"scripts": {
"test": "jest",
"build": "rollup -c rollup.config.js"
},
"devDependencies": {
...
}
}