写在结尾
- 因为
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…
- 首先是 koa 启动监听端口,用于拜访热更新服务
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
- 生成模板实现后,执行命令启动我的项目
yarn
yarn 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-check
import 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