vite基本实现

4 篇文章 0 订阅

vite基本实现

1. 设置入口并链接全局

  • 编写入口文件
// /bin/www.js
#! /usr/bin/env node

console.log('入口调用成功!!!')
  • 配置package.json
{
  "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执行结果

2. 搭建本地服务

  • 安装相关插件
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;

3. 静态目录托管

  • 根据传入的项目路径和app实例,使用中间件进行托管设置
// /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中

4. 修改第三方包引入路径

为了防止浏览器报错对第三方包引入路径报错,对此类路径加上/@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;

5. 解析路径映射表

  • 将对vue引入的url路径映射为读取文件的路径
// /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;

6. 转化.vue文件

步骤
  • 获取.vue内容
  • 获取模板解析方法
  • 修改template的内容
  • 修改script的内容
  • 对于?type=template的请求返回模板编译结果
// /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;

7. 往html添加环境变量

// /plugins/serverHtmlRewrite.js
const { readBody } = require('./utils');
function htmlRewrite({ app, root }) {
  const inject = `
    <script>
    window.process = {
      env: { NODE_ENV: 'development' }
    }
    </script>`;
  app.use(async (ctx, next) => {
    await next();
    if (ctx.body && ctx.response.is('html')) {
      let content = await readBody(ctx.body);
      ctx.body = content.replace(/<head>/, `$&${inject}`);
    }
  })
}
module.exports = htmlRewrite;

8. 总结

总的来说,vite的原理就是起一个静态服务器,使用ES6Module进行数据请求,普通文件直接通过路径获取,特殊文件则通过处理请求路径,转化为磁盘路径,fs读取文件内容后放到响应头中,同时对于.vue文件在服务端进行template的编译,前端通过新请求获得template编译结果挂载到__script.render上;

note:源码可通过npm install yuan-vite拉取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值