webpack 本质上是一个打包工具,它会根据代码的内容解析模块依赖,帮助我们把多个模块的代码打包。
开始接触
wbpack是在vue-cli脚手架中,用于打包、压缩以及一些各个环境的配置,当初花了些时间来研究它,发现里面的可配置项还是很多的。最近面试中也有问到过关于webpack的配置,今天重新把笔记中的记录结合官网最新的API做了一个整理,以备后续查阅!
webpack中包含以下几个主要部分:
entry入口:
用来指定一个入口起点(或多个入口起点),进入入口起点后,
webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。
module.exports = {
entry: './src/index.js'
}
output输出:
output属性告诉webpack在哪里输出它所创建的bundles,以及如何命名这些文件,默认值为./dist。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。你可以通过在配置中指定一个output字段,来配置这些处理过程。
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
loader解析器:
让
webpack能够去处理那些非JavaScript文件(webpack自身只理解JavaScript),将所有类型的文件转换为webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对它们进行处理。
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader'
]
},
{
test:/\.(png|svg|jpg|gif)$/,
use:[
'file-loader'
]
},
{
test:/\.(woff|woff2|eot|ttf|otf)/,
use:[
'file-loader'
]
},
{
test:/\.(csv|tsv)$/,
use:[
'csv-loader'
]
},
{
test:/\.xml$/,
use:[
'xml-loader'
]
}
]
}
};
plugins插件:
plugins并不是直接操作单个文件,它直接对整个构建过程起作用下面列举了一些我们常用的plugins和他的用插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务,例如开启gzip压缩,开发环境去掉警告,debugger,console注释等。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js'
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
// 插件管理,需先进行引入,再使用
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Output Management'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
};
一、非项目环境下的 webpack 的配置以及使用:
此处省略了官网的一步步下载
loader包并配置的过程,如需看详细的版本可以移步到官网详细查阅。 当前版本webpack@4.40.2webpack-cli@3.3.9
关于webpack的下载和初始化:
<!--初始化package.json-->
npm init -y
<!--下载webpack-->
npm install webpack webpack-cli --save-dev
初始化后的目录如下:
每个文件里的具体代码如下:
package.json:
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// 此处配置的build ,可在后期用 npm run build 直接运行压缩打包
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^3.2.0",
"csv-loader": "^3.0.2",
"file-loader": "^4.2.0",
"style-loader": "^1.0.0",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"xml-loader": "^1.2.1"
},
"dependencies": {
"lodash": "^4.17.15"
}
}
index.html前台页面:
<!doctype html>
<html>
<head>
<title>起步</title>
</head>
<body>
<!--这个bundle.js就是我们打包后的js压缩文件,配置在webpack.config.js中-->
<script src="bundle.js"></script>
</body>
</html>
index.js:
import _ from 'lodash';
import './style.css';
import Img from './img.jpg';
import Data from './data.xml'
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack!'], ' ');
return element;
}
document.body.appendChild(component());
webpack.config.js配置页面:
const path = require('path');
module.exports = {
<!--这里是入口-->
entry: './src/index.js',
<!--这里是压缩后的导出-->
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
<!--这里是我们下载的loader,不同格式的文件处理-->
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader'
]
},
{
test:/\.(png|svg|jpg|gif)$/,
use:[
'file-loader'
]
},
{
test:/\.(woff|woff2|eot|ttf|otf)/,
use:[
'file-loader'
]
},
{
test:/\.(csv|tsv)$/,
use:[
'csv-loader'
]
},
{
test:/\.xml$/,
use:[
'xml-loader'
]
}
]
}
};
接下来你需要下载package 里面的依赖:
cnpm install
执行压缩:
npm run build
然后打开index.html 可以看到如下页面:
好了到这里我们可以正常显示页面了,那接下来可以再添加一些其他文件,css,img,data来压缩试试:
style.css:
.hello{
color:red;
font-family: 'MyFont';
background:url('./img.jpg');
}
index.js
import _ from 'lodash';
import './style.css';
import Img from './img.jpg';
import Data from './data.xml'
function component() {
var element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
// 添加css
element.classList.add('hello');
// 将图像添加到我们现有到div
var myImg =new Image();
myImg.src = Img;
element.appendChild(myImg)
// 添加数据
console.log(Data)
return element;
}
document.body.appendChild(component());
然后再执行压缩,查看结果:
压缩后的文件:
页面展示:
到目前为止,我们实现了将打包都压缩到一个文件中,但是如果是大型项目中的话,这样是不可取的,接下来我们配置分别将不同文件压缩并输出不同的文件。
plugins插件之: HtmlWebpackPlugin( 配置多个文件分别打包 )
新建一个
print.js文件在src中
npm install --save-dev html-webpack-plugin
webpack.config.js配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入
module.exports = {
// 配置多入口
entry: {
app:'./src/index.js',
print:'./src/print.js'
},
//分别打包成不同名称的js文件
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
// 配置plugins模块
plugins:[
new HtmlWebpackPlugin({
title:'Output Management'
})
]
};
执行打包,然后打开
index.html,你就会看到HtmlWebpackPlugin创建了一个全新的文件,所有的bundle会自动添加到html中。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Output Management</title>
</head>
<body>
<!--我当前只打包了这两个文件,plugins就帮我都引入到了index.html-->
<script type="text/javascript" src="app.bundle.js"></script>
<script type="text/javascript" src="print.bundle.js"></script>
</body>
</html>
打包到dist 文件下
plugins插件之:clean-webpack-plugin( 每次打包清理dist文件夹 )
通常,在每次构建前清理
/dist文件夹,也是比较推荐的做法。
npm install clean-webpack-plugin --save-dev
webpack.config.js配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 此处有坑,在webpack的中文文档中这里的引入方式还是原来的方式
// const CleanWebpackPlugin = require('clean-webpack-plugin');
// 以下是正确的引入方式,小伙伴们请注意哦!
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Output Management'
})
]
};
执行后看到,我们到
dist文件中除了index.html和我们打包到两个文件,就没有了其他冗余的文件了。
plugins插件之:source-map ( 文件映射 )
当
webpack打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到bundle.js。这并通常没有太多帮助,因为你可能需要准确地知道错误来自于哪个源文件。source map就提供了这个功能。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
// 配置 source-map
devtool: 'inline-source-map',
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Output Management'
})
]
};
src/print.js中手动添加一条错误
export default function printMe() {
cosnole.error('I get called from print.js!');
}
打开index页面定位错误信息:
webpack-dev-server 热加载
现在我们每次修改完内容,还需要用
npm run build执行才会进行打包,这用起来很麻烦,那我们有没有方法,改变后直接重新打包并刷新页面呢?有的!
npm install --save-dev webpack-dev-server
在package.json中的script中配置脚本命令,用于开启热加载:
"start": "webpack-dev-server --open",
webpack.config.js配置:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: {
app: './src/index.js'
},
devtool: 'inline-source-map',
// 添加热加载
devServer: {
contentBase: './dist',
hot: true
},
// 配置css loader,用来后面测试热加载使用
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Output Management'
}),
//启动 HMR
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
};
index.js:
import _ from 'lodash';
import printMe from './print.js';
// 添加一个css样式,并引入进来
import './style.css'
function component() {
var element = document.createElement('div');
var btn = document.createElement('button');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
document.body.appendChild(component());
// 添加热更新,以便当 print.js 内部发生变更时可以告诉 webpack 接受更新的模块
if (module.hot) {
module.hot.accept('./print.js', function () {
console.log('Accepting the updated printMe module!');
printMe();
})
}
执行热加载命令:
npm start开启热加载,然后手动更改style.css中的样式,页面会自动更新,此时就已经完成了webpack的最基础的配置。
二、项目中的webpack配置
上面我们介绍了非项目中的
webpack配置,这一讲我们来试着在项目中配置一下webpack,看看有哪些注意事项。
首先如果是在Vue脚手架的项目中,我们搭建完成后即可生成以下目录,如果是react项目,自己生成也是一样的:
项目中的webpack版本为: "^3.6.0" 5.52.0
分别有如下三个文件:
1. webpack.base.conf.js
webpack的基础文件,里面用于配置一些关于webpack的基础属性,包含开发环境和生产环境的共同配置。
2. webpack.dev.conf.js
webpack关于开发环境下的配置文件。
3. webpack.prod.conf.js
webpack关于生产环境下的配置文件。
接下来分享些我项目中的基础配置:
配置babel-polyfill
entry: {
// 来解析es6的特性
app: ['babel-polyfill', './src/main.js']
}
配置resolve
resolve: {
// 用于设置自动添加后缀的顺序
extensions: ['.js', '.vue', '.json'],
// alias可以映射文件也可以映射路径,用于模块别名,方便后续直接引用别名,例如:@ 表示默认src层
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
// 避免新增默认文件,编码时使用详细的文件路径,代码会更容易解读,也有益于提高构建速度
mainFiles: ['index']
}
配置loader 之 ExtractTextPlugin( 抽离css样式 )
ExtractTextPlugin该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象,会单独打一个css的包出来。这样带来的好处还有,分离出来的css和js是可以并行下载的,这样可以更快地加载样式和脚本。
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['vue-style-loade','css-loader','less-loader', 'postcss-loader'],
}),
exclude: /node_modules/,
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?modules', 'less-loader', 'postcss-loader'],
}),
}
plugins: [
new ExtractTextPlugin("styles.css")
]
配置loader 之减少loader 编译范围 exclude 、include
include 表示哪些目录中的 .js 文件需要 loader 进行处理
exclude 表示哪些目录中的 .js 文件不要 loader 进行处理
我们在使用 loader 的时候,尽可能把 loader 应用的文件范围缩小,只在最少数必须的代码模块中去使用必要的 loader,例如 node_modules 目录下的其他依赖类库文件,基本就是直接编译好可用的代码,无须再经过 loader 处理了,exclude 的优先级要高于include ,当配置相同内容时,exclude 会覆盖include 里的配置,所以在配置时要明确是否存在交集:
exclude:
{
test: /\.(js|vue|jsx)$/,
loader: 'eslint-loader',
exclude: /node_modules/,
enforce: 'pre',
options: {
fix: true
}
}
include:
{
test: /\.js$/,
use: [{
loader: 'babel-loader'
},
{
loader: 'iview-loader',
options: {
prefix: false
}
}
],
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client'), resolve('node_modules/iview/src')]
下面是2019年的vue 项目中的关于webpack的配置:
webpack.base.conf.js基础配置文件:
'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
// 配置babel-polyfill 来解析es6的特性
app: ['babel-polyfill', './src/main.js']
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
// 其他解决方案
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
mainFiles: ['index']
},
module: {
rules: [
{
test: /\.vue$/,
use: [{
loader: 'vue-loader',
options: vueLoaderConfig
},
{
loader: 'iview-loader',
options: {
prefix: false
}
}]
},
{
test: /\.js$/,
use: [{
loader: 'babel-loader'
},
{
loader: 'iview-loader',
options: {
prefix: false
}
}
],
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client'), resolve('node_modules/iview/src')]
},
// ExtractTextPlugin 用于将css文件从js文件中拆出来,单独打成一个包,
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['vue-style-loade','css-loader','less-loader', 'postcss-loader'],
}),
exclude: /node_modules/,
},
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader?modules', 'less-loader', 'postcss-loader'],
}),
},
// 把小于limit阀值大小的图片转为base64字符串
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /\.(js|vue|jsx)$/,
loader: 'eslint-loader',
exclude: /node_modules/,
enforce: 'pre',
options: {
fix: true
}
}
]
},
plugins: [
/**
* 将样式提取到单独的css文件,而不是内嵌到打包的js文件中。
* 这样带来的好处时分离出来的css和js是可以并行下载的,
* 这样可以更快地加载样式和脚本。
*/
new ExtractTextPlugin("styles.css"),
// 清空输出文件夹之前的输出文件
new CleanWebpackPlugin(),
],
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
webpack.dev.conf.js开发环境配置文件:
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const path = require('path')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
const portfinder = require('portfinder')
const OpenBrowserPlugin = require('open-browser-webpack-plugin')
const HOST = process.env.HOST
const PORT = process.env.PORT && Number(process.env.PORT)
// 将基本配置合并到开发环境
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// cheap-module-eval-source-map is faster for development
devtool: config.dev.devtool,
// these devServer options should be customized in /config/index.js
devServer: {
clientLogLevel: 'warning',
historyApiFallback: {
rewrites: [
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
],
},
hot: true,
contentBase: false, // since we use CopyWebpackPlugin.
compress: true,
host: HOST || config.dev.host,
port: PORT || config.dev.port,
open: config.dev.autoOpenBrowser,
overlay: config.dev.errorOverlay
? { warnings: false, errors: true }
: false,
publicPath: config.dev.assetsPublicPath,
proxy: config.dev.proxyTable,
quiet: true, // necessary for FriendlyErrorsPlugin
watchOptions: {
poll: config.dev.poll,
}
},
plugins: [
// vue run dev后自动打开浏览器插件
new OpenBrowserPlugin({url: 'http://localhost:8080' }),
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
// 模块热替换,无需整个刷新页面
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
// 拷贝插件
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
]),
// 去除依赖中重复的插件
new webpack.optimize.DedupePlugin() ,
/**
* 为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,
* 并为它们分配最小的ID
*/
new webpack.optimize.OccurrenceOrderPlugin()
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// publish the new Port, necessary for e2e tests
process.env.PORT = port
// add port to devServer config
devWebpackConfig.devServer.port = port
// Add FriendlyErrorsPlugin
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
resolve(devWebpackConfig)
}
})
})
webpack.prod.conf.js生产环境的配置文件:
'use strict'
const version = require('../package.json').version || '';
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const zip = require('zip-webpack-plugin')
const env = require('../config/prod.env')
// 将基本配置合并到生产环境
const webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true,
usePostCSS: true
})
},
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// 对js文件进行压缩
new UglifyJsPlugin({
extractComments: {
// 根据条件保存需要保留的注释
condition: function (a, data) {
return /app.\w+.js$/.test(data.file);
},
//存储提取的注释的文件名调用
banner: 'version:' + version,
},
// 部分格式化,根据需要配置
uglifyOptions: {
compress: {
warnings: false, // 自动删除警告
drop_console: true, // 自动删除console.log
typeofs: false, //将typeof foo==“未定义”转换为foo==void 0。注意:对于IE10和早期版本有问题。
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
// extract css into its own file
// 将css 提取到自己的文件中
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
// Setting the following option to `false` will not extract CSS from codesplit chunks.
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
allChunks: true,
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// keep module.id stable when vendor modules does not change
new webpack.HashedModuleIdsPlugin(),
// enable scope hoisting
new webpack.optimize.ModuleConcatenationPlugin(),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks(module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
// CommonsChunkPlugin:提取通用模块文件,vue vuex vue-router axios
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// 拷贝插件
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
]),
// 打成zip包,一般用于上线
new zip({
path: './',
filename: `frame-fe.zip`
}),
/**
* 将原来的 chunk 分成更小的 chunk
* 直到各个 chunk 的大小达到 option 设置的 maxSize
*/
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 30000,
maxSize: 50000
}),
]
})
if (config.build.productionGzip) {
const CompressionWebpackPlugin = require('compression-webpack-plugin')
// 开启gzip压缩 gzip能在原本压缩的基础上再进行压缩50%以上!!!
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
三、关于 webpack 的一些周边问题 (更新日期:2022年,3月,18日)
1. loader 和 plugins 有什么区别
这个问题是在
19年面试字节时被问到的,当时对webpack的了解只是简单配置过些参数,至于loader是啥,plugins又是啥真的没有过多考究,接下来就借用上面的解释再详细分析一下。
loader
让
webpack能够去处理那些非JavaScript文件(webpack自身只理解JavaScript),将所有类型的文件转换为webpack能够处理的有效模块,然后你就可以利用webpack的打包能力,对它们进行处理。
plugins
plugins并不是直接操作单个文件,它直接对整个构建过程起作用下面列举了一些我们常用的plugins和他的用插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务,例如开启gzip压缩,开发环境去掉警告,debugger,console注释等。
区别:
-
功能不同:
loader是处理单文件的编译和转换;plugins,在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在不同的时机通过webpack提供的API改变输出结果。 -
运行时机不同:
loader运行在打包文件之前(loader为在模块加载时的预处理文件);plugins在整个编译周期都起作用。
2. loader 和 plugins 哪个先执行
这个问题是有不同场景的,大部分情况下
loader是在前,因为它需要为模块加载时执行文件的预处理,plugins在打包编译时提供各个功能。但有时的plugins是会在前面执行的,是因为plugins可以监听webpack的不同生命周期,可以在不同时期做出不同的反应。
3. 常用的plugins
-
ProvidePlugin自动加载模块,代替require和import -
AggressiveSplittingPlugin将原来的chunk分成更小的chunk -
CommonsChunkPlugin提取chunks之间共享的通用模块 -
CopyWebpackPlugin将单个文件或整个目录复制到构建目录 -
DefinePlugin允许在编译时(compile time)配置的全局常量 -
DllPlugin为了极大减少构建时间,进行分离打包 -
EnvironmentPluginDefinePlugin中process.env键的简写方式。 -
ExtractTextWebpackPlugin从bundle中提取文本(CSS)到单独的文件 -
HotModuleReplacementPlugin启用模块热替换(Enable Hot Module Replacement - HMR) -
HtmlWebpackPlugin简单创建HTML文件,用于服务器访问 -
IgnorePlugin从bundle中排除某些模块 -
optimize-css-assets-webpack-plugin不同组件中重复的css可以快速去重 -
LimitChunkCountPlugin设置chunk的最小/最大限制,以微调和控制chunk -
MinChunkSizePlugin确保chunk大小超过指定限制 -
NoEmitOnErrorsPlugin在输出阶段时,遇到编译错误跳过 -
NpmInstallWebpackPlugin在开发时自动安装缺少的依赖 -
speed-measure-webpack-plugin:可以看到每个loader和plugin执行耗时(整个打包耗时、每个plugin和loader耗时)
4. 常用的loader
-
file-loader:把⽂件输出到⼀个⽂件夹中,在代码中通过相对URL去引⽤输出的⽂件 -
url-loader:和file-loader类似,但是能在⽂件很⼩的情况下以base64的⽅式把⽂件内容注⼊到代码中去 -
source-map-loader:加载额外的Source Map⽂件,以⽅便断点调试 -
image-loader:加载并且压缩图⽚⽂件 -
babel-loader:把ES6转换成ES5 -
css-loader:加载CSS,⽀持模块化、压缩、⽂件导⼊等特性 -
style-loader:把CSS代码注⼊到JavaScript中,通过DOM操作去加载CSS。 -
eslint-loader:通过ESLint检查JavaScript代码
5. webpack 热更新原理
通过添加和浏览器的
websocket长连接来监听文件的变化,当文件发生变化后触发webpack进行编译,编译完成后将再通过socket通知浏览器准备更新,更新前通过生成文件的hash值来对比需要更新的模块,然后执行浏览器更新替换。