入口
打包默认的入口文件:src/index.js
配置入口的属性:entry
//webpack.config.js
module.exports = {
entry: './src/index.js'
}
entry
的属性值可以是一个字符串、数组或对象
- 字符串:配置单个文件的入口
- 数组:配置多个入口文件,一起注入到同一个
chunk
文件中
其中,entry: [ './src/polyfills.js', './src/index.js' ]
polyfills.js
中可能只是简单地引入一些polyfill
,如babel-polyfill、whatwg-fetch
,需要在最前面被引入。而且,在没有配置出口的情况下,它们都会打包进dist/main.js
- 对象:可以将不同文件分隔开进行打包,在后面多页面时会详细讲解。
出口
默认出口文件:dist/main.js
,配置编译文件的输出属性为 output
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'), // 必须是绝对路径
filename: 'bundle.js', // 输出一个名为 `bundle.js` 的文件
publicPath: '/' //通常是CDN地址
}
}
-
filename
filename
指定具体名称时,只会输出一个文件(chunk
);当有多个chunk
要输出时,就需要借助模版和变量了
考虑到CDN的缓存问题,通常会为编译后的文件名加上entry: { // 多入口 app: './src/app.js', search: './src/search.js' }, output: { // 多出口 path: path.resolve(__dirname, 'dist'), filename: '[name].js' // 使用 chunk 的原始名称 } --> 输出:dist/app.js, dist/search.js
hash
filename: 'bundle.[hash].js', // 单出口 filename: '[name].[hash:5].js', // 多出口
-
publicPath
:引用资源的发布路径publicPath: 'http://www.xxx.com/' // 编译构建之后 index.html 与 CSS background-image: url(http://www.xxx.com/assets/bg.png);
清理dist目录
每次编译并不会删除旧文件,如果文件名中带有hash
,那每次打包都会生成一个新的文件;插件clean-webpack-plugin
可以在每次打包前清理输出目录中的文件。
npm i -D clean-webpack-plugin
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin(),
]
CleanWebpackPlugin
可以接受一个配置对象,如果不传,则去找output.path
,默认是dist
目录;
配置对象可以做很多事,比如不希望dll
目录每次都清理:
new CleanWebpackPlugin({
// 不清理 dll 目录下的文件
cleanOnceBeforeBuildPatterns: ['**/*', '!dll', '!dll/**']
})
更多配置项
静态文件的拷贝
有时候,我们需要使用已有的JS
文件、CSS
文件、图片等静态资源,但不需要webpack
编译,比如在index.html
中引入public
目录下的js/css/image
,编译后肯定找不到了,而手动拷贝public
目录到输出目录必然又容易忘记。
插件copy-webpack-plugin
支持将单个文件或整个目录拷贝到构建(输出)目录。
npm i copy-webpack-plugin -D
// webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
plugins: [
new CopyWebpackPlugin([
{
// 拷贝 public 目录下的所有js文件 到 dist/js 目录中
from: 'public/js/*.js',
to: path.resolve(__dirname, 'dist', 'js'),
flatten: true
},
{
// 把 /src/assets 拷贝到 /dist/assets
from: path.join(__dirname, 'src/assets'),
to: path.join(__dirname, 'dist/assets')
},
])
]
参数 flatten
设置为 true
时,只会拷贝文件,而不会把文件夹路径都拷贝上。
CopyWebpackPlugin
还可以配置忽略哪些文件:ignore
new CopyWebpackPlugin([
{
from: 'public/js/*.js',
to: path.resolve(__dirname, 'dist', 'js'),
flatten: true
}
], {
ignore: ['other.js'] // 忽略 `other.js` 文件
})
ProvidePlugin
ProvidePlugin
是webpack
的内置插件,用于设置全局变量,在项目中不需要import/require
就能使用。
new webpack.ProvidePlugin({
identifier1: 'module1',
identifier2: ['module2', 'property2']
});
默认查找路径是当前目录 ./**
和 node_modules
,当然也可以直接指定全路径。
像React、jquery、lodash
这样的库,可能在多个文件中使用,把它们配置为全局变量,就不需要在每个文件中手动导入了。
const webpack = require('webpack');
module.exports = {
//...
plugins: [
new webpack.ProvidePlugin({
React: 'react',
Component: ['react', 'Component'],
Vue: ['vue/dist/vue.esm.js', 'default'],
$: 'jquery', // npm i jquery -S
_map: ['lodash', 'map'] // 把lodash中的map()配置为全局方法
})
]
}
配置之后,在项目中可以随便使用$、_map
了,且在编写React
组件时,也不需要import React/Component
了。
React
和Vue
的到处方式不同,所以配置方式有所区别。vue.esm.js
中是通过export default
导出的,而React
使用的是moudle.exports
导出的。
如果项目中启用了eslint
,还需要修改其配置问家:
{
"globals": {
"React": true,
"Vue": true,
//....
}
}
如果把第三方库手动下载添加到项目目录下,比如/static/js/jquery.min.js
那么可以先给它配置别名,然后再配置为全局变量:
module.exports = {
//...
resolve: {
alias: { // 配置JS库的路径的别名
jQuery: path.resolve(__dirname, 'static/js/jquery.min.js')
}
},
plugins: [
new webpack.ProvidePlugin({
$: 'jQuery', // 优先从 alias 别名对应的路径去查找
})
]
}
提取抽离CSS
很多时候我们都会把抽离CSS
,单独打包成一个或多个文件,这样既可以提高加载速度,又有利于缓存。
插件mini-css-extract-plugin(新版)
和 extract-text-webpack-plugin(旧版)
相比:
- 异步加载、不会重复编译(性能更好)
- 更容易使用、只适用于
CSS
mini-css-extract-plugin
的安装与配置
npm i mini-css-extract-plugin -D
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
test: /\.(le|c)ss$/,
use: [MiniCssExtractPlugin.loader, //替换之前的 style-loader
'css-loader', {
loader: 'postcss-loader',
options: {
plugins: function () { // [require('autoprefixer')]
return [
require('autoprefixer')()
]
}
}
}, 'less-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css' // 将css文件放在单独目录下
// publicPath:'../',
// chunkFilename: 'css/[id].[hash].css'
})
]
}
-
src/index.js
中引入CSS
:import '../public/base.less'
-
npm run build
构建打包生成:dist/css/main.css
,默认原名称为main
注意:如果output.publicPath
配置的是 ./
这种相对路径,那么如果将css
文件放在单独目录下,记得配置MiniCssExtractPlugin
的publicPath
开发模式下,只有第一次修改CSS才会刷新页面,需要为MiniCssExtractPlugin.loader
配置参数:
const ENV = process.env.NODE_ENV
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: ENV === 'development',
reloadAll: true,
}
},
// ...
]
更多配置项 查看mini-css-extract-plugin
CSS压缩
抽离CSS
默认不会被压缩,插件optimize-css-assets-webpack-plugin
用于压缩CSS
npm i optimize-css-assets-webpack-plugin -D
// webpack.config.js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
plugins: [
new OptimizeCssAssetsPlugin()
],
OptimizeCssPlugin
还可以传入一个配置对象:
new OptimizeCssAssetsPlugin({
assetNameRegExg: /\.css$/g,
cssProcessor: require('cssnano'),
cssProcessorPluginOptions: {
preset: ['default', {discardComments: {removeAll: true}}]
},
canPrint: true
})
-
assetNameRegExg
:正则表达式,用于匹配需要优化或压缩的资源名,默认值/\.css$/g
-
cssProcessor
:用于压缩和优化CSS
的处理器,默认cssnano
-
cssProcessorPluginOptions
:cssProcessor
的插件选项,默认值{}
-
canPrint
:表示插件能够在console
中打印信息,默认值true
-
discardComments
:去除注释
按需加载JS
很多时候并不需要一次性加载所有js
文件,而应该在不同阶段去加载所需要的代码。
webpack
内置了强大的代码分割功能,可以实现按需加载--> import()
比如,在点击了某个按钮之后,才需要使用对应js
文件中的代码
dom.onclick = function() {
import('./handle').then(fn => fn.default());
}
import()
语法需要 @babel/plugin-syntax-dynamic-import
插件的支持,但预设@babel/preset-env
中已经包含了该插件,所以无需单独安装和配置。
npm run build
构建时发现,webpack
会生成一个新的chunk
文件。
webpack
遇到 import(xxxx)
时:
- 以
xxxx
为入口生成一个新的chunk
- 当代码执行到
import()
时,才会加载该chunk
文件。
热更新
当前修改js
文件会刷新整个页面,热更新会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。
热更新:即模块热替换,HMR
,主要通过以下几种方式来显著加快开发速度
- 保留在完全重新加载页面时丢失的应用程序状态;
- 只更新变化的内容,以节省宝贵的开发时间;
- 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
首先配置 devServer.hot
为true
,然后使用webpack
的内置插件HotModuleReplacementPlugin
modules.exports = {
devServer: {
hot: true, // 开启热更新
// hotOnly: true,
contentBase: './dist',
// ...
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
]
}
-
hotOnly
表示只有热更新,不会自动刷新页面; -
NamedModulesPlugin
开启HMR时使用该插件会显示模块的相对路径
虽然修改CSS会自动热更新,但修改JS代码仍然是整个页面刷新,还需要在JS
文件中告诉webpack
接收更新的模块
// 修改入口文件 src/index.js
if(module && module.hot) {
module.hot.accept()
}
多页应用打包
我们的应用不一定是一个单页应用,而是一个多页应用,那么webpack
如何进行打包呢。
插件html-webpack-plugin
可以配置多个,每一个HtmlWebpackPlugin
对应一个页面,其filename
属性控制打包后的页面名称,默认值是index.html
,所以对于多页应用,一定不能省略filename
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
login: './src/login.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:6].js'
},
//...
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html' // 打包后的文件名
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html'
}),
]
}
查看打包后的index.html
和login.html
发现,entry
中配置的入口文件会在每个页面中都引入一次。而我们想要的是index.html
只引入./src/index.js
,login.html
只引入./src/login.js
HtmlWebpackPlugin
提供了一个chunks
属性,接受一个数组,用于指定将哪些入口js
文件引入到页面中。
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
chunks: ['index'] // 数组元素为 entry中的键名
}),
new HtmlWebpackPlugin({
template: './public/login.html',
filename: 'login.html',
chunks: ['login']
}),
]
还有一个和chunks
相对立的属性excludeChunks
,指定哪些文件不想引入到页面中。
resolve
resolve
配置 webpack
如何查找模块所对应的文件。
webpack
内置JavaScript
模块化语法解析功能,默认会采用模块化标准里约定好的规则去查找,但也可以根据自己的需要修改默认规则。
-
modules
resolve.modules
配置webpack
去哪些目录下查找第三方模块,默认只会去node_modules
下查找。
如果项目中某个目录下的模块经常被导入,但又不希望写很长的路径,那么就可以通过配置modules
来简化。
配置之后,//webpack.config.js module.exports = { //.... resolve: { modules: ['./src/components', 'node_modules'] //从左到右依次查找 } }
import Dialog from 'dialog'
就会先去./src/components
下查找了。- 使用绝对路径指明第三方模块存放的位置,可以减少搜索的步骤
function resolve(dir) { // 返回根路径 return path.join(__dirname, '../', dir) } module.exports = { resolve: { modules: [resolve('src'), resolve('node_modules')] } }
- 作为优化的方向,其实并不推荐,可能会出现问题,例如你的依赖中还存在
node_modules
目录,那么就会出现,对应的文件明明在,但是却提示找不到。
- 使用绝对路径指明第三方模块存放的位置,可以减少搜索的步骤
-
alias
resolve.alias
通过别名把原导入路径映射成一个新的导入路径。- 使用完整的文件路径,可以减少耗时的递归查找;
resolve: { alias: { 'react': resolve('./node_modules/react/dist/react.min.js'), 'assets': resolve('./public/assets') } }
- 对应的导入就可以使用别名
import 'react' import 'assets/index.css'
- 使用完整的文件路径,可以减少耗时的递归查找;
-
extensions
在导入语句时,如果没带后缀名,webpack
会自动带上不同的后缀去尝试查找;
resolve.extensions
用于配置查找文件的范围和顺序,默认是['.js', '.json']
,例如希望先找.web.js
,如果没有,再找.js
使用resolve: { extensions: ['.web.js', '.js'] // 当然,还可以配置 .json, .css }
import dialog from '../dialog';
时,因为没有带明确的后缀,webpack
会自动带上extensions
中配置的后缀,首先查找../dialog.web.js
,如果不存在,再查找../dialog.js
。
这在适配多端的代码中非常有用,否则就需要根据不同的平台引入不同的文件,牺牲了速度。
同时,应该将高频的后缀放在前面,并且数组不要太长,减少尝试访问的次数。 -
enforceExtension
配置resolve.enforceExtension
为 true,那么导入语句不能缺省文件后缀。 -
mainFields
有一些第三方模块会提供多份代码,如bootstrap
,查看其package.json
{ "style": "dist/css/bootstrap.css", "sass": "scss/bootstrap.scss", "main": "dist/js/bootstrap", }
resolve.mainFields
默认配置为['browser', 'main']
,即首先查找对应依赖的package.json
中的browser
字段,如果没有,则查找main
字段。
import 'bootstrap'
默认找的是main: "dist/js/bootstrap"
,如果希望默认去找css
文件的话,则配置resolve.mainFields
resolve: { mainFields: ['style', 'main'] }