写在结尾
- 因为
vite
这个构建工具被用在了vue3上门,而且它的构建思路我感觉优于webpack
,底层也是应用了esbuild
,性能上更优 - 那么为了关照一些小伙伴之前没有学习过
vite
的,咱们先来看看什么是vite
什么是vite
- Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,齐全跳过了打包这个概念,服务器随起随用,反对热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则能够把同一份代码用 rollup 打包
vite
的人造劣势:
- 疾速冷启动服务器
- 即时热模块更换(HMR)
- 真正的按需编译
vite工作原理
- 当申明一个 script 标签类型为 module 时
如: <script type="module" src="/src/main.js"></script>
- 浏览器就会像服务器发动一个GET
http://localhost:3000/src/main.js申请main.js文件:
// /src/main.js:import { createApp } from 'vue'import App from './App.vue'createApp(App).mount('#app')
- 浏览器申请到了main.js文件,检测到外部含有import引入的包,又会对其外部的 import 援用发动 HTTP 申请获取模块的内容文件
- 如:
GET http://localhost:3000/@modules/vue.js
- 如:
GET http://localhost:3000/src/App.vue
- 其Vite 的次要性能就是通过劫持浏览器的这些申请,并在后端进行相应的解决将我的项目中应用的文件通过简略的合成与整合,而后再返回给浏览器渲染页面,vite整个过程中没有对文件进行打包编译,所以其运行速度比原始的webpack开发编译速度快出许多
简略实现vite
- 因为代码量有一些大,我就不本人去写了,间接拿了他人的代码过去,原文地址是:
https://juejin.cn/post/689811...
function createServer() { let app = new Koa() const context = { // 间接创立一个上下文 来给不同的插件共享性能 app, root: process.cwd() //执行node命令的那个命令门路 } // 运行koa中间件(就是咱们的vite插件) resolvePlugin.forEach(plugin => plugin(context)) return app}createServer().listen(4000, () => {})
- 编写对应插件解决
- 首先解决模块的援用,因为浏览器只有相对路径和绝对路径
这里readBody
其实就是一个读取文件流的办法,封装过而已,看成一般的读取流办法即可
koa中间件解决
- 首先解决重写门路,因为浏览器只有绝对路径和相对路径
app.use(async (ctx, next) => { await next(); // 动态服务 // 默认会先执行 动态服务中间件 会将后果放到 ctx.body // 须要将流转换成字符串 , 只须要解决js中的援用问题 if (ctx.body && ctx.response.is('js')) { let r = await readBody(ctx.body); // vue => /@modules const result = rewriteImports(r); ctx.body = result; } }) },
- 重写完了门路后,须要拦挡
.vue
文件和带@module
(重写门路之前就是node_modules
外面的文件)
// 2. 拦挡含有/@modules/vue的申请, 去node_modules引入对应的模块并返回 ({ app, root }) => { const reg = /^\/@modules\// app.use(async (ctx, next) => { // 如果没有匹配到 /@modules/vue 就往下执行即可 if (!reg.test(ctx.path)) { return next(); } const id = ctx.path.replace(reg, ''); let mapping = { vue: path.resolve(root, 'node_modules', '@vue/runtime-dom/dist/runtime-dom.esm-browser.js'), } const content = await fs.readFile(mapping[id], 'utf8'); ctx.type = 'js'; // 返回的文件是js ctx.body = content; }) },
- 当解析解决完门路后,咱们须要解析vue的模板文件,(如果是react的jsx代码,同理)
// 3. 解析.vue文件 ({ app, root }) => { app.use(async (ctx, next) => { if (!ctx.path.endsWith('.vue')) { return next(); } const filePath = path.join(root, ctx.path); const content = await fs.readFile(filePath, 'utf8'); // 引入.vue文件解析模板 const { compileTemplate, parse } = require(path.resolve(root, 'node_modules', '@vue/compiler-sfc/dist/compiler-sfc.cjs')) let { descriptor } = parse(content); if (!ctx.query.type) { //App.vue let code = '' if (descriptor.script) { let content = descriptor.script.content; code += content.replace(/((?:^|\n|;)\s*)export default/, '$1const __script='); } if (descriptor.template) { const requestPath = ctx.path + `?type=template`; code += `\nimport { render as __render } from "${requestPath}"`; code += `\n__script.render = __render` } code += `\nexport default __script` ctx.type = 'js'; ctx.body = code } if (ctx.query.type == 'template') { ctx.type = 'js'; let content = descriptor.template.content const { code } = compileTemplate({ source: content }); // 将app.vue中的模板 转换成render函数 ctx.body = code; } }) }, // 4. 动态服务插件 实现能够返回文件的性能 ({ app, root }) => { app.use(static(root)) app.use(static(path.resolve(root, 'public'))) }]function createServer() { let app = new Koa() const context = { // 间接创立一个上下文 来给不同的插件共享性能 app, root: process.cwd() // C:\Users\...\my-vite-vue3 } // 运行中间件 resolvePlugin.forEach(plugin => plugin(context)) return app}
- 上面是两个工具函数:一个是流的读取,一个是重写门路的函数
//读取body办法async function readBody(stream) { if (stream instanceof Readable) { return new Promise((resolve) => { let res = '' stream.on('data', function (chunk) { res += chunk }); stream.on('end', function () { resolve(res) }) }) } else { return stream; }}
const resolvePlugin = [ // 1. 重写引入模块门路后面加上/@modules/vue, 重写后浏览器会再次发送申请 ({ app, root }) => { function rewriteImports(source) { let imports = parse(source)[0]; let ms = new MagicString(source); if (imports.length > 0) { for (let i = 0; i < imports.length; i++) { let { s, e } = imports[i]; let id = source.slice(s, e); // 利用的标识 vue ./App.vue // 不是./ 或者 / if (/^[^\/\.]/.test(id)) { id = `/@modules/${id}`; ms.overwrite(s, e, id) } } } return ms.toString(); }
这样一个简略的vite就实现了
开始在react中应用
- vite算是一个新的技术,而且在国内目前不够风行,为了防止踩坑,咱们间接采纳官网举荐的模板生成
npm init vite-app --template react
yarnyarn dev
- 这样一个
react
的我的项目就搭建好了,默认应用的是17.0.0
版本的react
,这样createElement
办法再也不必从react外面导出了,我想这样jsx
格调代码也会更容易被迁徙到其余框架我的项目中
"dependencies": { "react": "^17.0.0", "react-dom": "^17.0.0" }, "devDependencies": { "vite": "^1.0.0-rc.13", "vite-plugin-react": "^4.0.0" }
- 这个模板生成的是自带热更新的,绝对比较简单,如果是有非凡需要,能够应用更多的plugin,在vite.config.js中设置
- 默认的配置
// @ts-checkimport reactPlugin from 'vite-plugin-react'/** * @type { import('vite').UserConfig } */const config = { jsx: 'react', plugins: [reactPlugin]}export default config
写在最初
- 本文更多是在讲
vite
的实现原理,目前我还没有把它应用在生产环境中 - 在我看来,
vite
如果生态能倒退起来,可能咱们就用不到wepback6
这个版本了(当然将来不可猜想) - 通过浏览本文,你必定能分明理解vite的原理和react构建应用了,感觉不错的话,帮我点个
赞/在看
,关注一下【前端巅峰
】公众号吧
参考资料:
- https://juejin.cn/post/689811...
- https://juejin.cn/post/684490...
- https://github.com/vitejs/vite