在日常项目开发中构建工具是必不可少的,所以也是我们需要掌握学习的一个重要部分,在技术蓬勃发展的今天,许多优秀的构建工具涌现出来,前端主流打包工具主要有 grunt,gulp,rollup,webpack,vite,parcel等
那么接下来我们就介绍下比较常用的三个打包工具rollup,webpack,vite对比下这几个打包工具的优缺点和适用场景
tip:vite是下一代的前端工具链而非传统意义上的打包工具(这里有点标题党了,感谢大佬指正)
rollup---适合打包类库
Rollup 是一个 JavaScript 模块打包器,他所负责的工作只是把我们写的代码转化为js
特点:原生支持tree shaking(去除无用代码),支持同时生成umd、commonjs,es的代码
缺点:
- 模块过于静态化,HMR很难实现
- 仅面向ES module,无法可靠的处理
commonjs以及umd依赖
rollup基本配置
import resolve from 'rollup-plugin-node-resolve';// 提供解析import引入第三方依赖支持(node_modules里的文件)
import commonjs from '@rollup/plugin-commonjs';//插件将它们转换为ES6版本
import postcss from 'rollup-plugin-postcss';
import image from '@rollup/plugin-image';
import babel from 'rollup-plugin-babel';//将代码转译为浏览器或者node支持的语法,
import json from 'rollup-plugin-json'//引入该插件可以让源码中可以用import引入json文件
import serve from 'rollup-plugin-serve';//本地服务
import livereload from 'rollup-plugin-livereload';//热更新
const input = 'src/index.js'
export default {
input, // 打包入口//必填
external:[],//指出应该将哪些模块看做外部模块,不和我们的源码包打在一起,该参数接收数组或者参数为模块名称的函数,返回true则将被看做外部引入不打包在一起
output: [{ // 打包出口
file: 'dist/index.js', // 最终打包出来的文件路径和文件名,这里是在package.json的browser: 'dist/index.js'字段中配置的
format: 'umd', // 必填//umd是兼容amd/cjs/iife的通用打包格式,适合浏览器//不设置该选项导出代码无法执行(无法识别export)
name:'index',//导出的文件名称//必填
globals:{//设置全局变量?
jquery:"$"
}
},
{
file: input.replace('src/', 'dist/').replace('.js', '.cjs'),
format: 'cjs'
},{
file: input.replace('src/', 'dist/').replace('.js', '.mjs'),
format: 'esm'
}],
plugins: [ // 打包插件
postcss({
extensions: [ '.css' ],
}),
resolve(), // 查找和打包node_modules中的第三方模块
commonjs(), // 将 CommonJS 转换成 ES2015 模块供 Rollup 处理,用在其他插件转换源码之前,放置其他插件改变破坏CommonJS的检测
babel({
exclude: 'node_modules/**' // 只转译我们的源代码
}),
image(),
json(),//让源码可以引入json 文件
// 热更新 默认监听根文件夹
livereload({watch:'dist'}),
// 本地服务器
serve({
open: true, // 自动打开页面
port: 8000,
openPage: '/src/test.html', // 打开的页面
contentBase: ''
})
]
};
webpack---适合打包项目
配置项复杂,社区丰富 在webpack项目中需要使用的静态资源,css,以及less等预处理器需要手动引入对应的loader才能使用, 支持代码分割,热更新,丰富的插件系统,通过使用loader可以解析各种类型的资源等
缺点:冷启动,热更新随着项目体量增大而变慢,体量大的项目热更新一次甚至需要几分钟
webpack的底层原理 找到入口->形成文件之间的依赖关系树=>通过loader处理对应资源=>生成打包结果=>启动本地服务进行渲染=>浏览器请求,整个包返回
在整个工作过程的每个环节都有预留钩子,插件可以在不同环节进行一些自定义任务
webpack原理图:
webpack基本配置
//webpack.base.config.js
const path = require('path')// 引入 path 模块
const CopyWebpackPlugin = require('copy-webpack-plugin')// 引入静态资源复制插件
const isProduction = process.env.NODE_ENV === 'production'// 判断当前环境是否是生产环境
const MiniCssExtractPlugin = require('mini-css-extract-plugin')// 样式单独分离到一个文件中
module.exports = {
// 打包入口地址
entry: {
// 由于可能是多页,所以采用对象的形式
index: ['./src/views/index/index.js']
},
// 模块resolve的规则
resolve: {
//自动的扩展后缀,比如一个js文件,则引用时书写可不要写.js
extensions: ['.js', '.json', '.css', '.less'],
// 路径别名
alias: {
'@': path.join(__dirname, '../src')
}
},
// 构建目标
target: ['web', 'es5'],
// context 是 webpack entry 的上下文,是入口文件所处的目录的绝对路径,默认情况下是当前根目录。
// 由于我们 webpack 配置文件放于 build 目录下,所以需要重新设置下 context ,使其指向根目录。
context: path.resolve(__dirname, '../'),
plugins: [
// 把public的一些静态文件复制到指定位置,排除 html 文件
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, '../public'),
globOptions: {
dot: true,
gitignore: false,
ignore: ['**/*.html']
}
}
]
})
],
module: { // 不同类型模块的处理规则
rules: [// 处理 css、less 文件
{
test: /\.(css|less)$/,
use: [
isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: !isProduction, // 是否使用source-map
esModule: false// 兼容IE11
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: !isProduction // 是否使用source-map
}
},
'less-loader'
]
},
{// 解析 html 中的 src 路径
test: /\.html$/,
use: 'html-loader'
},
{// 对图片资源文件进行处理,webpack5已经废弃了url-loader,改为type
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
type: 'asset',
exclude: [path.resolve(__dirname, 'src/assets/imgs')],
generator: {
filename: 'imgs/[name].[contenthash][ext]'
}
},
{// 对字体资源文件进行处理,webpack5已经废弃了url-loader,改为type
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'fonts/[name].[contenthash][ext]'
}
},
{// 对音频资源文件进行处理,webpack5已经废弃了url-loader,改为type
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
type: 'asset',
exclude: [path.resolve(__dirname, 'src/assets/medias')],
generator: {
filename: 'medias/[name].[contenthash][ext]'
}
},
{
test: /\.(m|j)s$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
]
}
]
}
}
//webapck.dev.config.js
const { merge } = require('webpack-merge')// 引入合并对象插件
const path = require('path')// 引入 path 模块
const HtmlWebpackPlugin = require('html-webpack-plugin')// 引入打包html插件
const baseWebpackConfig = require('./webpack.base.config')// 引入基础配置文件
const devWebpackConfig = merge(baseWebpackConfig, {
// 模式,必填项
mode: 'development',
// 开启持久化缓存
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
output: {
// 输出文件目录
path: path.resolve(__dirname, '../dist'),
// 输出文件名
filename: 'js/[name].js',
},
// 源码映射
devtool: 'eval-cheap-module-source-map',
// 开发服务配置
devServer: {
// 服务器 host,默认为 localhost
host: '0.0.0.0',
// 服务器端口号,默认 8080
port: 7001,
// 静态资源属性
static: {
// 挂载到服务器中间件的可访问虚拟地址
// 例如设置为 /static,在访问服务器静态文件时,就需要使用 /static 前缀
// 相当于webpack-dev-server@3.X的 contentBasePublicPath 属性
publicPath: './',
// 告诉服务器从哪里提供内容
directory: path.join(__dirname, '../public')
},
// 需要监听的文件,由于是多页应用,无法实现热更新,所以都只能刷新页面
watchFiles: ['src/**/*']
},
// 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/views/index/index.html',
favicon: path.resolve(__dirname, '../public/favicon.ico')
})
]
})
module.exports = devWebpackConfig
vite---适合打包项目
开箱即用 对于项目中需要的静态资源,html,less等无需手动引入loader来处理这些资源,直接引入对应的库即可
vite的底层原理 开启web服务器=>浏览器请求对应资源=>本地服务器编译对应文件=>编译结果返回给浏览器渲染;
vite原理图
启动服务器=> 请求模块时按需动态编译显示;跟webpack相比开发环境的又是在项目大的时候优势就很明显了
开发环境:
基于esbuild进行预构建,不进行打包操作,依托于浏览器本身对es module的解析,热更新时只更新修改部分,从而达到短时间更新的效果
生产环境:
通过rollup进行打包,rollup支持原生的tree shaking所以打出来的包体量小 配置项主要分为以下几大类,
- resolve,plugins等共享配置,
- build,打包配置
- build.rollupOptions(rollup底层的配置,将合并vite本身默认的rollup选项),
- server,本地服务配置
- preview,预览配置
- optimizeDeps,
- ssr:服务端渲染配置
vite基本配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import jsx from '@vitejs/plugin-vue-jsx'
// 加别名的三种方法
// const path = require("path");//2
import { resolve } from "path" //3 主要用于alias文件路径别名
// // 加别名的函数3
// function pathResolve(dir) {
// return resolve(__dirname, ".", dir)
// }
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),jsx()],
resolve:{
alias:[{//1别名配置
find: '@',
replacement: '/src',
},{
find: '_v',
replacement: '/src/views',
},{
find: '_c',
replacement: '/src/components',
}],
// alias:{ "/c": path.resolve(__dirname, "./src/components"),},//2需要用绝对路径
// alias:{ "/c": pathResolve("src/components"),},//3文件格式已被处理,根目录前不用加/
extensions:['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json','vue']
},
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
// nested: resolve(__dirname, 'nested/index.html')
}
}
},
server:{
hmr: true, // 开启热更新
proxy:{
// 选项写法
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
}
})
| 对比项 | rollup | webpack | vite |
|---|---|---|---|
| 启动速度 | / | 慢:运行机制:会先进行打包操作,完成之后服务再去请求资源,时间自然就长了 ;底层原理:js写的 | 快:运行机制:不进行打包操作就不用分析模块依赖,编译等操作,直接启动开发服务器,然后按需编译(得益于现代浏览器本身支持es-module);底层原理:基于esbuild进行预构建,速度只有webpack的2%-3%;esbuild用go写的,速度比js写的快10-100倍,go(纳秒级)语言本身相对js(毫秒级)来说就有很大的优势,(并行,线程之间共享内存等)blog.csdn.net/weixin_4386… |
| 热更新速度 | / | 慢:需要重新将这个模块的所有依赖重新编译 | 快:某个模块内容改变,重新请求该模块 |
| 打包速度 | |||
| 打包体积大小 | 借助es6模块的静态分析,只打包用的到代码,比Webpack和Browserify使用的CommonJS模块机制更高效 | 需手动优化,通过插件(uglify)来进行代码压缩 tree shaking | 有默认配置项,利用rollup进行打包,rollup原生支持tree shaking和esm |
| 配置难度 | 小 | 大 | 小,开箱即用,有默认的优化配置 |
| 兼容性 | / | / | 开发环境因为esm不支持IE,移动端百度,uc不支持 |
| 生态,社区活跃度 | 活跃度高,社区丰富,老牌打包工具 | 新崛起的工具,社区相比webpack没那么丰富 | |
| 使用上 | / | 文件结构不同,可以用require引入资源,通过process.env获取环境变量等 | 项目中不支持reuqire,通过import引入资源,import.meta.env获取环境变量,变量名必须以VITE_开头 |
问题集锦
- 既然esbuild构建速度这么快为什么vite不用esbuild打包呢?
答:目前暂不支持代码分隔和css相关处理,但是生产环境的代码仍需一些分包功能来提升应用性能
vite使用问题集锦(vite2.x+vue2.x)
- 使用jsx报错,在script里加上lang="jsx"
- 不支持require改用import下的方法引入资源,官方解释,ssr不支持require 静态资源导入可使用一下两种方法进行导入替代require,或者直接使用路径导入
tip:需要转载的小伙伴请注明出处哦