# Koa
// 封装
const http = require('http')
class Application {
constructor() {
this.middlewares = []
}
listen(...args) {
const server = http.createServer(async (req, res) => {
const ctx = new Context(req, res)
await runMiddlewares(this.middlewares, ctx)
ctx.res.end(ctx.body)
})
server.listen(...args)
}
use(middleware) {
this.middlewares.push(middleware)
}
}
class Context {
constructor(req, res) {
this.req = req
this.res = res
}
}
async function runMiddlewares(middlewares, ctx) {
const run = async (i) => {
const middleware = middlewares[i]
if (i === middlewares.length) {
return
}
await middleware(ctx, () => run(i + 1))
}
await run(0)
}
module.exports = Application;
// 使用
const Application = require('./index')
const app = new Application()
app.use(async (ctx, next) => {
console.log('Middleware 1 Start')
await next()
console.log('Middleware 1 End')
})
app.use(async (ctx, next) => {
console.log('Middleware 2 Start')
await next()
console.log('Middleware 2 End')
})
app.listen(4000)
# mvvm
<body>
<div id="app">
<input type="text" v-modal="name">
<div class="outer">
<span>{{name}}</span>
<p><span v-html="name"></span></p>
</div>
<button @click="reset">重置</button>
</div>
<script>
class Wvue {
constructor(option) {
this.$option = option
this.$data = option.data
this.$methods = option.methods
let that=this;
Object.keys(option.data).forEach(function (key) {
that.proxy(key);
});
//数据劫持
this.observer(this.$data)
//模板解析
this.$compile = new Compile(option.el, this)
}
observer(obj) {
if (!obj || typeof obj !== "object") {
return;
}
Object.keys(obj).forEach(key => {
this.defineProperty(obj, key, obj[key])
})
}
defineProperty(obj, key, val) {
this.observer(val)
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 每次访问name 都会创建一个watcher,并加入到Dep中
Dep.target !== null && dep.addDep(Dep.target)
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
}
proxy(key) {
//使得读写vm的方法编程读写vm.data的
var that = this;
Object.defineProperty(that, key, {
configurable: false,
enumerable: true,
get() {
return that.$data[key];
},
set(newVal) {
that.$data[key] = newVal;
}
});
}
}
class Dep {
constructor() {
this.dep = []
}
addDep(dep) {
this.dep.push(dep)
}
notify() {
// 通知所有的watcher执行更新
this.dep.forEach(watcher => {
watcher.update()
})
}
}
class Watcher {
constructor(vm, key, cb) {
this.$vm = vm
this.$key = key
this.$cb = cb
// 用一个全局变量来指代当前watch
Dep.target = this
// 实际是访问了this.name,触发了当前变量的get,
// 当前变量的get会收集当前Dep.target指向的watcher,即当前watcher
this.$vm.$data[this.$key]
Dep.target = null
}
update() {
// 执行
// console.log(his.$vm[this.$key])
this.$cb.call(this.$vm, this.$vm[this.$key])
}
}
class Compile {
constructor(el, vm) {
this.$vm = vm
// $el挂载的就是需要处理的DOM
this.$el = document.querySelector(el)
// 将真实的DOM元素拷贝一份作为文档片段,之后进行分析
const fragment = this.node2Fragment(this.$el)
// 解析文档片段
this.compileNode(fragment)
// 将文档片段加入到真实的DOM中去
this.$el.appendChild(fragment)
}
node2Fragment(el) {
// 创建空白文档片段
const fragment = document.createDocumentFragment()
let child
// appendChild会把原来的child给移动到新的文档中,当el.firstChild为空时,
// while也会结束 a = undefined => 返回 undefined
while ((child = el.firstChild)) {
fragment.appendChild(child);
}
return fragment
}
// 通过迭代循环来找出{{}}中的内容,v-xxx与@xxx的内容,并且单独处理
compileNode(node) {
const nodes = node.childNodes
// 类数组的循环
Array.from(nodes).forEach(node => {
if (this.isElement(node)) {
this.compileElement(node)
} else if (this.isInterpolation(node)) {
this.compileText(node)
}
node.childNodes.length > 0 && this.compileNode(node)
});
}
isElement(node) {
return node.nodeType === 1;
}
isInterpolation(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
compileText(node) {
const reg = /\{\{(.*?)\}\}/g
const string = node.textContent.match(reg)
// RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个 子匹配(以括号为标志)字符串
// 以此类推,RegExp.$2,RegExp.$3,..RegExp.$99总共可以有99个匹配
this.text(node, RegExp.$1)
}
compileElement(node) {
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(arr => {
if (arr.name.indexOf('v-') > -1) {
this[`${arr.name.substring(2)}`](node, arr.value)
}
if (arr.name.indexOf('@') > -1) {
// console.log(node, arr.value)
this.eventHandle(node, arr.name.substring(1), arr.value)
}
})
}
// 因为是大括号里面的内容,所以沿用之前的逻辑,都加上watcher
text(node, key) {
new Watcher(this.$vm, key, () => {
node.textContent = this.$vm.$data[key]
})
// 第一次初始化界面, 不然如果不进行赋值操作,
// 就不会触发watcher里面的回调函数
node.textContent = this.$vm.$data[key]
}
html(node, key) {
new Watcher(this.$vm, key, () => {
node.innerHTML = this.$vm.$data[key]
})
node.innerHTML = this.$vm.$data[key]
}
// 对@xxx事件的处理
eventHandle(node, eventName, methodName) {
node.addEventListener(eventName, () => {
this.$vm.$methods[methodName].call(this.$vm)
})
}
// v-modal的处理 不仅仅当赋值的时候回触发watcher,并且为input添加事件
// input中的值去修改this.$data.$xxx的值,实现双向绑定
modal(node, key) {
new Watcher(this.$vm, key, () => {
node.value = this.$vm.$data[key]
})
node.value = this.$vm.$data[key]
node.addEventListener('input', (e) => {
this.$vm.$data[key] = e.target.value
})
}
}
</script>
<script>
const data = {
el: '#app',
data: {
name: '米粒'
},
methods: {
reset() {
this.name = ''
}
},
}
const app = new Wvue(data)
</script>
</body>
# rollup
// modules/myModule.js
export const name = 'jiahang'
export const age = 18
export const height = 180
// src/mian.js
import { name, age } from './modules/myModule'
function say() {
console.log(`my name is ${name}`);
}
console.log(age);
say();
// 打包后
'use strict';
const name = 'jiahang';
const age = 18;
function say() {
console.log(`my name is ${name}`);
}
console.log(age);
say();
实现
# vite-server
// 写一个node服务器,相当于devServer
const Koa = require("koa");
const app = new Koa();
const fs = require("fs");
const path = require("path");
const compilerSfc = require("@vue/compiler-sfc");
const compilerDom = require("@vue/compiler-dom");
// 返回用户首页
app.use(async (ctx) => {
const { url, query } = ctx.request;
if (url === "/") {
// 首页
ctx.type = "text/html";
const p = path.join(__dirname, "./index.html");
//mock process
const content = fs.readFileSync(p, "utf8").replace(
'<script type="module" src="/src/main.js"></script>',
`<script>
window.process = { env: { NODE_ENV: 'dev' } }
</script>
<script type="module" src="/src/main.js"></script>
`
);
ctx.body = content;
} else if (url.endsWith(".js")) {
// 响应js请求
const p = path.join(__dirname, url);
console.log(p);
ctx.type = "text/javascript";
const file = rewriteImport(fs.readFileSync(p, "utf8"));
ctx.body = file;
} else if (url.startsWith("/@modules/")) {
// 获取@modules后面部分,模块名称
const moduleName = url.replace("/@modules/", "");
const prefix = path.join(__dirname, "../node_modules", moduleName);
// 要加载文件的地址
const module = require(prefix + "/package.json").module;
const filePath = path.join(prefix, module);
const ret = fs.readFileSync(filePath, "utf8");
ctx.type = "text/javascript";
ctx.body = rewriteImport(ret);
} else if (url.indexOf(".vue") > -1) {
// 读取vue文件内容
const p = path.join(__dirname, url.split("?")[0]);
// compilerSfc解析SFC, 获得一个ast
const ret = compilerSfc.parse(fs.readFileSync(p, "utf8"));
// 没有query.type,说明是SFC
if (!query.type) {
// 处理内部script
console.log(ret);
// 获取脚本内容
const scriptContent = ret.descriptor.script.content;
// 转换默认导出配置对象为变量
const script = scriptContent.replace(
"export default ",
"const __script = "
);
ctx.type = "text/javascript";
ctx.body = `
${rewriteImport(script)}
// template解析转换为另一个请求单独做
import {render as __render} from '${url}?type=template'
__script.render = __render
export default __script
`;
} else if (query.type === "template") {
const tpl = ret.descriptor.template.content;
// 编译为包含render模块
const render = compilerDom.compile(tpl, { mode: "module" }).code;
ctx.type = "text/javascript";
ctx.body = rewriteImport(render);
}
} else if (url.endsWith(".png")) {
ctx.body = fs.readFileSync("src" + url);
}
});
// 重写导入,变成相对地址
function rewriteImport(content) {
return content.replace(/ from ['"](.*)['"]/g, function (s0, s1) {
// s0匹配字符串,s1分组内容
// 看看是不是相对地址
if (s1.startsWith("./") || s1.startsWith("/") || s1.startsWith("../")) {
// 原封不动的返回
return s0;
} else {
// 裸模块
return ` from '/@modules/${s1}'`;
}
});
}
app.listen(3001, () => {
console.log("kvite start!");
});