第一步
新建 runtime-dom 模块
packages/runtime-dom/package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "name": "@vue/runtime-dom", "version": "1.0.0", "module": "dist/runtime-dom.esm-bundler.js", "unpkg": "dist/runtime-dom.global.js", "buildOptions": { "name": "RuntimeDOM", "formats": [ "esm-bundler", "esm-browser", "cjs", "global" ] } }
|
修改打包编译的模块名称
/package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "name": "vue3-resource", "private": true, "version": "1.0.0", "description": "", "main": "index.js", "type": "module", "scripts": { "dev": "node scripts/dev.js runtime-dom -f esm" }, ... }
|
因为可以在多平台来渲染,所以提供了 createRenderer 来让我们可以自定义渲染器,同时提供了 render 内置渲染器,来渲染 dom 元素,h 方法可以用来创建一个虚拟 dom,具体使用方法如下:
packages/runtime-dom/dist/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"></div> <script type="module"> import { createRenderer, render, h } from '/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js';
const ele = h('h1', 'xiaosheng'); render(ele, app); const renderer = createRenderer({ createElement(type) { return document.createElement('h1'); }, setElementText(el, text) { el.textContent = text; }, insert(el, container) { container.appendChild(el); } }); renderer.render(ele, app); </script> </body> </html>
|
第二步
runtime-dom 是指针对浏览器的,runtime-dom 是基于 runtime-core 的,runtime-core 是跨平台的,其次,runtime-core 又依赖 reactivity 来实现响应式,因此在 runtime-dom 中需要安装以下依赖:pnpm i @vue/shared@workspace --filter @vue/runtime-dom
runtime-dom 中包含了对节点的增删改查,以及对节点属性的相关操作,具体代码如下:
packages/runtime-dom/src/nodeOps.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export const nodeOps = { insert: (el, parent, anchor) => parent.insertBefore(el, anchor || null), remove(el) { const parent = el.parentNode; parent && parent.removeChild(el); }, createElement: (type) => document.createElement(type), createText: text => document.createTextNode(text), setText: (node, text) => node.nodeValue = text, setElementText: (el, text) => el.textContent = text, parentNode: node => node.parentNode, nextSibling: node => node.nextSibling, };
|
packages/runtime-dom/src/patchProp.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import patchClass from './modules/patchClass'; import patchStyle from './modules/patchStyle'; import patchEvent from './modules/patchEvent'; import patchAttr from './modules/patchAttr';
export default function patchProp(el, key, prevValue, nextValue) { if (key === 'class') { return patchClass(el, nextValue); } else if (key === 'style') { return patchStyle(el, prevValue, nextValue); } else if (/^on[^a-z]/.test(key)) { return patchEvent(el, key, nextValue) } else { patchAttr(el, key, nextValue) } }
|
packages/runtime-dom/src/modules/patchAttr.ts
1 2 3 4 5 6 7
| export default function patchAttr(el, key, value) { if (!value) { el.removeAttribute(key) } else { el.setAttribute(key, value) } }
|
packages/runtime-dom/src/modules/patchClass.ts
1 2 3 4 5 6 7
| export default function patchClass(el, value) { if (value === null) { el.removeAttribute('class') } else { el.className = value } }
|
packages/runtime-dom/src/modules/patchEvent.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function createInvoker(value) { const invoker = (e) => invoker.value(e); invoker.value = value; return invoker; }
export default function patchEvent(el, name, nextValue) { const invokers = el._vei || (el._vei = {}); const eventName = name.slice(2).toLowerCase();
const existingInvokers = invokers[name];
if (nextValue && existingInvokers) { return existingInvokers.value = nextValue; }
if (nextValue) { const invoker = invokers[name] = createInvoker(nextValue); return el.addEventListener(eventName, invoker); }
if (existingInvokers && !nextValue) { el.removeEventListener(eventName, existingInvokers); invokers[name] = undefined; } }
|
packages/runtime-dom/src/modules/patchStyle.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default function patchStyle(el, prevValue, nextValue) { const style = el.style; for (const key in nextValue) { style[key] = nextValue[key]; }
if (prevValue) { for (const key in prevValue) { if (nextValue && !nextValue[key]) { style[key] = null; } } } }
|