1、generate.js
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; //{{aaa}}
export function generate(el) {
console.log("-----------", el);
//遍历树 将树拼接成字符串
let children = genChildren(el);
let code = `_c("${el.tag}",${
el.attrs.length ? genProps(el.attrs) : "undefined"
}${children ? `,${children}` : ""})`;
return code;
console.log(code);
}
function genProps(attrs) {
let str = "";
for (let i = 0; i < attrs.length; i++) {
let attr = attrs[i];
if (attr.name === "style") {
//color:red;background:blue;
let styleObj = {};
arr.value.replace(/([^;:]+)\:([^;:]+)/g, function () {
console.log(arguments[1], arguments[2]);
styleObj[arguments[1]] = arguments[2];
});
attr.value = styleObj;
}
str += `${arr.name}:${JSON.stringify(arr.value)},`; //stringify 是为了添加双引号的
}
return `${str.slice(0, -1)}`;
}
// html 字符串 转化成字符串 c_('div',{id:'app',a:1},'hello');
function genChildren(el) {
let children = el.children;
if (children) {
return children.map((c) => gen(c)).join(",");
}
return false;
}
function gen(el) {
if (el.type == 1) {
//element =1 text=3;
return generate(el);
} else {
let text = el.text;
if (defaultTagRE.test(text)) {
return `_v('${text}')`;
} else {
//
let tokens = [];
let match;
let lastIndex = (defaultTagRE.lastIndex = 0); //css-loader 原理一样 就是一个字符串拼接
while ((match = defaultTagRE.exec(text))) {
let index = match.index; //开始索引
if (index > lastIndex) {
tokens.push(JSON.stringify(text.slice(lastIndex, index)));
}
tokens.push(`_s(${match[1].trim()})`); //JSON.stringify()
let lastIndex = index + match[0].length;
}
if (lastIndex < text.length) {
tokens.push(JSON.stringify(text.slice(lastIndex)));
}
return `_v(${tokens.join("+")})`;
}
}
}
2、index.js
import { generate } from './generate';
import {parserHTML} from './parser';
export function compileToFunction(template) {
let root=parserHTML(template); //解析成html
/* render(){
return _c('div',{id:'app',a:1},'hello');
}
*/
//
let code=generate(root);
}
//htmlparser2 开始标签 结束标签 文本标签
// ast 语法层面的描述 (js css html) vdom (dom节点而已)
// html -> ast(语法不存在的不能描述,只能描述存在的语法) ->render 函数 虚拟dom (增加额外属性)->生成真实dom
3、init.js
import { compileToFunction } from ".";
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
if (!options.render) {
let template = options.template;
if (!template && el) {
template = el.outerHTML;
let render = compileToFunction(template);
options.render = render;
}
}
console.log(options.render); //调用render方法 渲染成真实dom 替换掉页面的内容
mountComponent(vm,el); //组件的挂载流程
};
4、lifecycle.js
export function mountComponent(vm,el){
console.log(vm,el);
// 更新函数 数据变化后 会再次调用此函数
let updateComponent=()=>{
// 调用render函数 生成虚拟dom
// 用虚拟dom 生成真实dom
vm._update(vm._render());
// 后续更新可以调用updateComponent方法;
}
updateComponent();
}
export function lifecycleMixin(Vue){
Vue.prototype._update=function(vnode){
console.log('update',vnode);
const vm=this;
let render=vm.$options.render; // 就是我们解析出来的render方法,同时也有可能是用户写的。
let vnode=render.call(vm);
return vnode;
}
}
5、parser.js
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //标签名
const qnameCaptrue = `((?:${ncname}\\:)?${ncname})`; //用来获取标签名 match后的索引为1的 <aa:xxx></aa:xxx>
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 匹配开始标签
const endTag = new RegExp(`^\\/${qnameCaptrue}[^>]*>`); // 匹配闭合标签
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+)+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配属性 a=b a="b" a='b'
const startTagClose = /^\s*(\/?)>/; // />
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; //{{aaa}}
// 分组 ()
// 可有可无 ?
let r = "<xxxxx>".match(new RegExp(qnameCaptrue));
console.log(r);
function createAstElement(tagName, attrs) {
return {
tag: tagName,
type: 1,
children: [],
parent: null,
attrs,
}
}
let root = null;
let stack = [];
function start(tagName, attributes) {
let parent = stack[stack.length - 1];
let element = createAstElement(tagName, attributes);
if (!root) {
root = element;
}
if (parent) {
element.parent = parent;
parent.children.push(element);
}
stack.push(element);
console.log("start", tagName, attributes);
}
function end(tagName) {
let last = stack.pop();
if (last.tag !== tagName) {
throw new Error("标签有误");
}
console.log("end", tagName);
}
function chars(text) {
text = text.replace(/\s/g, "");
let parent = stack[stack.length - 1];
if (text) {
parent.children.push({
type: 3,
text,
});
}
console.log("charts", text);
}
export function parserHTML() {
function advance(len) {
html = html.substring(len);
}
function parserStartTag(html) {
const start = html.match(startTagOpen);
if (start) {
const match = {
tagName: start[1],
attrs: [],
};
advance(start[0].length);
let end;
let attr;
console.log(html);
// 如果没有遇到标签结尾,就不停的解析
while (
!(end == html.match(startTagClose)) &&
attr == html.match(attribute)
) {
console.log(attr);
match.attrs.push({
name: attr[1],
value: attr[3] || attr[4] || attr[5],
});
advance(attr[0].length);
}
if (end) {
advance(end[0].length);
}
return match;
}
return false;
}
while (html) {
//看要解析的内容是否存在,如果存在就不停的解析
let textEnd = html.indexOf("<"); //当前解析的开头
if (textEnd == 0) {
const startTagMatch = parserStartTag();
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
const endTagMatch = html.match(endTag);
if (endTagMatch) {
end(endTagMatch[1]);
advance(endTagMatch[0].length);
continue;
}
}
let text;
if (textEnd >= 0) {
html = html.substring(0, textEnd);
}
if (text) {
chars(text);
advance(text.length);
}
}
return root;
}
// 看一下用户是否传入了render
/***
* render
* 没传入 可能传入的是template template也没有传入 就是外层的div了
* 将我们的html =》 词法解析 (开始标签 结束标签 属性 文本)
*
* ast语法树 用来描述html
*
*/
export function compileToFunction(template){
let root=parserHTML(template);
let code=generate(root);
let render=new Function(`with(this){return ${code}}`); //code 中会用到数据 数据在vm上
console.log(render.toString());
return render;
/* render.call(vm); */
// html =>ast (只能描述语法 语法不存在的属性无法描述 ) =>render 函数 => 虚拟dom (增加额外的属性) => 生成真实dom
// render 函数 + (width +new Function) =>虚拟dom (增加额外的属性) => 生成真实dom
}
/* let vm={arr:1}
with(this){
console.log(arr);
}; */
6、render.js
export function renderMixin(Vue) {
Vue.prototype._c = function (tag, data, ...children) {
//createElement
return createElement(this, ...arguments);
console.log(tag, data, ...children);
};
Vue.prototype._v = function (text) {
//createTextElement
return createTextElement(this, text);
console.log(text);
};
Vue.prototype._s = function (val) {
//stringify
return JSON.stringify(val);
};
Vue.prototype._render = function () {
const vm = this;
let render = vm.$options.render;
let vnode = render.call(vm);
console.log("render");
};
}
7、state.js
function proxy(vm, source, key) {
console.log(vm, source, key);
Object.defineProperty(vm, source, {
get() {
return vm[source][key];
},
set(newValue) {
vm[source][key] = newValue;
},
});
}
function initData(vm) {
let data = vm.$options.data;
data = vm._data = isFunction(data) ? data.call(vm) : data;
// 用户去vm.xxx => vm._data.xxx
for (let key in data) {
proxy(vm, "_data", key);
}
}