随着前端的发展,只会傻傻使用脚手架,然后npm run serve、npm run build显然是不够的,在面试中也常常会有面试官问道会用webpack吗?会自定义进行配置管理吗?学会webpack往往能使你在面试中脱颖而出,废话不多说,我们开始走进webpack的世界。
本文仅供参考,如果错误烦请指正,蟹蟹
项目地址:GitHub
webpack可以看成一个模块打包工具,分析目录结构,处理模块化依赖,转换成浏览器“认识”的代码。
- 代码转换:
TypeScript编译转换成JavaScript、SCSS,LESS,SASS编译转换成CSS - 文件优化:压缩
JavaScript、CSS、HTML代码,压缩合并图片 - 代码分割:提取多个页面的公共代码,提取首屏不需要执行的部分代码让其异步加载
- 模块合并:在采用模块化的项目里会有多个模块和文件,需要构建功能把模块分类合并成一个文件
- 自动刷新:使用热加载插件,监听本地代码的变化,自动重新构建代码、刷新浏览器
新建一个空文件夹,初始化npm
// npm
npm init
// yarn
yarn initwebpack是运行在node环境中的,我们需要安装一下两个包
// npm
npm i -D webpack webpack-cli
// yarn
yarn add -D webpack wbpack-cli新绛一个文件夹src,然后新建一个文件main.js,写一点代码进行测试
console.log("我在学习webpack");配置package.json命令
// 运行 yarn run build 命令时将会找到 src 目录下的 main.js 当做入口文件进行打包操作
"scripts": {
"build": "webpack src/main.js"
}执行
// npm
npm run build
// yarn
yarn run build此时如果生成了一个dist文件夹,并且内部含有main.js文件说明已经打包成功,这时候可能会出现如下警告(因为我们没有配置开发环境与生产环境,但是并不影响打包的执行):
上面的一个简单示例只是webpack默认的配置,在开发中往往我们需要自定义一些配置,新建一个build文件夹,里面新建一个webpack.config.js文件
// webpack.config.js
const path = require('path');
module.exports = {
mode:'development', // 开发模式
entry: path.resolve(__dirname,'../src/main.js'), // 入口文件
output: {
filename: 'zhizhi.js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
}
}entry:入口文件,webpack会找到该文件进行解析output:输出文件配置path:输出文件的位置(要将文件输出到哪个目录)filename:输出文件的名字
更改我们的命令为
"scripts": {
"build": "webpack --config build/webpack.config.js"
},执行
// npm
npm run build
// yarn
yarn run build此时生成了如下文件
js文件打包完成,但是我们不能每次都在相应的html模板中手动引入打包之后的js文件
有的老板对这里的可能会有疑问,我们打包好的js文件名不都是一样的吗?不是引入一次就行了吗?然而我们在开发中为了避免浏览器缓存导致页面更新不及时,往往会这样配置
// 这里的 [name] 为你的文件名称,如入口文件名为main.js [name] 就为main
// 而[hash:8] 表示在文件名之后加上8位的 hash 值,此时你每次打包之后的文件名都是不一样的
module.exports = {
// 省略其他配置
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
}
}此时生成的文件为:
因为每次生成的js文件名都不一样,而我们都需要将js文件引入到html中,这时候我们就需要一个插件来帮助我们html-webpack-plugin
安装html-webpack-plugin
// npm
npm i -D html-webpack-plugin
// yarn
yarn add -D html-webpack-plugin新建一个与build同级的文件夹public,在里面新建一个index.html,然后修改配置文件webpack.config.js
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:'development', // 开发模式
entry: path.resolve(__dirname,'../src/main.js'), // 入口文件
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
},
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html')
})
]
}此时生成的目录和效果如下
生成多个
html-webpack-plugin实例来解决
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode:'development', // 开发模式
entry: {
main:path.resolve(__dirname,'../src/main.js'),
header:path.resolve(__dirname,'../src/header.js')
},
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname,'../dist') // 打包后的目录
},
plugins:[
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html'),
filename:'index.html',
title:"main",
hash:true,
minify: {
removeAttributeQuotes: true
}
chunks:['main'] // 与入口文件对应的模块名
}),
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/header.html'),
filename:'header.html',
title:"header",
hash:true,
minify: {
removeAttributeQuotes: true
}
chunks:['header'] // 与入口文件对应的模块名
}),
]
}多个入口配置:
template:html模板的路径地址filename:生成的文件名title:传入的参数chunks:需要引入的chunkhash:在引入的JS文件中加入hash值(引入时候文件名之后加上?xxxxxxxxx)removeAttributeQuotes:去掉引号,减少文件大小
执行npm run build 或者 yarn run build此时生成的目录和文件如下
在我们每次执行打包之后会发现
dist文件夹里面会残留上次打包的文件,我们会使用clean-webpack-plugin插件来帮助我们在打包输出前清空目标文件夹
先安装clean-webpack-plugin
// npm
npm i -D clean-webpack-plugin
// yarn
yarn add -D clean-webpack-plugin修改配置文件webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
// ...省略其他配置
plugins:[new CleanWebpackPlugin()]
}我们的入口文件是js,所以我们在入口js文件中引入我们的CSS文件
src目录新建assets文件夹,里面新建index.css 和 index.less文件,然后在main.js文件中引入css文件
因为我们引入了CSS文件,所以需要一些loader来解析我们的CSS文件
// npm
npm i -D style-loader css-loader less less-loader // 如果我们使用了less来构建样式,则需要多安装两个,scss/sass同理
// yarn
yarn add -D style-loader css-loader less less-loadercss-loader:支持css中的importstyle-loader:把css以style标签的形式引入到htmlsass-loader,less-loader,scss-loader:转译为cssnode-sass:sass转译依赖,其他预处理语言同理
修改配置文件webpack.config.js
// webpack.config.js
module.exports = {
// ...省略其他配置
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'], // 从右向左解析原则
exclude: /node_modules/
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader'], // 从右向左解析原则
exclude: /node_modules/
}
]
}
} test:一个正则表达式,匹配文件名use:一个数组,里面放入需要执行的loader,从右往左解析exclude:取消匹配node_modules里面的文件
此时浏览器打开index.html可以看到CSS以style标签的形式嵌入到页面中
安装postcss-loader 和 autoprefixer
// npm
npm i -D postcss-loader autoprefixer
// yarn
yarn add -D postcss-loader autoprefixer修改配置webpack.config.js
// webpack.config.js
module.exports = {
module:{
rules:[
{
test:/\.less$/,
use:['style-loader','css-loader','postcss-loader','less-loader'] // 从右向左解析原则
}
]
}
} 修改package.json文件(不进行配置可能会添加前缀失败)
{
// 忽略其他配置
"browserslist": [
"last 1 version",
"> 1%",
"IE 10"
],
}接下来我们还需要引入autoprefixer使其生效,这里有两种方式
1.在项目跟目录创建一个postcss.config.js,配置如下
module.exports = {
plugins: [require('autoprefixer')] // 引用该插件即可了
}2.直接在webpack.config.js文件中配置
// webpack.config.js
module.exports = {
//...省略其他配置
module:{
rules:[{
test:/\.less$/,
use:['style-loader','css-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
},'less-loader'] // 从右向左解析原则
}]
}
}这时候我们已经完成了css在html文件中的引入,但是如果样式文件很多,全部添加到html中会显得混乱。而我们可以通过插件mini-css-extract-plugin来拆分css
安装mini-css-extract-plugin
// npm
npm i -D mini-css-extract-plugin
// yarn
yarn add -D mini-css-extract-plugin在webpack 4.0以前,我们通过
extract-text-webpack-plugin插件,把css样式从js文件中提取到单独的css文件中,webpack 4.0之后,官方推荐使用mini-css-extract-plugin插件来打包css文件
配置文件如下
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
//...省略其他配置
module: {
rules: [
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[hash].css",
chunkFilename: "[id].css",
})
]
}运行npm run build 或者 yarn run build打包之后文件目录如下
在这里遇到一个坑,在修改配置文件的时候没有删除style-loader,然后运行打包报错,后来想起来style-loader是将css文件通过style标签内嵌到页面中,而mini-css-extract-plugin是将css文件拆分成独立文件,两个插件冲突所以报错,将style-loader删除就好了
上面我们用到的
mini-css-extract-plugin是将所有的css样式合并为一个css文件,如果我们要拆分多个css文件,需要用到extract-text-webpack-plugin,而目前mini-css-extract-plugin还不支持此功能,我们需要安装@next版本的extract-text-webpack-plugin
安装:
// npm
npm i -D extract-text-webpack-plugin@next
// yarn
yarn add -D extract-text-webpack-plugin@next修改webpack.config.js配置
// webpack.config.js
const path = require('path');
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
let indexLess = new ExtractTextWebpackPlugin('index.less');
let indexCss = new ExtractTextWebpackPlugin('index.css');
module.exports = {
module:{
rules:[
{
test:/\.css$/,
use: indexCss.extract({
use: ['css-loader']
})
},
{
test:/\.less$/,
use: indexLess.extract({
use: ['css-loader','less-loader']
})
}
]
},
plugins:[
indexLess,
indexCss
]
}打包之后的目录如图所示
file-loader就是将文件在进行一些处理之后(主要是处理文件名和路径、解析文件url),并将文件移动到输出的目录中
url-loader一般会与file-loader搭配使用,功能与file-loader类似,如果文件小于限制的大小,则会返回文件的base64编码,否则使用file-loader将文件移动到输出目录中
// webpack.config.js
module.exports = {
// 省略其它配置 ...
module: {
rules: [
// ...
{
test: /\.(jpe?g|png|gif)$/i, //图片文件
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
]
}
}为了使我们的js代码兼容更多的浏览器环境,我们需要将ES6和更高版本的语法进行转义
安装
// npm
npm i -D babel-loader @babel/preset-env @babel/core
// yarn
yarn add -D babel-loader @babel/preset-env @babel/core- 注意
babel-loader与babel-core的版本对应关系babel-loader8.x对应babel-core7.xbabel-loader7.x对应babel-core6.x
修改webpack.config.js配置
// webpack.config.js
module.exports = {
// 省略其它配置 ...
module:{
rules:[
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
},
exclude:/node_modules/
},
]
}
}举个栗子:
修改配置后我们在main.js中写入
运行打包之后找到打包后的main.xxxxxxxx.js文件,打开之后找到我们的console
不难发现,上面的babel-loader只会将ES6/7/8语法转为ES5语法,但是对新的api并不会转换,例如:Promise, Generator, Set, Maps, Proxy等
此时我们需要借助babel-polyfill来进行转换
安装
// npm
npm i -D @babel/polyfill
// yarn
yarn add -D @babel/polyfill修改webpack.config.js配置
// webpack.config.js
const path = require('path')
module.exports = {
entry: ["@babel/polyfill",path.resolve(__dirname,'../src/index.js')], // 入口文件
}
// 多个入口文件时配置如下
entry: {
main:["@babel/polyfill",path.resolve(__dirname,'../src/main.js')],
header:["@babel/polyfill",path.resolve(__dirname,'../src/header.js')],
},Babel的配置建议在根目录下新建一个.babelrc文件
{
"presets": [
"env",
"stage-0",
"react"
],
"plugins": [
"transform-runtime",
"transform-decorators-legacy",
"add-module-exports"
]
}presets:预设,一个预设包含多个插件,起到方便作用,不用引用多个文件env:只转换新的语法,例如const,let,()=>等,不转换如Promise,Proxy,Generator,Set,Maps,Symbol等stage-0:es7提案转码规则,有0 1 2 3 几个阶段,阶段0包含了所有的react:转换react的jsx语法plugins:插件,可以自己开发插件,转换代码(依赖于AST抽象语法树)transform-runtime:转换新语法,自动引入polyfill插件,另外可以避免污染全局变量transform-decorators-legacy:支持装饰器add-module-exprots:转译export default{};添加上module.exports = exports.default支持commonjs
到现在为止我们对
webpack有了个初步了解,但是想要熟练运用,我们需要一个系统的实战,开始摆脱脚手架尝试搭建自己的Vue项目吧
上面的栗子🌰已经帮我们实现了打包css、图片、js、HTML等文件,但是我们还需要一下几种配置
安装
// npm
npm i -D vue-loader vue-template-compiler vue-style-loader
npm i -S vue
// yarn
yarn add -D vue-loader vue-template-compiler vue-style-loader
yarn add vuevue-loader用于解析.vue文件
vue-template-compiler用于编辑template模板
修改webpack.config.js配置
const vueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
module:{
rules:[{
test:/\.vue$/,
use:['vue-loader']
},]
},
resolve:{
alias:{
'vue$':'vue/dist/vue.runtime.esm.js',
' @':path.resolve(__dirname,'../src')
},
extensions:['*','.js','.json','.vue']
},
plugins:[
new vueLoaderPlugin()
]
}安装
//npm
npm i -D webpack-dev-server
// yarn
yarn add -D webpack-dev-server修改webpack.config.js配置
const Webpack = require('webpack')
module.exports = {
// ...省略其他配置
devServer:{
port:3000,
hot:true,
contentBase:'../dist'
},
plugins:[
new Webpack.HotModuleReplacementPlugin()
]
}完整配置如下
// vue环境
const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
let indexLess = new ExtractTextWebpackPlugin('index.less');
let indexCss = new ExtractTextWebpackPlugin('index.css');
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const Webpack = require('webpack')
module.exports = {
mode: 'development', // 开发模式
entry: {
main: ["@babel/polyfill", path.resolve(__dirname, '../src/main.js')],
header: ["@babel/polyfill", path.resolve(__dirname, '../src/header.js')],
},
output: {
filename: '[name].[hash:8].js', // 打包后的文件名称
path: path.resolve(__dirname, '../dist') // 打包后的目录
},
plugins: [
new vueLoaderPlugin(),
new CleanWebpackPlugin(),
new Webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html'),
filename: 'index.html',
chunks: ['main'] // 与入口文件对应的模块名
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/header.html'),
filename: 'header.html',
chunks: ['header'] // 与入口文件对应的模块名
}),
],
resolve: {
alias: {
'vue$': 'vue/dist/vue.runtime.esm.js',
' @': path.resolve(__dirname, '../src')
},
extensions: ['*', '.js', '.json', '.vue']
},
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
},
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}]
},
{
test: /\.less$/,
use: ['vue-style-loader', 'css-loader', {
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}, 'less-loader']
}
]
},
devServer:{
port:3000,
hot:true,
contentBase:'../dist'
},
}打包文件配置完毕,接下来我们测试一下,在src文件夹里新建vueMain.js
在src文件夹里新建一个App.vue文件
在public文件夹下新建index.html
此时运行npm run dev 或者 yarn run dev 它会在本地跑一个node服务,端口为3000并自动打开浏览器访问:
在实际应用中,我们需要区分开发环境和生产环境,我们在原来webpack.config.js文件的基础上再新增两个文件
-
webpack.dev.js开发环境的配置文件开发环境需要的主要是热更新,不需要压缩代码,拥有完整的
resourceMap -
webpack.prod.js生产环境配置文件生产环境需要的是代码压缩、提取
CSS文件、合理的resourceMap、分割代码需要安装一下依赖:
webpack-merge合并配置copy-webpack-plugin拷贝静态资源optimize-css-assets-webpack-plugincss压缩uglifyjs-webpack-pluginjs压缩
webpack mode设置production的时候回自动压缩js代码。原则上不需要引入uglifyjs-webpack-plugin进行重复工作,但是optimize-css-assets-webpack-plugin压缩css的同时会破坏原有的js压缩,所以这里我们需要引入uglifyjs进行压缩
const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const vueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const devMode = process.argv.indexOf('--mode=production') === -1;
module.exports = {
entry:{
main:path.resolve(__dirname,'../src/main.js')
},
output:{
path:path.resolve(__dirname,'../dist'),
filename:'js/[name].[hash:8].js',
chunkFilename:'js/[name].[hash:8].js'
},
module:{
rules:[
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
},
exclude:/node_modules/
},
{
test:/\.vue$/,
use:[{
loader:'vue-loader',
options:{
compilerOptions:{
preserveWhitespace:false
}
}
}]
},
{
test:/\.css$/,
use:[{
loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
options:{
publicPath:"../dist/css/",
hmr:devMode
}
},'css-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
}]
},
{
test:/\.less$/,
use:[{
loader:devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
options:{
publicPath:"../dist/css/",
hmr:devMode
}
},'css-loader','less-loader',{
loader:'postcss-loader',
options:{
plugins:[require('autoprefixer')]
}
}]
},
{
test:/\.(jep?g|png|gif)$/,
use:{
loader:'url-loader',
options:{
limit:10240,
fallback:{
loader:'file-loader',
options:{
name:'img/[name].[hash:8].[ext]'
}
}
}
}
},
{
test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use:{
loader:'url-loader',
options:{
limit:10240,
fallback:{
loader:'file-loader',
options:{
name:'media/[name].[hash:8].[ext]'
}
}
}
}
},
{
test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use:{
loader:'url-loader',
options:{
limit:10240,
fallback:{
loader:'file-loader',
options:{
name:'media/[name].[hash:8].[ext]'
}
}
}
}
}
]
},
resolve:{
alias:{
'vue$':'vue/dist/vue.runtime.esm.js',
' @':path.resolve(__dirname,'../src')
},
extensions:['*','.js','.json','.vue']
},
plugins:[
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template:path.resolve(__dirname,'../public/index.html')
}),
new vueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css'
})
]
}const Webpack = require('webpack')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
module.exports = WebpackMerge(webpackConfig,{
mode:'development',
devtool:'cheap-module-eval-source-map',
devServer:{
port:3000,
hot:true,
contentBase:'../dist',
host: 'localhost',
overlay: true,
compress: true,
open:true,
inline: true,
progress: true
},
devtool:"inline-source-map",
plugins:[
new Webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
]
})contentBase: 静态文件地址port: 端口号host: 主机overlay: 如果出错,则在浏览器中显示出错误compress: 服务器返回浏览器的时候是否启动gzip压缩open: 打包完成自动打开浏览器hot: 模块热替换 需要webpack.HotModuleReplacementPlugin插件inline: 实时构建progress: 显示打包进度devtool: 生成代码映射,查看编译前代码,利于找bugwebpack.NamedModulesPlugin: 显示模块的相对路径
使用uglifyjs-webpack-plugin
const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = WebpackMerge(webpackConfig,{
mode:'production',
devtool:'cheap-module-source-map',
plugins:[
new CopyWebpackPlugin([{
from:path.resolve(__dirname,'../public'),
to:path.resolve(__dirname,'../dist')
}]),
],
optimization:{
minimizer:[
new UglifyJsPlugin({//压缩js
cache:true,
parallel:true,
sourceMap:true
}),
new OptimizeCssAssetsPlugin({})
],
splitChunks:{
chunks:'all',
cacheGroups:{
libs: {
name: "chunk-libs",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial" // 只打包初始时依赖的第三方
}
}
}
}
})使用webpack-parallel-uglify-plugin
const path = require('path')
const webpackConfig = require('./webpack.config.js')
const WebpackMerge = require('webpack-merge')
const WebpackParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
module.exports = WebpackMerge(webpackConfig,{
mode:'production',
devtool:'cheap-module-source-map',
plugins:[
new WebpackParallelUglifyPlugin(
{
uglifyJS: {
mangle: false,
output: {
beautify: false,
comments: false
},
compress: {
warnings: false,
drop_console: true,
collapse_vars: true,
reduce_vars: true
}
}
}
),
],
})uglifyJS配置:
mangle: 是否混淆代码output.beautify: 代码压缩成一行 true为不压缩 false压缩output.comments: 去掉注释compress.warnings: 在删除没用到代码时 不输出警告compress.drop_console: 删除consolecompress.collapse_vars: 把定义一次的变量,直接使用,取消定义变量compress.reduce_vars: 合并多次用到的值,定义成变量- 具体文档
webpack的优化,关系到打包出来文件的大小,打包的速度等,我们从以下几个方面对webpack进行优化:
构建速度指的是我们每次修改代码之后热更新的速度和发布前打包文件的速度
mode 可设置development(开发)和production(生产)两个参数
如果没有设置,webpack4会将mode的默认值设置为production
production模式下会进行tree shaking(去除无用代码)和uglifyjs(代码压缩混淆)
-
alias:当我们代码中出现import vue时,webpack会采用向上递归的方式去node_modules目录下去找。为了减少搜索范围我们可以直接告诉webpack去哪个路径下查找,也就是alias(别名) -
include exclude: 同样配置include exclude也可以减少webpack loader的搜索转换时间 -
noParse:当我们代码中用到import jq from 'jquery'时,webpack回去解析jq这个库是否有依赖的包。但是我们对类似jquery这类依赖库,一般会认为不会引用其他的包(特殊的除外),增加npParse属性告诉webpack不必解析,一次增加打包速度 -
extensions:webpack会根据extensions定义的后缀查找文件(频率高的文件类型优先写在前面)module.exports = { // 忽略其他配置 module:{ noParse:/jquery/, // 不去解析jquery中的依赖库 rules:[ { test:/\.js$/, use:{ loader:'babel-loader', options:{ presets:['@babel/preset-env'] } }, exclude:/node_modules/ }, { test:/\.vue$/, use:[{ loader:'vue-loader', options:{ compilerOptions:{ preserveWhitespace:false } } }] }, { test:/\.css$/, use:[{ loader: devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader, options:{ publicPath:"../dist/css/", hmr:devMode } },'css-loader',{ loader:'postcss-loader', options:{ plugins:[require('autoprefixer')] } }] }, { test:/\.less$/, use:[{ loader:devMode ? 'vue-style-loader' : MiniCssExtractPlugin.loader, options:{ publicPath:"../dist/css/", hmr:devMode } },'css-loader','less-loader',{ loader:'postcss-loader', options:{ plugins:[require('autoprefixer')] } }] }, { test:/\.(jep?g|png|gif)$/, use:{ loader:'url-loader', options:{ limit:10240, fallback:{ loader:'file-loader', options:{ name:'img/[name].[hash:8].[ext]' } } } } }, { test:/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, use:{ loader:'url-loader', options:{ limit:10240, fallback:{ loader:'file-loader', options:{ name:'media/[name].[hash:8].[ext]' } } } } }, { test:/\.(woff2?|eot|ttf|otf)(\?.*)?$/i, use:{ loader:'url-loader', options:{ limit:10240, fallback:{ loader:'file-loader', options:{ name:'media/[name].[hash:8].[ext]' } } } } } ] }, resolve:{ alias:{ 'vue$':'vue/dist/vue.runtime.esm.js', ' @':path.resolve(__dirname,'../src') }, extensions:['*','.js','.json','.vue'] }, }
在
webpack构建过程中,实际上耗费时间大多数用在loader解析转换以及代码的压缩中。日常开发中我们需要使用loader对js,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的。由于js单线程的特性使得这些转换操作不能并发处理,而是需要一个个文件进行处理。HappyPack的基本原理是将这部分任务分解到多个子进程中并行处理,子进程处理完成之后把结果发送到主进程中,从而减少构建时间
安装
// npm
npm i -D happypack
// yarn
yarn add -D happypack修改webpack.config.js配置
const HappyPack = require("happypack")
const os = require("os")
const happyThreadPool = HappyPack.ThreadPool({size:os.cpus().length})
module.exports = {
// 忽略其他配置
module:{
rules:[
{
test:/\.js$/.
use:[{
loader:"happypack/loader?id=happyBabel"
}],
exclude:/node_modules/
},
],
plugins:[
new HappyPack({
id:"happyBabel",
loaders:[
{
loader:"babel-loader",
options:{
presets:[
["@babel/preset-env"]
],
cacheDirectory: true
}
}
],
threadPool:happyThreadPool // 共享进程池
})
]
}
}上面已经对
loader转换进行了优化,还有一个难点就是优化代码的压缩时间
安装
// npm
npm i -D webpack-parallel-uglify-plugin
// yarn
yarn add -D webpack-parallel-uglify-plugin修改webpack.config.js配置
const HappyPack = require("happypack")
const os = require("os")
const happyThreadPool = HappyPack.ThreadPool({size:os.cpus().length})
module.exports = {
// 忽略其他配置
optimization:{
minimizer:[
new ParallelUglifyPlugin({
cacheDir:"./cache",
uglifyJS:{
output:{
comments:false,
beautify:false
},
compress:{
drop_console:true,
collapse_vars:true,
reduce_vars:true
}
}
})
]
}
}对于开发项目中不经常会变更的静态依赖文件,因为很少会去变更,所以我们不希望这些依赖要被集成到内一次的构建逻辑中去。以后只要我们不升级第三方包的时候,那么
webpack就不会对这些库去打包,这样可以快速的提高打包的速度。
这里我们使用webpack内置的DllPlugin DllReferencePlugin进行抽离
在与webpack配置文件同级目录下新建webpack.dll.config.js 代码如下:
// webpack.dll.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
// 你想要打包的模块的数组
entry: {
vendor: ['vue','element-ui']
},
output: {
path: path.resolve(__dirname, 'static/js'), // 打包后文件输出的位置
filename: '[name].dll.js',
library: '[name]_library'
// 这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, '[name]-manifest.json'),
name: '[name]_library',
context: __dirname
})
]
};在package.json中配置如下命令
"dll": "webpack --config build/webpack.dll.config.js"接下来我们在webpack.config.js中增加配置
module.exports = {
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./vendor-manifest.json')
}),
new CopyWebpackPlugin([ // 拷贝生成的文件到dist目录 这样每次不必手动去cv
{from: 'static', to:'static'}
]),
]
};执行npm run dll 或者 yarn run dll
之后会发现生成了我们需要的集合第三方代码的vendor.dll.js 我们需要在html文件中手动引入这个js文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="static/js/vendor.dll.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>这样如果我们没有更新第三方依赖包,就不必执行npm run dll 或者 yarn run dll,直接执行run dev 和 run build的时候就会发现我们的打包速度明显提升,因为我们已经通过dllPlugin将第三方依赖包进行了抽离
















