// /bin/www.js
#! /usr/bin/env node
console.log('入口调用成功!!!')
{
"name": "yuan-vite",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": {
"yuan-vite": "./bin/www.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
npm link
链接到全局,命令行输入yuan-vite
测试是否输出www.js
执行结果npm i koa koa-static
// /bin/ww.js
#! /usr/bin/env node
const createServer = require('./index.js');
const PORT = 4000;
createServer().listen(PORT, () => {
console.log(`Dev server run at http://localhost:${
PORT}`);
});
index.js
实现创建服务的函数const Koa = require('koa');
const serverStaticPlugin = require('../plugins/serverStatic');
const pathRewritePlugin = require('../plugins/serverPathRewrite');
const resolvePlugin = require('../plugins/serverResolve');
const VuePlugin = require('../plugins/serverVue');
const htmlRewritePlugin = require('../plugins/serverHtmlRewrite');
function createServer() {
const app = new Koa();
// 获取执行 yuan-vite 的项目路径
const root = process.cwd();
// 处理文件的插件数组
const plugins = [
htmlRewritePlugin,
// 路径修改
pathRewritePlugin,
// 解析.vue
VuePlugin,
// 解析vue包
resolvePlugin,
// 静态目录
serverStaticPlugin
];
plugins.forEach(plugin => plugin({
app, root }));
return app;
}
module.exports = createServer;
// /plugins/serverStatic.js
const koaStatic = require('koa-static');
const path = require('path');
function serverStaticPlugin({
app, root }) {
app.use(koaStatic(root));
app.use(koaStatic(path.join(root, 'public')));
}
module.exports = serverStaticPlugin;
index.js
引入,注册到plugins中为了防止浏览器报错对第三方包引入路径报错,对此类路径加上/@modules/
前缀
// /plugins/utils.js
// 读取数据流
function readBody(stream) {
if (stream instanceof Readable) {
return new Promise((resolve) => {
let res = '';
stream.on('data', data => res += data);
stream.on('end', () => resolve(res));
});
};
return stream.toString();
}
// /plugins/serverPathRewrite.js
const {
readBody } = require('./utils');
const {
parse } = require('es-module-lexer');
const MagicString = require('magic-string');
function reWriteBody(source) {
let imports = parse(source)[0];// 获取引入坐标信息
let magicString = new MagicString(source);// 魔法字符串对象
imports.forEach(({
s, e }) => {
// 起始位和终止位
let importPath = source.substring(s, e);
// 第三方引入加前缀
if (/^[^\/\.]/.test(importPath)) {
magicString.overwrite(s, e, `/@modules/${
importPath}`);
}
})
return magicString.toString();
}
function pathRewritePlugin({
app, root }) {
app.use(async (ctx, next) => {
await next();
// 只对js文件处理
if (ctx.body && ctx.response.is('js')) {
let content = await readBody(ctx.body);// 响应体内容
ctx.body = reWriteBody(content);
}
})
}
module.exports = pathRewritePlugin;
// /plugins/utils.js
// 构建vue文件真实路径映射表
function resolveVue(root) {
// 获取compiler-sfc的目标文件
const compilerPkgPath = path.join(root, 'node_modules', '@vue/compiler-sfc/package.json');
const compilerPkg = require(compilerPkgPath);
const compilerPath = path.join(path.dirname(compilerPkgPath), compilerPkg.main);
// 获取 runtime-dom runtime-core reactivity shared 对应的真实路径
const resolve = name => path.resolve(root, 'node_modules', `@vue/${
name}/dist/${
name}.esm-bundler.js`);
const runtimeDomPath = resolve("runtime-dom");
const runtimeCorePath = resolve("runtime-core");
const reactivePath = resolve("reactivity");
const sharedPath = resolve("shared");
// 返回映射表
return {
compiler: compilerPath,
"@vue/runtime-dom": runtimeDomPath,
"@vue/runtime-core": runtimeCorePath,
"@vue/reactivity": reactivePath,
"@vue/shared": sharedPath,
vue: runtimeDomPath
}
}
// /plugins/serverResolve.js
const fs = require('fs').promises;
const {
resolveVue } = require('./utils');
function resolvePlugin({
app, root }) {
// 获取路径映射表
const vueResolved = resolveVue(root);
console.log(JSON.stringify(vueResolved, null, " "));
// 拦截请求
app.use(async (ctx, next) => {
const moduleREG = /\/@modules\//;
if (!moduleREG.test(ctx.path))
return next();
let id = ctx.path.replace(moduleREG, '');// 去掉前缀
ctx.type = "js";
// console.log(id, "----", vueResolved[id])
let content = await fs.readFile(vueResolved[id], 'utf8');// 读取真实路径
ctx.body = content;// 返回读取出来的数据
})
}
module.exports = resolvePlugin;
// /plugins/serverVue.js
const path = require('path');
const fs = require('fs').promises;
const {
resolveVue } = require('./utils');
function VuePlugin({
app, root }) {
app.use(async (ctx, next) => {
if (!ctx.path.endsWith('.vue')) {
return next();
};
// 1. 获取.vue文件内容
let filePath = path.join(root, ctx.path);
let content = await fs.readFile(filePath, 'utf8');
// 2. 获取compiler内的方法
const {
parse, compileTemplate } = require(resolveVue(root).compiler);
const {
descriptor } = parse(content);
// 修改.vue文件的内容
if (!ctx.query.type) {
let code = '';
// 替换script
if (descriptor.script) {
let {
content } = descriptor.script;
const defaultExportREP = /((?:^|\n|;)\s*)export default/;
code += content.replace(defaultExportREP, `$1const __script = `);
}
// 替换template
if (descriptor.template) {
const templateRequest = ctx.path + '?type=template';
code += `\nimport { render as __render } from ${
JSON.stringify(templateRequest)}`;
code += `\n __script.render = __render`;
}
ctx.type = "js";
code += `\nexport default __script`;
ctx.body = code;
}
// 获取模板编译后的文件
if (ctx.query.type == 'template') {
ctx.type = "js";
let {
content } = descriptor.template;
const {
code } = compileTemplate({
source: content });
// 返回模板编译后的代码
ctx.body = code;
}
})
}
module.exports = VuePlugin;
// /plugins/serverHtmlRewrite.js
const {
readBody } = require('./utils');
function htmlRewrite({
app, root }) {
const inject = `
`;
app.use(async (ctx, next) => {
await next();
if (ctx.body && ctx.response.is('html')) {
let content = await readBody(ctx.body);
ctx.body = content.replace(//, `$&${
inject}`);
}
})
}
module.exports = htmlRewrite;
总的来说,vite的原理就是起一个静态服务器,使用ES6Module进行数据请求,普通文件直接通过路径获取,特殊文件则通过处理请求路径,转化为磁盘路径,fs读取文件内容后放到响应头中,同时对于.vue文件在服务端进行template的编译,前端通过新请求获得template编译结果挂载到__script.render
上;
note:源码可通过npm install yuan-vite
拉取