赞
踩
vite是一个开发构建工具,开发过程中它利用浏览器native ES Module特性按需导入源码,预打包依赖。
特点: 启动快,更新快
1.浏览器利用es module imports,关键变化是index.html中的入口导入方式(开发阶段需要考虑浏览器兼容)
所以,开发阶段不需要进行打包操作
本地文件使用 type=module,不支持本地路径,需要开启一个本地serve
2.第三方依赖模块预打包,并将导入地址修改为相对地址,从而统一所有请求为相对地址
预打包:第三方依赖提前打包存在node_modules中,程序启动时,直接从node_modules中去下载(浏览器只能加载相对路径)
vite需要根据请求资源类型的不同做不同的解析工作,解析的最后结果是js对象
比如app.vue 文件
import HelloWorld from "./components/HelloWorld.vue";
解析为
const _script = {
name: "App",
components: {
HelloWorld
}
}
// template 部分转换为了一个模板请求,解析结果是一个渲染函数
import {render as _render} from "src/App.vue?type=template"
// 将解析得到的render函数设置到组件配置对象上
_script.render=_render
_script._hmrId="src/App.vue""
_script._file="/Users/...../src/App.vue"
vue文件 在服务端提前被vite编译之后,直接输出给浏览器,直接使用
1、基础实现
基于koa框架,创建node服务器
基本配置:
// 一个node服务器,相当于devServer 基于koa const Koa = require("koa"); const app = new Koa(); const fs = require("fs"); const path = require("path"); const compilerSfc = require("@vue/compiler-sfc"); // 编译器,得到一个组件配置对象,解析vue文件 const compilerDom = require("@vue/compiler-dom"); // 编译模板 app.use(async (ctx) => { // 处理文件解析 }); // 设置端口监听 app.listen(4002, () => { console.log("vite start"); });
2、解析html
利用fs对文件进行读取
// 首页
ctx.type = "text/html";
ctx.body = fs.readFileSync("./index.html", "utf8");
3、解析js
跟html解析区别在意文本类型的不同
// 响应js请求
const p = path.join(__dirname, url);
ctx.type = "text/javascript";
ctx.body = fs.readFileSync(p, "utf-8");
4、第三方库的支持,比如vue
我们在代码里一般这样引入
import { createApp, h } from “vue”;
这里引入会报错
表示期望路径是绝对路径或者相对路径
第三方的依赖我们都会安装在node_modules中,所以需要去node_modules中去寻找对应的js文件,因此我们需要对路径进行重写
比如vue,我们可以给它加一个别名----/@module/vue,
一个改写函数
// 重写导入,让引用的第三方文件,变成相对地址
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 '/@module/${s1}'`;
}
});
}
名称改写好之后,我们要去node_modules里面对应的js文件
比如vue,可以先看一下vue的位置
dist/vue.runtime.esm-bundler.js 是vue文件真正的路径
所以我们需要把
import XX from “vue” 变成 … from “baseurl + /node_moudle/dist/vue.runtime.esm-bundler.js”
// 获取模块名称,就是@module/ 后面的内容
const moduleName = url.replace("/@module/", "");
// 在node_module 中找到对应的模块
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, "utf-8");
ctx.type = "text/javascript";
ctx.body = rewriteImport(ret);
看一下处理后的路径地址,对filePath的打印
如果要保证运行,还需要在index.html添加
// 这是vue的第三方包中的某个配置,这里相当于欺骗了浏览器
<script>
window.process = {
env: {
NODE_ENV: 'dev'
}
}
</script>
5、单文件组件支持,.vue文件(sfc)
实现思路:*.vue文件 => template 模板 => render函数
// 读取vue文件的内容 const p = path.join(__dirname, url.split("?")[0]); const ret = compilerSfc.parse(fs.readFileSync(p, "utf-8")); // 将vue文件中的块进行解析,得到一个ast if (!query.type) { // 没有type说明是sfc(单文件组件) // 解析sfc,处理内部的js console.log(ret); // 获取脚本内容 const scriptContent = ret.descriptor.script.content; ctx.type = "text/javascript"; ctx.body = ` ${rewriteImport(scriptContent)} // template解析转换为另一个请求单独做 import { render as __render } from '${url}?type=template' // __script.render = __render export default __render `; } else if (query.type === "template") { // 2.template => render 函数 const tpl = ret.descriptor.template.content; // 编译为包含render的模块 const render = compilerDom.compile(tpl, { mode: "module" }).code; ctx.type = "text/javascript"; ctx.body = rewriteImport(render); }
对ret进行打印,看一下script和template
对其中的content部分进行处理
6、处理.css文件
思路:创建style便签,将css文件的内容进行读取,添加到style标签中
const p = path.join(__dirname, url.split("?")[0]);
const file = fs.readFileSync(p, "utf-8");
// css 转化为js代码
// 利用js创建style标签
const content = `
const css = "${file.replace(/\n/g, "")}"
let link = document.createElement('style')
link.setAttribute('type', 'text/css')
document.head.appendChild(link)
link.innerHTML = css
export default css
`;
ctx.type = "application/javascript";
ctx.body = content;
vite插件可以扩展vite能力,比如解析用户自定义的文件输入,在打包代码前转义代码,或者查找第三方模块
1、 插件钩子
开发时,vite dev server 创建一个插件容器,按照Rollup调用创建钩子的规则请求各个钩子函数
在服务启动时调用一次:
optipns 替换或操控rollup选项(只在打包时有用,开发时为空)
buildStart 开始创建(只是一个信号)
每次有模块请求时都会调用:
resolveId 创建自定义确认函数,常用语句定位第三方依赖
load 创建自定义加载函数,可用于返回自定义的内容
transform 可用于装换已加载的模块内容
在服务器关闭时调用一次:
buildEnd
closeBundle
vite 特有钩子
config:修改Vite配置(可以配置别名)
configResolved :vite配置确认
configureServer:用于配置dev server
transformIndexHtml: 用于转换宿主页(可以注入或者删除内容)
handleHotUpdate:自定义HMR更新时调用
2、钩子调用顺序
config → configResolved → optipns → configureServer → buildStart → transform → load → resolveId → transformIndexHtml → vite dev server
3、插件顺序
别名处理 Alias
用户插件执行(如果设置 enforce : ‘pre’)
Vite 核心插件(plugin-vue)
用户插件执行(如果未设置 enforce)
Vite 构建插件
用户插件执行(如果设置 enforce : ‘post’)
Vite 构建后置插件
4、实现一个mock服务器—vite-plugin-mock
实现思路:
给开发服务器实例(concent)配置一个中间件,这个中间件可以存储用户配置接口映射信息,并提前处理输入请求,如果请求的url和路由表匹配则接管,按用户配置的handler返回结果
import path from "path"; let mockRouteMap = {}; function matchRoute(req) { let url = req.url; let method = req.method.toLowerCase(); let routeList = mockRouteMap[method]; return routeList && routeList.find((item) => item.path === url); } // 默认导出的插件工厂函数 export default function (options = {}) { // 获取mock文件入口,默认是index options.entry = options.entry || "./mock/index.js"; // 转换为绝对路径 if (!path.isAbsolute(options.entry)) { options.entry = path.resolve(process.cwd(), options.entry); } // 返回的插件 return { configureServer: function ({ app }) { // 定义路由表 const mockObj = require(options.entry); // 创建路由表 createRoute(mockObj); // 定义中间件:路由匹配 const middleware = (req, res, next) => { // 1.执行匹配过程 let route = matchRoute(req); //2. 存在匹配,是一个mock请求 if (route) { console.log("mock req", route.method, route.path); res.send = send; route.handler(req, res); } else { next(); } }; // 最终目标,给app注册一个中间件 app.use(middleware); }, }; } function createRoute(mockConfList) { mockConfList.forEach((mockConf) => { let method = mockConf.method || "get"; let path = mockConf.url; let handler = mockConf.response; // 路由对象 let route = { path, method: method.toLowerCase(), handler }; if (!mockRouteMap[method]) { mockRouteMap[method] = []; } console.log("create mock api"); // 存入映射对象中 mockRouteMap[method].push(route); }); } // 实现一个send方法 function send(body) { let chunk = JSON.stringify(body); if (chunk) { chunk = Buffer.from(chunk, "utf-8"); this.setHeader("Content-Length", chunk.length); } this.setHeader("Content-Type", "application/json"); this.statusCode = 200; this.end(chunk, "utf8"); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。