注:webpack本身只能处理js、json等文件资源,其他的文件需要loader、plugin进行配合才能支持
简介:
功能:
webpack功能比较局限,但可以通过plugins和loader进行扩展,从而使其功能强大
开发模式:仅能编译js中的ES Module语法
生产模式:仅能编译js中的ES Module语法,压缩js代码
打包方式:
它会以一个或多个文件作为打包的入口(单页面或多页面),将项目所有的文件编译打包为一个或多个bundle文件输出
基础使用
第一步:初始化package.json
npm init -y
第二步:安装webpack及webpack-cli
npm i webpack webpack-cli -D
第三步:创建webpack.config.js文件并配置mode和entry
module.exports = {
mode: 'development',
entry: './main.js',
};
第四步:配置脚本指令
"scripts": {
"dev": "webpack"
}
五大核心配置
1.entry(入口文件)
2.output(输出文件)
指示webpack编译打包完的文件输出到哪个位置,如何命名等
path:
filename:
publicPath:
所有资源文件的基础路径
output: {
path:path.resove(__dirname,'dist'),
filename:'bundle.js'
},
3.loader(加载器)
webpack本身只能处理js、json等文件资源,其他资源如:图片、css、字体、vue等文件资源需要借助loader来进行支持
// 单个写法
{
test:'正则',
loader:'xxx-loader',
option:{}
}
// 多个写法
{
test:'正则',
use:[
'xxx-loader',
{
loader:'xxx-loader1,
option:{}
},{
loader:'xxx-loader2',
option:{}
}
]
}
4.plugins(插件)
5.mode(模式)
主要分为develpoment、production
module.exports = {
entry: '入口地址',
output: {输出配置},
module: {
rules: [
loader配置
],
},
plugins: [
plugin配置
],
mode: '生产环境',
};
其他常用模块
1.resolve(解析)
alias:
extensions:
modules:
告诉 webpack 解析模块时应该搜索的目录。
resolve: {
/* 配置路径别名,优点:简写路径;缺点:没有提示 */
alias: {
$css: resolve(__dirname, "src/css")
},
/* 配置省略文件后缀名 */
extensions: [".js", ".json", ".css", "jsx"],
/* 告诉 webpack 解析模块去哪个目录找 */
modules: [resolve(__dirname, "../../node_modules"), "node_modules"]
}
2.Externals(外部扩展)
externals: {
jquery: 'jQuery',
},
3.devServer(本地服务)
devServer: {
// 运行代码的目录
contentBase: resolve(__dirname, 'build'),
// 监视contentBase目录下的所有文件,一旦有变化就会reload
watchContentBase: true,
watchOptions:{
// 忽略文件
ignored: /node_modules/
},
// 启动gzip压缩
compress: true,
// 端口号
port: 5000,
// 域名
host: 'localhost',
// 自动打开浏览器
open: true,
// 开启HMR功能
hot: true,
// 不要显示启动服务器的日志
clientLogLevel: 'none',
// 除了一些基本启动信息外,其他内容都不要显示
quiet: true,
// 如果出现错误信息,不要全屏提示
overlay: false,
// 服务器代理,解决开发环境下跨域问题
proxy: {
// 一旦devserver(5000)服务器接收到 /api/xxx的请求,就会把请求转发到另外一个服务器(3000)
'/api': {
target: 'http://localhost:3000',
// 发送请求时,请求路径重写:将/api/xxx -> /xxx(去掉/api)
pathRewrite:{
'^/api': ''
}
}
}
}
开发模式
- 支持css及css预处理
- 支持高版本js
- 支持图片
- 支持fonts
- 支持其他资源
- html自动引入脚本
- 本地服务
```c
module.exports = {
mode: ‘development’,
output: {
path: ‘/dist’,
filename: ‘js/[name].js’,
publicPath: ‘/‘,
chunkFilename: ‘js/[name].js’,
},
resolve: {
alias: {
}, extensions: [‘.mjs’, ‘.js’, ‘.jsx’, ‘.vue’, ‘.json’, ‘.wasm’], modules: [‘node_modules’, ‘/node_modules’, ‘/node_modules\@vue\cli-service\node_modules’], }, module: { noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/, rules: ['@': '/src',
], }, plugins: [ new VueLoaderPlugin(), //支持vue-loader new HtmlWebpackPlugin({/* config.module.rule('vue') */ { test: /\.vue$/, use: ['cache-loader', 'vue-loader'], }, /* config.module.rule('images') */ { test: /\.(png|jpe?g|gif|webp)(\?.*)?$/, use: [ { loader: 'url-loader', options: { limit: 4096, name: 'img/[name].[hash:8].[ext]', }, }, ], }, /* config.module.rule('svg') */ { test: /\.(svg)(\?.*)?$/, use: [ { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]', }, }, ], }, /* config.module.rule('media') */ { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, use: [ { loader: 'url-loader', options: { limit: 4096, name: 'media/[name].[hash:8].[ext]', }, }, ], }, /* config.module.rule('fonts') */ { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, use: [ /* config.module.rule('fonts').use('url-loader') */ { loader: 'url-loader', options: { limit: 4096, name: 'fonts/[name].[hash:8].[ext]', }, }, ], }, /* config.module.rule('css') */ { test: /\.css$/, use: ['style-loader', 'css-loader'], }, /* config.module.rule('scss') */ { test: /\.scss$/, use: ['style-loader', 'css-loader', 'scss-loader'], }, /* config.module.rule('sass') */ { test: /\.sass$/, use: ['style-loader', 'css-loader', 'sass-loader'], }, /* config.module.rule('less') */ { test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'], }, /* config.module.rule('stylus') */ { test: /\.styl(us)?$/, use: ['style-loader', 'css-loader', 'stylus-loader'], }, /* config.module.rule('js') */ { test: /\.m?jsx?$/, use: ['cache-loader', 'babel-loader'], },
}), ], entry: { app: [‘./src/main.js’], }, devServer: { host: ‘0.0.0.0’, // 项目运行时的本地地址 port: 8880, // 端口号 open: true, // 配置自动启动浏览器 }, };title: 'vue_template', template: '/public/index.html',
<a name="wPKdl"></a>
## 生产环境
1. 支持css及css预处理、压缩css、兼容css、抽离css(开发模式使用的style-loader通过js插入到head当中,需要等到js全部解析完成,才开始插入,页面会出现闪屏,为了解决这一问题,生产环境需要抽离css)、通过contentHash进行命名,利用浏览器缓存进行优化
1. 支持高版本js、压缩js(terser-webpack-plugin)、通过contentHash进行命名,利用浏览器缓存进行优化
1. 支持图片、通过hash进行命名,利用浏览器缓存进行优化
1. 支持fonts、通过hash进行命名,利用浏览器缓存进行优化
1. 支持其他资源、通过hash进行命名,利用浏览器缓存进行优化
1. 对html进行压缩(HtmlWebpackPlugin)
```c
module.exports = {
mode: 'production',
context: '',
devtool: 'source-map',
output: {
path: '/dist',
filename: 'js/[name].[contenthash:8].js',
publicPath: '/',
chunkFilename: 'js/[name].[contenthash:8].js',
},
resolve: {
alias: {
'@': '/src',
vue$: 'vue/dist/vue.runtime.esm.js',
},
extensions: ['.mjs', '.js', '.jsx', '.vue', '.json', '.wasm'],
modules: ['node_modules', '/node_modules', '/node_modules\\@vue\\cli-service\\node_modules'],
},
module: {
noParse: /^(vue|vue-router|vuex|vuex-router-sync)$/,
rules: [
/* config.module.rule('vue') */
{
test: /\.vue$/,
use: ['cache-loader', 'vue-loader'],
},
/* config.module.rule('images') */
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'img/[name].[hash:8].[ext]',
},
},
],
},
/* config.module.rule('svg') */
{
test: /\.(svg)(\?.*)?$/,
use: [
/* config.module.rule('svg').use('file-loader') */
{
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]',
},
},
],
},
/* config.module.rule('media') */
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'media/[name].[hash:8].[ext]',
},
},
],
},
/* config.module.rule('fonts') */
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
/* config.module.rule('fonts').use('url-loader') */
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'fonts/[name].[hash:8].[ext]',
},
},
],
},
/* config.module.rule('css') */
{
test: /\.css$/,
use: [
{
loader: 'mini-css-extract-plugin',
options: {
hmr: false,
publicPath: '../',
},
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]',
},
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: false,
},
},
],
},
/* config.module.rule('scss') */
{
test: /\.scss$/,
use: [
{
loader: 'mini-css-extract-plugin',
options: {
hmr: false,
publicPath: '../',
},
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]',
},
},
},
{
loader: 'scss-loader',
options: {
sourceMap: false,
},
},
],
},
/* config.module.rule('sass') */
{
test: /\.sass$/,
use: [
{
loader: 'mini-css-extract-plugin',
options: {
hmr: false,
publicPath: '../',
},
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]',
},
},
},
{
loader: 'sass-loader',
options: {
sourceMap: false,
},
},
],
},
/* config.module.rule('less') */
{
test: /\.less$/,
use: [
{
loader: 'mini-css-extract-plugin',
options: {
hmr: false,
publicPath: '../',
},
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]',
},
},
},
{
loader: 'less-loader',
options: {
sourceMap: false,
},
},
],
},
/* config.module.rule('stylus') */
{
test: /\.styl(us)?$/,
use: [
{
loader: 'mini-css-extract-plugin',
options: {
hmr: false,
publicPath: '../',
},
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: {
localIdentName: '[name]_[local]_[hash:base64:5]',
},
},
},
{
loader: 'stylus-loader',
options: {
sourceMap: false,
},
},
],
},
/* config.module.rule('js') */
{
test: /\.m?jsx?$/,
use: [
'cache-loader',
'thread-loader',
'babel-loader'
],
},
],
},
plugins: [
/* config.plugin('vue-loader') */
new VueLoaderPlugin(),
/* config.plugin('extract-css') */
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css',
}),
/* config.plugin('optimize-css') */
new OptimizeCssnanoPlugin({
sourceMap: false,
cssnanoOptions: {
preset: [
'default',
{
mergeLonghand: false,
cssDeclarationSorter: false,
},
],
},
}),
/* config.plugin('html') */
new HtmlWebpackPlugin({
title: 'vue_template',
minify: {
removeComments: true,
collapseWhitespace: true,
collapseBooleanAttributes: true,
removeScriptTypeAttributes: true,
},
template: '/public/index.html',
}),
],
entry: {
app: ['./src/main.js'],
},
};
vue-cli开发环境webpack配置
npx vue-cli-service inspect —mode development >> webpack.config.development.js
vue-cli生产环境webpack配置
npx vue-cli-service inspect —mode production >> webpack.config.production.js
webpack优化
提升webpack打包速度
hotModuleReplacement(热模块):默认开启
开发时我们修改了其中一个模块代码,webpack默认会将所有模块全部重新打包编译,速度会很满。使 用MHR,webpack就只会对我们改动的模块进行打包编译,从而减少打包编译的时间
注:css、html默认是开启的,而js需要通过module.hot.accept去监测。在vue项目中,vue-loader已 经实现了这一功能,无需通过module.hot.accept去监测
devServer: {
hot: true,
},
oneOf:如果匹配到一个能满足规则的, 则终止循环, 不在继续查找
开发时,我们需要配置多个loader的规则,一个文件需要由上而下的去匹配规则,就算是已经匹配到 了其中一个,匹配还是不会停止,直至匹配完成,浪费时间。oneOf是指当一个文件在由上而下匹配的 时候,一但匹配就会停止不会继续匹配这个文件。
module: {
rules: [
{
oneOf:[
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
oneOf: [
{
loader: 'url-loader',
},
{
loader: 'file-loader',
},
],
},
{
test: /\.css$/,
oneOf: [
{
loader: 'url-loader',
},
{
loader: 'file-loader',
},
],
},
]
}
],
},
include/Exclude:包含和排除
开发时我们需要使用第三方的库或者插件,但这些库或插件是不需要编译打包就可以直接使用的,所以我们在js进行babel处理的时候需要将这些库或插件排除在外,来减少打包的时间。
module:{
rules:[
{
test: /\.m?jsx?$/,
exclude:/node_module/, // 排除
use: [
'cache-loader',
'thread-loader',
'babel-loader'
],
},
]
}
cache:对性能开销较大进行缓存
在第一次打包以后对性能开销较大的进行缓存(主要为js,一般存储在node_module/.cache文件中),第二次打包时,只需要打包缓存中没有的文件,来减少打包时间。
module:{
rules:[
{
test: /\.m?jsx?$/,
use: [
'cache-loader', // 缓存
'thread-loader',
'babel-loader'
],
},
]
}
多线程:thread-loader
当打包时间到达一定的临界点时,多线程打包才能生效,因为没开启一个线程需要消耗600ms的时间。
module:{
rules:[
{
test: /\.m?jsx?$/,
use: [
'cache-loader',
'thread-loader', // 多线程
'babel-loader'
],
},
]
}
减少代码体积
减少babel生成文件的体积:@babel/plugin-transform-runtime
Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。你可以引入 Babel runtime 作为一个独立模块,来避免重复引入。
下面的配置禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。
module:{
rules: [
// 'transform-runtime' 插件告诉 Babel
// 要引用 runtime 来代替注入。
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
]
}
treeShaking
开发时,我们定义了一些工具函数或引用了第三方函数库或组件库,如果没有特殊处理的话,webpack会打包整个库,但实际上我们只用到了很小的一部分,这样会增大打包的体积,明显是我们不需要的。treeShaking就是移除掉我们没使用上的代码(注:treeShaking依赖ESmodule,如果是common.js是没发使用的),webpack默认是开启这个功能的,无需配置
优化代码性能
code split
为什么
打包代码时会将所有js文件打包到一个文件中,体积太大,如果我们只要渲染首页,就应该只要加载首页的js文件,而不需要加载其他的文件。所以我们需要使用代码分割,生成多个js文件,渲染那一个就加载哪一个js,这样就减少了资源的加载。
是什么
分割文件,将打包的文件进行分割,生成多个js(提取公共代码)
按需加载,需要那个文件就加载那个文件
拆分思路:
业务代码
第三方库:当三方库比较多导致文件较大,可以通过三方库的大小进行拆分或CDN引入
业务代码的公共业务模块
module、chunk、bundle
总结:其实是同一份逻辑代码在不同的场景下的3个名字
module:我们直接手写的代码,还没进过编译的代码,对于webpack而言,所有文件都是module
chunk:webpack正在打包的时候
bundle:打包结束可以直接在浏览器上运行的文件,即所有输出的文件
上图可以看出,一个chunk可以由多个模块或单个模块组成,一个chunk可以输出一个bundle或多个bundle
产生Chunk的三种途径:
entry入口:
string:产生一个chunk
array:产生一个chunk
object:有多少个key就产生多少个chunk
动态导入:import()
代码分割:codeSplit(没有定义test值,只针对chunk提取公共模块,定义test将test指定的目录单独分割为一个chunk)
webpack默认配置
optimization: {
splitChunks: {
// 表示选择哪些 chunks 进行分割,可选值有:
// async:只对动态导入的chunk生效
// initial:入口
// all:入口和动态组件
chunks: "async",
// 表示新分离出的 chunk 必须大于等于 minSize,20000,约 20kb。
minSize: 20000,
// 通过确保拆分后剩余的最小 chunk 体积超过限制来避免大小为零的模块,仅在剩余单个 chunk 时生效
minRemainingSize: 0,
// 表示一个模块至少应被 minChunks 个 chunk 所包含才能分割。默认为 1。
minChunks: 1,
// 表示按需加载文件时,并行请求的最大数目。
maxAsyncRequests: 30,
// 表示加载入口文件时,并行请求的最大数目。
maxInitialRequests: 30,
// 强制执行拆分的体积阈值和其他限制(minRemainingSize,maxAsyncRequests,maxInitialRequests)将被忽略
enforceSizeThreshold: 50000,
// cacheGroups 下可以可以配置多个组,每个组根据 test 设置条件,符合 test 条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据 priority 来决定打包到哪个组中。默认将所有来自 node_modules 目录的模块打包至 vendors 组,将两个以上的 chunk 所共享的模块打包至 default 组。
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
// 一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority(优先级)的缓存组。
priority: -10,
// 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
// vuecli
optimization: {
splitChunks: {
cacheGroups: {
vendors: { //将node_modules中的三方库打包到一起
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {//将动态导入的模块中公用的模块分割
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
},
}
networkCache:hash、chunkHash、contentHash
core.js
babel的预设是有限的,一些更高级的语法是没法兼容的,比如promise、async/awite等,需要配置core.js让webpack自动的根据代码去匹配需要那些polyfill,对js进行兼容性处理,让程序能够运行在更低版本的浏览器上