目录
1 简介
环境:nodejs 10+、webpack 4.26+
webpack:前端资源构建工具,静态模块打包器,将前端所有资源作为模块化处理,分析依赖并打包成静态资源bundle。可以编译转换浏览器无法识别最新的语法:es6或者less等。webpack相当于一个工具集合,里面包含了很多小工具。
流程:从入口进入,分析入口文件所依赖的各种资源,并进行引入,形成chunk
。对chunk
中的各种资源进行分别解析编译形成bundle
。
2 webpack五个核心概念
- Entry:入口,webpack以哪个文件为起点分析和构建关系依赖图。
- Output:打包好后的bundle输出的目的地,及命名规则
- Loader:扩展,webpack可识别的文件类型(默认只识别js)
- Plugins:插件,功能更强,相当于功能扩展
- Mode:模式,开发和生产,选择不同的模式会自动启用不同的一些插件
- 开发:
process.env.NODE_ENV=development
,启用:NamedChunksPlugin和NamedModulesPlugin - 生产:
process.env.NODE_ENV=production
,启用:FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin, UglifyJsPlugin
- 开发:
注意:loader和plugin区别
loader即为文件加载器,操作的是文件,将文件A通过loader转换成文件B,是一个单纯的文件转化过程。
plugin即为插件,是一个扩展器,丰富webpack本身,增强功能 ,针对的是在loader结束之后,webpack打包的整个过程,他并不直接操作文件,而是基于事件机制工作,监听webpack打包过程中的某些节点,执行广泛的任务。
3 尝试
全局安装:npm i webpack webpack-cli -g
本地安装:npm i webpack webpack-cli -D
3.1 打包js/json
前提准备:新建:build目录,src目录,src/index.js文件,并在本地npm安装好了webpack和webpack-cli
执行:webpack ./src/index.js -o ./build/built.js --mode=development
(会将es6模块化编译成浏览器可识别模块化)
index.js代码:
import data from './data.json'
console.log(data)
function add(a,b){return a+b}
console.log(add(1,2))
data.json代码:
{
"name":"pp",
"age":12,
"sex":"male"
}
执行:webpack ./src/index.js -o ./build/built.js --mode=production
(表示生产环境,还会对代码做压缩)
3.2 打包css/less
index.js中引入css文件然后正常打包,打包报错,说明webpack默认不支持css/img,需要loader
来翻译不识别的文件
loader使用需要【下载、配置】才能生效
webpack.config.js
配置文件,如果需要配置详细参数,建立配置文件来使用
特别注意:由于构建工具基于nodejs的,模块化采用的commonjs,所以配置文件导出使用:module.exports
const { resolve } = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
// path必须要是全路径,所以使用nodejs中的path来获取
path: resolve(__dirname,'build')
},
// loader有关配置
module: {
rules: [
// 详细配置
{
// test表示匹配文件类型
test: /\.css$/,
// 使用哪些loader,执行顺序从后往前
use: [
'style-loader', // 将js中样式字符串插入到style标签中,放到head标签中生效
'css-loader' // 将css文件编程commonjs模块加载到js中,内容是样式字符串
]
}
]
},
// plugins插件有关配置
plugins: [
],
mode: 'development',
// mode: 'production'
}
如果需要打包less:需要配置less的loader才行,如下
处理less文件,在css loader后添加一个loader即可,注意:每个loader只能处理一个文件类型
{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader']
}
配置好后需要下载相关模块:npm i less less-loader -D
3.3 打包html
打包html使用的是插件,插件需要【下载、引入、配置】,才能生效
下载:html-webpack-plugin
:npm i html-webpack-plugin -D
引入和配置:webpack.config.js
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname,'build')
},
// loader有关配置
module: {
rules: []
},
// plugins插件有关配置
plugins: [
new HtmlWebpackPlugin({ // 默认创建一个空的html文件,并引入打包输出的所有资源,如果需要模板则需要传入配置{template}
template: './src/index.html' // 以该文件为模板
})
],
mode: 'development',
// mode: 'production'
}
注意:不要在html模板文件中再手动引入资源,会自动引入,只需在入口文件中引入资源即可
3.4 打包图片
引用图片分两种:
- 样式文件中的url引入
- html中的src引入
对于css样式文件中引入的,需要安装url-loader
和file-loader
,并配置webpack:
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname,'build')
},
// loader有关配置
module: {
rules: [
{
// test表示匹配文件类型
test: /\.css$/,
// 使用哪些loader,执行顺序从后往前
use: [
'style-loader', // 将js中样式字符串插入到style标签中,放到head标签中生效
'css-loader' // 将css文件编程commonjs模块加载到js中,内容是样式字符串
]
},
{
test: /\.(jpg|png|gif)$/, // 多个文件类型这样写
loader: 'url-loader', // 一个loader这样写
options: { // 该loader的额外配置
limit: 8*1024 // 8kb,小于该值会被base64编码
}
}
]
},
// plugins插件有关配置
plugins: [
new HtmlWebpackPlugin({ // 默认创建一个空的html文件,并引入打包输出的所有资源,如果需要模板则需要传入配置{template}
template: './src/index.html' // 以该文件为模板
})
],
mode: 'development',
// mode: 'production'
}
base64处理图片可以减少请求数量,减轻服务器压力,但是图片体积可能会更大,所以一般用来处理8kb以下的
对于html中src引入的:需要下载html-loader
,该loader专门用于处理html中图片src引入,使其可以被url-loader解析
{
test: /.\html$/,
loader: 'html-loader'
}
此处会发现html中的图片引用错误,原因是:html-loader是采用commonjs模块化导出,而url-loader是使用es6模块化解析的
解决方法:关闭url-loader的es6模块化,使用默认的commonjs解析
{
test: /\.(jpg|png|gif)$/, // 多个文件类型这样写
loader: 'url-loader', // 一个loader这样写
options: { // 该loader的额外配置
limit: 8*1024, // 8kb,小于该值会被base64编码
esModule: false // 关闭该loader的es6模块化,使用commonjs解析(因为html-loader解析后引入的图片方式采用commonjs)
}
}
给引入的图片进行重命名:name:'[hash:10].[ext]
,取图片hash值前10位
{
test: /\.(jpg|png|gif)$/, // 多个文件类型这样写
loader: 'url-loader', // 一个loader这样写
options: { // 该loader的额外配置
limit: 8*1024, // 8kb,小于该值会被base64编码
esModule: false, // 关闭该loader的es6模块化,使用commonjs解析(因为html-loader解析后引入的图片方式采用commonjs)
name:'[hash:10].[ext]'
}
}
3.5 打包其他资源
其他资源:除了css/js/html外的其他资源,可以使用exclude
来匹配排除类型外的所有资源,只需要在loader中配置:
{
exclude: /\.(css|js|html)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]'
}
}
所有不包括css/js/html的资源都会被这个loader打包处理
3.6 devServer
配置开发服务器,可以自动编译打包和打开浏览器,刷新,看到更改后的效果
// 会在内存中打包,不会有任何输出
// 启动:webpack-dev-server
devServer: {
contentBase: resolve(__dirname,'build'), // 运行项目的目录,如果配置了html-webpack-plugin则该配置不起作用
compress: true, // 启动gzip压缩
port: 3000 // 端口号
open: true // 自动打开浏览器
}
需要先安装:npm i -D webpack-dev-server
将指令配置到package.json中去:"start":"webpack-dev-server"
然后使用npm run start
来启动
注意:webpack5后启动命令改成了:webpack serve
一旦源代码被修改,并保存后,会自动进行编译,并展示到页面上
4 环境基本配置
4.1 开发环境
打包:js、json、html、css、less、img、file等资源
const {resolve} = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/built.js', // 入口文件存放地,该文件包括js和css的合并
path: resolve(__dirname,'build') // 所有资源存放地
},
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader']
},
{
test: /\.css$/,
use: ['style-loader','css-loader']
},
{
test: /\.(jpg|png|jpeg|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
esModule: false,
outputPath: 'imgs'
}
},
{
test: /.\html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|js|css|less|jpg|png|jpeg|gif)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'medias'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
// 会在内存中打包,不会有任何输出
// 启动:webpack-dev-server
devServer: {
contentBase: resolve(__dirname,'build'), // 运行项目的目录
compress: true, // 启动gzip压缩
port: 3000, // 端口号
open: true // 自动打开浏览器
},
mode: 'development'
}
4.2 生产环境
为了让代码更快,性能更好,稳定性更高(开发环境不需要考虑这些)
- 开发环境中css和js是打包到一起的bundle,在加载过程中,先加载js然后再创建style标签加载css,这样会出现闪屏现象
- 代码需要统一压缩
- 样式的兼容
4.2.1 提取CSS到单独文件
需要安装插件:npm i -D mini-css-extract-plugin
步骤:注册插件 替换style-loader
为MiniCssExtractPlugin.loader
(sytle-loader为js中的样式字符串创建style标签并插入到html中,现在由MiniCssExtractPlugin.loader来提取js中的样式字符串到单独文件,并引入样式标签)
const {resolve} = require("path")
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/built.js', // 入口文件存放地,该文件包括js和css的合并
path: resolve(__dirname,'build') // 所有资源存放地
},
module: {
rules: [
...
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
},
...
]
},
plugins: [
...
new MiniCssExtractPlugin({
filename: 'css/built.css'
})
],
...
}
4.2.2 css兼容性处理
需要使用:postcss-loader
和postcss-preset-env
(用于精确到浏览器版本)
安装:npm i -D postcss-loader postcss-preset-env
const {
resolve
} = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 这样设置node环境变量,postcss会读取该环境变量来判断读取生产还是开发配置
// process.env.NODE_ENV = "development"
process.env.NODE_ENV = "production"
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/built.js', // 入口文件存放地,该文件包括js和css的合并
path: resolve(__dirname, 'build') // 所有资源存放地
},
module: {
rules: [{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'postcss',
plugins: [
require('postcss-preset-env')()
]
}
}
},
'less-loader',
]
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'postcss',
plugins: [
require('postcss-preset-env')()
]
}
}
}
]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
})
],
}
特别注意:一、需要配置node环境变量:process.env.NODE_ENV = “production”;二、配置postcss-loader及其参数,这个loader需要先css-loader处理。三、在package.json中配置browserlist
在package.json中配置browserslist
,来指定加载css兼容性样式
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
关于browserslist详细配置可以去github上查询,注意:默认情况插件执行的是生产环境配置,除非配置了Node环境变量(和webpack.config.js中的mode无关)
编译后的css代码加了兼容样式
#b {
display: -webkit-flex;
display: flex;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
4.2.3 css压缩
需要用到插件:optimize-css-assets-webpack-plugin
安装:npm i -D optimize-css-assets-webpack-plugin
配置方法:引入插件,new插件即可。
const {resolve} = require("path")
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/built.js', // 入口文件存放地,该文件包括js和css的合并
path: resolve(__dirname,'build') // 所有资源存放地
},
module: {
rules: [
...
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
},
...
]
},
plugins: [
...
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin()
],
...
}
注意:在postcss-loader
中有个:'cssnano': {}
的配置,也可以用来压缩css!示例:cssnano: ctx.env === 'production' ? {} : false
4.2.4 JS语法检查——eslint
eslint用于js语法检查,需要安装:eslint-loader
和eslint
注意:由于需要限定只检查src下的源代码,不检查第三方库(使用exclude)
eslint检查规则需要在package.json
中的eslintConfig
属性设置,建议使用airbnb规则
eslint-config-airbnb-base:基本检查库,不包括react插件,该库依赖:eslint-plugin-import eslint
eslint-config-airbnb:包括react插件
步骤:(需要先安装:npm i -D eslint eslint-loader eslint-config-airbnb-base eslint-plugin-import
)
- loader配置
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre", // 编译前检查
exclude: /node_modules/, // 不检测的文件
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
fix: true // 自动修复错误
}
}
- package.json配置,这里使用airbnb语法规则
"eslintConfig":{
"extends":"airbnb-base"
}
可以使用注释来取消检查:// eslint-disable-next-line
取消下一行语法检查,/* eslint-disable */
放在文件第一行表示该文件不检查
参考:https://blog.youkuaiyun.com/kai_vin/article/details/89026115
4.2.5 JS兼容性处理
JS兼容即有些浏览器不支持ES6语法,babel会将其转码成ES5,依赖:@babel/core
,称为兼容处理。使用到的loader为:babel-loader
,依据使用的预设环境可以转换不同的语法。
【基本兼容处理】:@babel/preset-env
,
安装:npm i -D babel-loader @babel/core @babel/preset-env
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
只能转换基本的如const、箭头函数等。不支持Promise等。
【暴力处理】全部js兼容处理:@babel/polyfill
安装:npm i -D @babel/polyfill
使用:在入口文件中引入即可:import '@babel/polyfill
@babel/polyfill
目前已经替换成了:core-js/stable
+ regenerator-runtime/runtime
,使用:
import "core-js/stable";
import "regenerator-runtime/runtime";
【按需加载兼容处理】使用core-js
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
{
useBuiltIns: 'usage', // 开启按需加载
corejs: {version: 3}, // 指定core-js版本
targets: {chrome:'60',firefox:'60',ie:'9',safari:'10',edge:'17'} // 指定兼容性哪个版本以上
}
]
}
}
注意:使用该方案则不能使用暴力处理方式
4.2.6 html和js压缩
js压缩:只需要将mode='production'
开启即可(发挥作用的插件是:UglifyJsPlugin
)
html压缩:同样,只需要开启生产模式即可,当然也可以在插件html-webpack-plugin
中配置:
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true, // 移除空格
removeComments: true // 移除注释
}
}),
4.2.7 生产环境终极配置
const { resolve } = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
// 这样设置node环境变量,postcss会读取该环境变量来判断读取生产还是开发配置
// process.env.NODE_ENV = "development"
process.env.NODE_ENV = "production"
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
ident: 'postcss',
plugins: [
require('postcss-preset-env')()
]
}
}
}
]
module.exports = {
entry: './src/index.js',
output: {
filename: 'js/built.js', // 入口文件存放地,该文件包括js和css的合并
path: resolve(__dirname, 'build') // 所有资源存放地
},
module: {
rules: [{
test: /\.less$/,
use: [
...commonCssLoader,
'less-loader'
]
},
{
test: /\.css$/,
use: [
...commonCssLoader
]
},
{
test: /\.(jpg|png|jpeg|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
esModule: false,
outputPath: 'imgs'
}
},
{
test: /.\html$/,
loader: 'html-loader'
},
{
exclude: /\.(html|js|css|less|jpg|png|jpeg|gif)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'medias'
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
{
useBuiltIns: 'usage', // 开启按需加载
corejs: {version: 3}, // 指定core-js版本
targets: {chrome:'60',firefox:'60',ie:'9',safari:'10',edge:'17'} // 指定兼容性哪个版本以上
}
]
}
},
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre", // 编译前检查,相同test中该属性为true的先执行
exclude: /node_modules/, // 不检测的文件
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
fix: true
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true, // 移除空格
removeComments: true // 移除注释
}
}),
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin()
],
// 会在内存中打包,不会有任何输出
// 启动:webpack-dev-server
devServer: {
contentBase: resolve(__dirname, 'build'), // 运行项目的目录
compress: true, // 启动gzip压缩
port: 3000, // 端口号
open: true // 自动打开浏览器
},
mode: 'production'
}
package.json中:
{
...
"scripts": {
...
"build": "webpack",
"start": "webpack serve"
},
...
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig":{
"extends":"airbnb-base"
}
}
5 webpack优化
两种环境的优化
开发环境:1 优化打包构建速度 2 优化代码调试
生产环境:1 优化打包构建速度 2 优化代码运行性能
5.1 开发环境
问题1:修改任意模块,整个项目所有模块会重新加载
解决:HMR:热模块替换,一个模块变化只会打包该模块,不会打包所有
在devServer中添加:hot:true
devServer: {
contentBase: resolve(__dirname, 'build'), // 运行项目的目录
compress: true, // 启动gzip压缩
port: 3000, // 端口号
open: true, // 自动打开浏览器
hot: true // 热加载
},
对于样式修改,由于style-loader
实现了HMR功能,所以可以实现热模块更新
对于JS文件,没有HMR功能
解决办法:添加HMR功能
// 在入口文件中,对于引入的其他模块进行监听是否更新
if(module.hot){
module.hot.accept('./xxx.js',function(){回调函数}
}
注意:入口js文件不需要也不可以做HMR,因为他改了其他函数肯定会重新引入的
对于HTML文件,不能使用HMR功能(因为html只有一个文件),且开启后无法响应更新了
解决办法:在webpack.config.js中入口处加上html文件入口:entry: ['./src/index.js','./src/index.html'],
source-map:提供源代码和构建后代码的映射技术,方便查询错误原因和定位
配置:在webpack.config.js中添加:devtool: 'source-map'
即可
注意:source-map有多种值:
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
其中:
source-map:外部 错误定位到 【源】 代码,精确到行和列
inline-source-map:内联 错误定位到 【源】 代码
hidden-source-map:外部 错误定位到 【编译后】 代码
eval-source-map:内联 错误定位到 【源】 代码
nosources-source-map:外部 错误定位到 【源】 代码,但是无法查看
cheap-source-map:外部 错误定位到 【源】 代码,之精确到行,不到列
cheap-module-source-map:外部 错误定位到 【源】 代码,精确度稍微好点。。。
内敛生成的映射在打包的js中,构建起来速度更快,外部则在js同级
开发推荐使用:eval-source-map
生产推荐使用:source-map