webpack默认配置文件
webpack默认配置文件:webpack.config.js,但也可以通过webpack --config webpack.product.config.js指定配置文件。
webpack的配置组成:
- entry: 用来指定webpack打包的入口文件
- output: 打包的输出,输出到磁盘的xx位置和输出的文件名称
- mode: 指定当前的打包环境
- module: Loader的配置
- plugin: 插件配置
module.exports = {
entry: './scr/index.js',
output: './dist/main.js',
mode: 'production',
module: {
rules: [
{test: /\.txt$/, use: 'raw-loader'}
]
},
plugins: [
new HtmlwebpackPlugin({
template: './src/index.html'
})
]
}
webpack运行
- ./node_modules/.bin/webpack
- 在package.json scripts中增加build命令,然后执行npm run build
{
"name": "webpack-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
}
}
webpack的核心概念
entry、output、loaders、plugins、mode
entry
entry用来指定webpack的打包入口 webpack依赖的入口是entry,对于非代码比如图片、字体依赖会不断的加入到依赖图中
entry的用法
单入口:entry是一个字符串,适用于单页应用
module.exports = {
entry: './path/to/my/entry/file.js'
}
多入口:entry是一个对象,适用于多页应用
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js'
}
}
output
output用来告诉webpack如何将编译后的文件输出到磁盘(输出到磁盘的xx位置,以及输出的文件名称)。
output用法
单入口配置:
const path = require('path');
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'boundle.js',
}
};
多入口配置:output的输出位置只有一个,但是entry是多个的情况下,output需要使用占位符
const path = require('path');
module.exports = {
entry: {
app: './src/app.js',
search: './src/search.js',
detail: './src/detail.js'
},
output: {
filename: '[name].js', // 通过占位符确保文件名称的唯一
path: path.join(__dirname, 'dist')
}
};
loaders
loaders用来处理webpack不能解析的文件,使这些模块能够被webpack解析
常见的loaders
- babel-loader:转换es6、es7等js新特性的语法
- css-loader:支持css文件的加载和解析
- less-loader:将less文件转换为css
- sass-loader:将sass文件转换为css
- ts-loader:将TS转换成JS
- file-loader:进行图片、字体等的打包
- url-loader
- postcss-loader
- px2rem-loader
- raw-loader:将文件以字符串的形式导入,可以用于静态资源内联
- thread-loader:多进程打包JS和CSS,使打包的速度更快
loaders的用法
module.exports = {
...
module: {
rules: [
{test: /\.txt$/, use: 'raw-loader'} // test指定匹配法则, use指定使用的loader名称
]
}
...
}
plugins
loaders无法实现的,可以通过plugins来实现。plugin用于bundle文件的优化,资源管理和环境变量注入,增强webpack的功能,作用于整个构建过程。
常见的plugins
- CommonsChunkPlugin:将chunks相同的模块代码提取成公共的js
- CleanWebpackPlugin:清理构建目录
- ExtractTextWebpackPlugin:将css从bundle文件里提取成一个独立的css文件
- CopyWebpackPlugin:将文件或者文件夹拷贝到构建的输出目录
- HtmlWebpackPlugin:创建html文件去承载输出的bundle
- UglifyjsWebpackPlugin:压缩js
- ZipWebpackPlugin:将打包出的资源生成一个zip包
mode(webpack4引入的)
mode用来指定webpack当前打包的环境:production、development、none 设置mode可以使用webpack内置的函数,默认值为production
mode的内置函数功能
- mode为development:会设置process.env.NODE_ENV的值为development,开启NamedChunksPlugin和NamedModulesPlugin
- mode为production:会设置process.env.NODE_ENV的值为production,开启FlagDependencyUsagePlugin、FlagIncludeChunksPlugin、ModuleConcatenationPlugin、NoEmitOnErrorsPlugin、OcurrenceOrderPlugin、SideEffectsFlagPlugin和TerserPlugin
- mode为none:不开启任何优化选项
资源解析
解析ES6
- 使用babel-loader
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'boundle.js',
},
module: {
rules: [
{test: /\.js$/, use: 'babel-loader'}
]
}
};
- 设置babel的配置文件 .babelrc,增加ES6的babel preset配置
// .babelrc文件
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/proposal-class-properties"
]
}
解析CSS
- css-loader:用于加载.css文件,并且转换成commonjs对象
- style-loader:将样式通过style标签插入到head中
```javascript
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'boundle.js',
},
module: {
rules: [
{test: /\.js$/, use: 'babel-loader'},
{test: /\.css$/, use: ['style-loader', 'css-loader']}
]
}
};
解析Less/SaSS
- less-loader:将less文件转换为css
- sass-loader:将sass文件转换为css
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'boundle.js',
},
module: {
rules: [
{test: /\.js$/, use: 'babel-loader'},
{test: /\.css$/, use: ['style-loader', 'css-loader']},
{test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']},
{test: /\.s[ac]ss$/i, use: ['style-loader', 'css-loader', 'sass-loader']},
]
}
};
解析图片和字体
- file-loader: 用于处理文件
- url-loader: 也可以处理图片和字体,它其实也是基于 file-loader,只不过 url-loader 多了可以自动设置小图片、字体 转换为 base64,内联到代码里面。
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'boundle.js',
},
module: {
rules: [
{test: /\.js$/, use: 'babel-loader'}, // 解析es6
{test: /\.css$/, use: ['style-loader', 'css-loader']}, // 解析css文件
{test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader']}, // 解析less文件
{test: /\.s[ac]ss$/i, use: ['style-loader', 'css-loader', 'sass-loader']}, // 解析scss文件
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: 'file-loader'}, // 解析图片
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'url-loader', options: { limit: 10240 }}]}, // 解析图片另一种方法
{test: /\.(woff|woff2|eot|ttf|otf)$/, use: 'file-loader'}, // 解析字体
]
}
};
webpack中的文件监听
文件监听是在发现源码发生变化时,自动重新构建出新的输出文件。
webpack开启监听模式的两种方式:
- 启动webpack时带上 --watch参数
- 在配置webpack.config.js中设置 watch: true
缺陷:每次都需要手动刷新浏览器
webpack文件监听的原理分析:
轮询判断文件的最后编辑时间是否发生变化 某个文件发生了变化,会将文件变化的时间存储起来,下次再修改的时候,会和上次的修改时间进行比对,如果不一致,并不会立即告诉监听者,而是将文件的修改先缓存起来,等agreegateTimeout,如果等待的时间有其他文件发生变化,会把变化的文件列表一起去构建,最后把构建结果输出到output设置的bundle中。
module.exports = {
// 默认是false,也就是不开启
watch: true,
// 只有开启监听时,watchOptions才有意义
watchOptions: {
// 默认为空,不监听的文件或文件夹,支持正则匹配
ignored: /node_modules/,
// 监听到变化发生后,会等300ms再去执行,默认是300ms
agreegateTimeout: 300,
// 判断文件是否发生变化是通过不停地询问系统指定文件有没有变化实现的,默认每秒问1000次,即1ms询问一次
poll: 1000
}
}
webpack中的热更新
热更新:webpack-dev-server
WDS不刷新浏览器,不输出文件,而是放在内存中,通常和HotModuleReplacementPlugin插件一起使用。
hot: true,开启此配置时会自动添加HotModuleReplacementPlugin插件,所以也可以不用单独引入。
webpack-dev-server(WDS)的功能提供 bundle server的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访问,另外 WDS 也提供 livereload(浏览器的自动刷新)。
webpack 构建出来的 bundle.js 本身是不具备热更新的能力的,HotModuleReplacementPlugin 的作用就是将 HMR runtime 注入到 bundle.js代码里面去,使得bundle.js可以和HMR server建立websocket的通信连接,一旦磁盘里面的文件修改,那么 HMR server 会将有修改的 js module 信息发送给 HMR runtime,然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。
热更新的原理
- Webpack Compile:将JS编译成bundle.js
- HMR Server:将热更新的文件输出给HMR runtime,是服务端,用来将变化的 js 模块通过 websocket 的消息通知给浏览器端。
- Bundle Server:提供文件在浏览器的访问
- HMR Runtime:会被注入到浏览器,更新文件的变化。是浏览器端,用于接受 HMR Server 传递的模块数据,浏览器端可以看到 .hot-update.json 的文件过来。
- bundle.js:构建输出的文件
热更新的过程
- 启动阶段(1->2->A->B):在文件系统,将初始代码通过webpack complile进行打包,将编译好的文件传输给bundle server,bundle server是一个服务器,bundle server让浏览器能够访问到这个bundle.js文件
- 更新阶段(1- 2 - 3- 4- 5),文件发生变动时,通过webpack compile进行编译,将代码发送给HMR server,通知HMR runtime,更新代码。
文件指纹
- Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会更改
- Chunkhash:和webpack打包的chunk有关,不同的entry会生成不同的chunkhash值
- Contenthash:根据文件内容来定义hash,文件内容不变,则contenthash不变
文件指纹设置
- 设置output的filename,使用[chunkhash]
- 设置MiniCssExtractPlugin的filename,使用[contenthash]
- 设置file-loader的name,使用[hash]
- [ext]:资源后缀名
- [name]:文件名称
- [path]:文件的相对路径
- [folder]:文件所在的文件夹
- [contenthash]:文件的内容hash,默认是md5生成
- [hash]:文件内容的hash,默认是md5生成
- [emoji]:一个随机的指代文件内容的emoji
// webpack.pro.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader']}, // 解析css文件
{test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']}, // 解析less文件
{test: /\.s[ac]ss$/i, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']}, // 解析sass文件
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'file-loader', options: {name: '[name]_[hash:8].[ext]'}}]}, // 解析图片
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'url-loader', options: { limit: 10240 }}]}, // 解析图片另一种方法
{test: /\.(woff|woff2|eot|ttf|otf)$/, use: [{loader: 'file-loader', options: {name: '[name]_[hash:8].[ext]'}}]}, // 解析字体
]
},
plugins: [
// new ExtractTextPlugin('[name].[contenthash:10].css'),
new MiniCssExtractPlugin({filename: '[name].[contenthash:8].css'}),
]
mode: 'production'
};
// package.json
{
// ...
"scripts": {
"build": "webpack --config webpack.pro.config.js",
"dev": "webpack-dev-server --config webpack.dev.config.js --open"
},
// ...
}
文件的压缩
- html压缩:修改html-webpack-plugin设置压缩参数
- css压缩:使用optimize-css-assets-webpack-plugin,同时使用cssnano(css处理器)
- js压缩:webpack4中内置了uglifyjs-webpack-plugin, mode是production时,默认开启
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader']}, // 解析css文件
{test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']}, // 解析less文件
{test: /\.s[ac]ss$/i, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']}, // 解析sass文件
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'file-loader', options: {name: '[name]_[hash:8].[ext]'}}]}, // 解析图片
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'url-loader', options: { limit: 10240 }}]}, // 解析图片另一种方法
{test: /\.(woff|woff2|eot|ttf|otf)$/, use: [{loader: 'file-loader', options: {name: '[name]_[hash:8].[ext]'}}]}, // 解析字体
]
},
plugins: [
// new ExtractTextPlugin('[name].[contenthash:10].css'),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css'
}),
new OptimizeCssAssetsWebpackPlugin({
assetNameRegExp: '/\.css$/g',
cssProcessor: require('cssnano')
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'), // 文件模板所在的位置
filename: 'index.html', // 打包出来的文件名称
chunks: ['search'], //
inject: true, // 打包出来的js、css会自动的注入到打包出来的html文件中
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
]
mode: 'production'
};
webpack进阶用法
自动清理构建目录
清理构建目录有两种方式。
- 在package.json scripts中添加以下命令中的一个:
- rm -rf ./dist
- rimraf ./dist
// package.json
{
// ...
"scripts": {
"build": "rm -rf ./dist && webpack --config webpack.pro.config.js",
"dev": "webpack-dev-server --config webpack.dev.config.js --open"
},
// ...
}
- 避免构建前每次都需要手动删除dist,使用clean-webpack-plugin,默认会删除output指定的输出目录。
自动补齐css3前缀
使用postcss的autoprefixer插件
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader']}, // 解析css文件
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ["last 2 version", "> 1%", "ios 7"]
})
]
}
}
]
}, // 解析less文件
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'url-loader', options: { limit: 10240 }}]}, // 解析图片另一种方法
]
},
plugins: [
// new ExtractTextPlugin('[name].[contenthash:10].css'),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css'
})
]
mode: 'production'
};
移动端css px自动转换成rem
rem定义
font-size of the root element
rem和px的区别
- rem:相对单位,相对根元素的字体的大小
- px:绝对单位
px自动转换为rem
使用px2rem-loader,和手淘的lib-flexible一起使用。
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js',
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
},
{test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader']}, // 解析css文件
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ["last 2 version", "> 1%", "ios 7"]
})
]
}
},
{
loader: 'px2rem-loader',
options: {
remUnit: 37.5, // 1rem = 37.5px, 适用于375的设计稿
remPrecision: 8 // 转换为rem,小数点后的保留位数
}
}
]
}, // 解析less文件
{test: /\.(png|svg|jpg|jpeg|gif)$/, use: [{loader: 'url-loader', options: { limit: 10240 }}]}, // 解析图片另一种方法
]
},
plugins: [
// new ExtractTextPlugin('[name].[contenthash:10].css'),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css'
})
]
mode: 'production'
};
多页面打包的通用方案
基本思路
一个页面对应一个entry,一个html-webpack-plugin
缺点:每次新增/删除页面,都需要手动的修改webpack的配置
通用打包方案
- 动态获取entry和设置heml-webpack-plugin
- 利用glob.sync
// webpack.pro.config.js
var glob = require('glob');
var setMAP = () => {
var entry = {};
var htmlWebpackPlugins = [];
var entryFiles = glob.sync(path.join(__dirname, './src/*/index.js')); // 读取src文件夹中的文件形成的数组
Object.keys(entryFiles).map((index) => {
const entryFile = entryFiles[index];
const match = entryFile.match(/src\(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`), // 文件模板所在的位置
filename: `${pageName}.html`, // 打包出来的文件名称
chunks: [pageName], // 指定使用哪些chunk
inject: true, // 打包出来的js、css会自动的注入到打包出来的html文件中
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
});
return {
entry,
htmlWebpackPlugins
}
};
const { entry, htmlWebpackPlugins } = setMAP();
module.exports = {
entry,
output: {
filename: '[name]_[chunkhash:8].js', // 通过占位符确保文件名称的唯一
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
// ...
]
},
plugins: [
// ...
].concat(htmlWebpackPlugins),
mode: 'production'
}
source map
source map关键字
- eval:使用eval包裹模块代码
- source map:产生.map文件
- cheap:不包含列信息
- inline:将.map作为DataURI嵌入,不单独生成.map文件
- module:包含loader的source map
提取页面公共资源
基础库分离
- 使用html-webpack-externals-plugin,将react、react-dom、vue等基础包,通过cdn引入,不打入bundle中
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
module.exports = {
// ...
plugins: [
new HtmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: 'xxx/react.min.js', // cdn上react的地址,或者本地的文件
global: 'React'
},
{
module: 'react-dom',
entry: 'xxx/react-dom.min.js', // cdn上react的地址,或者本地的文件也可以
global: 'ReactDOM'
}
]
})
]
};
- 利用SplitChunksPlugin,这个是webpack4内置的,替代CommonsChunkPlugin。
chunks参数说明:
- async:异步引入的库进行分离(默认)
- initial:同步引入的库进行分离
- all:所有引入的库进行分离(推荐)
module.exports = {
// ...
// HtmlWebpackPlugin的chunks中需要加入vendors
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src/index.html'), // 文件模板所在的位置
filename: 'index.html', // 打包出来的文件名称
chunks: ["vendors", "index"], // 指定使用哪些chunk
inject: true, // 打包出来的js、css会自动的注入到打包出来的html文件中
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
],
optimization: {
splitChunks: {
// chunks: 'all',
// minSize: 3000, // 分离出来的公共包最小的体积
// maxSize: 0, // 抽离出来的公共包最大的大小
// minChunks: 1, // 设置最小的引用次数
// maxAsyncRequests: 5, // 浏览器同时请求异步资源的数量
// maxInitialRequests: 3,
// automaticNameDelimiter: '~',
// name: true,
cacheGroups: {
commons: {
test: /(react|react-dom)/,
name: "vendors",
chunks: "all"
}
}
}
}
};
分离页面公共文件
利用SplitChunksPlugin, 会把公共引用的文件,打包出一个commons.js
module.exports = {
// ...
optimization: {
splitChunks: {
// chunks: 'all',
// minSize: 3000, // 分离出来的公共包最小的体积
maxSize: 0, // 抽离出来的公共包最大的大小
// minChunks: 1, // 设置最小的引用次数
// maxAsyncRequests: 5, // 浏览器同时请求异步资源的数量
// maxInitialRequests: 3,
// automaticNameDelimiter: '~',
// name: true,
cacheGroups: {
commons: {
name: "commons",
chunks: "all",
minChunks: 2 //
}
}
}
}
};
Tree Shaking(摇树优化)
概念
一个模块可能有多个方法,只要其中某个方法使用到了,则整个文件都会被打包到bundle中去,tree shaking就是只把用到的方法打包到bundle中去,没用到的方法会在uglify阶段被擦除掉。
webpack默认支持,在.babelrc中设置modules: false即可
mode是production时,默认开启。
要求:必须是es6的语法,commonjs的方式是不支持的
原理
利用ES6模块的特点:
- 只能作为模块顶层的语句出现
- import的模块名只能是字符串常量
- import binding是immutable(不可变的)
uglify阶段擦除无用代码:
- 代码不会被执行,不会到达
- 代码执行的结果不会被用到
- 代码只会影响死变量(只写不读)
动态加载js的方式
- commonjs:require.ensure()
- ES6:动态import () => import('xxx'); // 需要babel转换
优化构建时命令行的显示日志
- 使用friendly-errors-webpack-plugin
- success:构建成功的日志提示
- warning:构建警告的日志提示
- error:构建报错的日志提示
- 设置stats为errors-only stats的值可选:
- errors-only:只在发生错误时输出
- minimal:只在发生错误或者新的编译时输出
- none:没有输出
- normal:标准输出
- verbose:全部输出
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
// ...
plugins: [
new FriendlyErrorsWebpackPlugin()
],
stats: 'errors-only'
}
编译成功时的日志提示: 编译失败时的日志提示: 编译发生警告时的提示: