2021年2月17日,Vite 2.0发布了,并在前端圈引起了轰动。
引起轰动的原因如下:
◎ 去掉了打包步骤,可快速冷启动。
◎ 可及时热更新模块,不会随着模块变多而使得热更新变慢。
◎ 真正的按需编译。
Vite是基于浏览器native的ES module开发的,基于Bundleless思想。
在了解Vite之前,需要先了解Bundle和Bundleless。Bundle和Bundleless是两种开发方式,自2015年ESM标准发布后,这两种开发方式就逐渐明确。
在日常开发中,一般使用Webpack 对代码进行编译,并打包生成Bundle文件。其原因如下:
◎ 很多应用都运行在HTTP/1.1上,并且各浏览器有连接限制。
◎ 系统不能直接运行浏览器不支持的模块,如CommonJS。
◎ 浏览器不识别新的语法。
◎ 代码依赖关系与顺序管理。
但是,在项目达到一定规模后,基于Bundle构建优化的“收益”就变得越来越少,无法实现质的提升。
Webpack 变慢的主要原因是,它将各个资源打包整合在一起形成 Bundle,项目规模越大,资源就越多。是否可以不用打包直接在浏览器中执行代码呢?当然可以,这就是Bundleless,如图1所示。
图1
在Bundleless模式下,应用不再需要构建成一个完整的Bundle,修改文件时也不需要重新生成Bundle文件,浏览器只需重新加载单个文件即可。也就是说,只需刷新即可即时生效。如果有 HotModuleReplace等相关技术加持,则可以实现完美的开发体验。
实现 Bundleless 一个很重要的前提是模块的动态加载能力,实现这个功能的主要思路有两个:
◎ 使用System.js之类的ES模块加载器,优点是具有很好的模块兼容性。
◎ 直接利用标准的ES module。该module实现已经标准化,并且各个浏览器厂商也已纷纷支持(Edge 79、Firefox 67、Chrome 63、Safari 11.1、Opera 50,这几个浏览器支持ES module的最低版本 )。相信以后前端同时整体架构和在各种标准化的基础上也会变得更加简单。
Bundle和Bundleless的对比如表1所示。
表1
基于ES module的构建,其实Vite并不是首创,同样的实战在之前有类似的“轮子”,如esbuild、snowpack、es-dev-server等。下面通过示例讲解Vite是如何进行开发的。
与常见的开发工具一样,Vite提供了用npm或者Yarn一键生成项目结构的方式。这里使用Yarn生成一个React项目。
yarn create vite-app vite-project
cd vite-project
yarn install
结果如图2所示。
图2
index.html为页面入口;main.jsx为系统主入口;vite.config.js为配置文件,该文件可以类比Vue项目的vue.config.js。
在项目开始之前,先引入几个项目核心库:核心库react-router-dom和history、UI库Ant Design、AJAX库axios和CSS预处理器Less。
首先配置组件库,因为在后面的组件中会用到UI组件。注意,组件库可以在配置文件中引入,而不是在main.jsx中引入。如果在main.jsx中引入,则在创建项目时构建工具会引入整个CSS文件,这是没有必要的。
Vite需要借助插件vite-plugin-imp 来按需加载。
yarn add vite-plugin-imp -D
在vite.config.js中配置插件。
import vitePluginImp from 'vite-plugin-imp'
plugins: [
vitePluginImp({
libList: [
{
libName: "antd",
style: (name) => `antd/lib/${name}/style/index.less`,
},
],
})
],
css: {
preprocessorOptions: {
less: {
//内联 JavaScript
javascriptEnabled: true,
}
}
}
CSS预处理器提取公有CSS变量及CSS函数并放在一个文件中,所以确认增加以上配置。并且配置javascriptEnabled为true,支持Less内联JavaScript。
另一个比较实用的功能是自动刷新,Vite也没有掉队,借助插件@vitejs/plugin-react-refresh即可实现。
import reactRefresh from '@vitejs/plugin-react-refresh'
plugins: [
reactRefresh()
]
短路径配置
resolve: {
alias: {
"@": path.resolve(__dirname, 'src')
}
},
代理配置
server : {
proxy: {
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
环境参数配置
在日常开发中,有些代码和配置是要区分环境的。在 Webpack 中,可以在 scripts 中定义NODE_ENV,或者在webpack .config.js中定义DefinePlugin来区分环境。在Vite中,可以在scripts中定义mode来区分环境。
"dev": "vite --mode development",
//环境值
const env = process.argv[process.argv.length - 1]
console.log("当前环境:", env) // development
我们先在container目录下新建两个组件:home和main。当path为“/”时渲染home组件,当path为“/main”时渲染main组件。
// container/home/home.jsx
import { Button } from 'antd'
function Home() {
return (
<div>
<div>Home, from router</div>
<Button type="primary">submit</Button>
</div>
);
}
//container/main/index.jsx
function Main() {
return (
<div>
Main, from router
</div>
);
}
有了组件之后,下面开始配置router。在routers目录下新建index.js。
import Home from "@/contanier/home"
import Main from "@/contanier/main"
export default [
{
path: "/",
component: Home
},
{
path: "/main",
component: Main
}
]
定义配置后,需要在app.jsx中遍历这个数组,生成路由配置。
//app.jsx
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import routes from "./routers"
function App() {
const [count, setCount] = useState(0)
return (
<Router>
<div className="App">
<header className="App-header">
// 省略部分代码
<Switch>
{
routes.map(route => <Route exact key={route.path} path={route.path}>
<route.component />
</Route>)
}
</Switch>
</header>
</div>
</Router>
)
}
启动项目
yarn run dev
vite v2.1.2 dev server running at:
> Local: http://localhost:3000/
> Network: http://192.168.1.6:3000/
> Network: http://192.168.192.196:3000/
ready in 1982ms.
结果如图3所示。
图3
输入http://localhost:3000/main,结果如图4所示。
图4
有了页面组件之后,就需要考虑AJAX请求的事儿了,否则页面是没有灵魂的。在api目录下新建request.js,对axios做一层封装,配置请求拦截器和响应拦截器,这也是前端开发中的通用做法。
import axios from "axios";
import StatusCode from "@/constants/statusCode";
const instance = axios.create({
baseURL: "",
timeout: 50000,
xsrfCookieName: "xsrf-token",
});
//请求拦截器,如果需要在header中增加一些参数,则可以在这里统一处理
instance.interceptors.request.use(
(config) => {
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 添加一个响应拦截器,对每次返回的数据进行拦截,并进行业务判断
instance.interceptors.response.use(
(response) => {
return Promise.reject(response.data);
},
(error) => {
return Promise.reject(error);
}
);
axios拦截器为我们的日常开发提供了诸多便利,如果需要在每个请求中增加相同的参数,则可以在请求拦截器中进行配置。如果是统一处理返回的数据,如无权限、404、没有登录等这种通用场景,则可以统一在响应拦截器中进行处理。
以上是Vite配合React开发的基本配置。
▼
本文节选自《前端开发必知必会:从工程核心到前沿实战》一书,欢迎阅读本书了解更多关于前端开发必知必会的知识点。