一、webpack的简介
- webpack是前端资源的构建和打包工具,所谓构建工具就是将一些浏览器不识别的语法代码转换为浏览器识别的语法。如.less -> CSS
- 打包过程
通过入口文件index.js(可通过Entry配置)的引用关系,形成引用关系图,然后按照这个关系图把所有依赖引入进来形成chunk代码块,然后webpack根据不同的工具把浏览器不识别的语法转移为识别的语法(.less -> css),转换后输出浏览器识别的代码bundle。
二、五个核心概念
位于src(项目源代码,使用的ES6Module)同级的webpack.config.js文件中(配置文件,当运行webpack时,会加载里面的配置,根据配置进行打包,构建.所以构建平台是基于node.js,所以使用的CommonJs)
1 Entry:规定webpack,创建依赖关系的入口文件。
2 loader:翻译官,将less、sass、img等不识别的资源,翻译为webpack识别的资源。
3 Plugin:让webpack具有压缩、优化等高级功能。
4 Output:规定打包后生产的boudle文件的位置和明名。
5 Mode:规定代码运行的环境,包括开发环境development和生产环境(production:会自动压缩Js代码),不同的环境,webpack会自动启用不同的plugin插件。
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.export = {
// 构建入口文件
entry: './src/index/js',
// 构建后文件位置
output: {
// 构建后文件名称
filename: 'built.js',
// 构建后的文件输出路径 防止路径错误,采用绝对路径
// resolve是node.js的一个变量,用于获取当前文件目录的觉得路径,这里是webpack.config.js的目录即WEBPACK项目
path: resolve(__dirname, 'build'),
},
// loader 下载 使用
module: {
// 具体的loader配置
rules: [
// 不同的文件需要使用不同的loader进行处理,每一个配置都会生成一个style标签,这里处理css、less就会在head中插入两个style标签,
{
// 一般为正则表达式 匹配对应文件,用于loader处理
test: /\.CSS$/,
// 对于匹配中的文件,使用那些loader进行处理
// 一般的loader执行顺序是由右向左,从下往上顺序执行
use: [
// 在head中创建Style标签,并将js中的样式插入进去
'style-loader',
// 将css文件变成commonJs模块,并引入到Js中,字符串的形式
'css-loader',
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
// less-loader 需要下载 less-loader(将less文件编译成css文件)、less(添加less模块,可以编写less代码)
'less-loader',
]
},
{
test: /\.(png|jpg|git)$/,
// 只使用一个loader可以这样写,多个loader就需要使用use来写
// url-loader依赖file-loader,所以这里需要下载两个loader
// 默认不能处理html中img引入的图片资源
loader: 'url-loader',
// 对于图片处理的配置
options: {
// 当图片小于8K时,webpack就不会引入这个资源,而是会编译为base64处理,有浏览器根据url自行渲染
// 优点:请求次数减少(服务器压力降低)
// 缺点: 使用base64编码后,图片体积会变大,文件请求时间变长(一般大小8 - 12 K可以使用base64编码)
limit: 8 * 1024,
// 使用html-loader来处理html中的img图片资源时候,由于使用的语法不同,会导致打包后img的scr变为[object Module]
// url-loader 使用的ES6Module 而html-loader使用的CommonJs
// 解决:关闭url-loader中的ES6Module,使用CommonJs解析
esModule: false,
// 由于打包后的图片名称是hash值,会很长,所以我们重命名打包后的图片名称。
// [hash:10] 取生成hash值的前10为 [ext]: 使用原文件的扩展名
name: '[hash:10].[ext]',
}
},
{
test: /\.html$/,
// html-loader可以处理html中的img图片资源,可负责将其中的图片引入,然后交由url-loader进行解析
loader: 'html-loader',
esModule: false,
},
{
// 打包其他资源(除了js、css、html之外的资源)
exclude: /\.(css|js|html)$/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
}
}
],
},
// Plugin 下载 引入 使用
// 可以理解为插件就是一个类,使用new的方式,来运行插件
plugins: [
// 具体的plugins配置
// 'html-webpack-plugin 插件为创建一个空的html文件,并自动引入打包的模块资源(比如:css/js)
// 创建的是空的html,不包含内容结构,但是我们需要包含结构的时候,就需要添加使用配置
new HtmlWebpackPlugin({
// 不在创建空的html文件,而且使用配置路径提供的模板文件,复制该文件,并自动引入打包的模块资源(比如:css/js)
template: './src/index/html',
}),
],
// model
// mode: 'production',
mode: 'development'
}
三、devServe
当我们在修改源代码文件时,wenpack并不会自动更新打包,需要手动再次打包最新代码,所以使用devServe来解决这个问题,通过配置devServe使webpack具有自动化功能(自动编译,自动打开并刷新浏览器)
devServer: {
// 项目构建后的文件路径
contentBase: resolve(__dirname, 'build'),
// 使用gzip压缩
compress: true,
// 默认打开端口
port: 3000,
// 自动打开默认浏览器
open: true,
}
四、提取CSS文件并优化
4.1 提取CSS文件
因为通过css-loader之后,我们的样式代码都被放进了js文件中,这会导致js文件过大的问题,然后style-loader将创建style标签,因为浏览器是先执行js后,然后创建style样式。所以这里会出现闪屏现象,为了解决这些问题,我们需要把CSS文件从js文件中单独提取出来,并且我们可以做一些优化(代码压缩、兼容性处理),这里借助mini-css-extract-plugin插件来提取(这个插件主要功能就是将css文件从js文件中提取出来,并通过link的方式引入,而不是创建style标签,避免了闪屏问题)。
4.2 CSS兼容性处理
这里借助postcss-loader 、postcss-preset-env 来实现兼容处理。
{
// 这里通过使用postcss-loader、 postcss-preset-env插件来做css的兼容处理
// postcss-loader 功能是对css进行兼容
// postcss-preset-env 是 帮postcss-loader在package.json中找到browserslist中配置的需要兼容的css版本
/* "browserslist": {
// 开发环境,需要设置node.js的环境变量来让postcss使用这里的配置 process_env_NODE_ENV = "development"
"development": [
"last 1 chrome versions", 兼容最近一个版本的chrome浏览器
"last 1 firefox versions", 兼容最近一个版本的firefox浏览器
"last 1 safari versions" 兼容最近一个版本的safari浏览器
],
// 生产环境,postcss-loader默认使用生产环境的配置
"production": [
">0.2%", // 基本兼容所有的浏览器
"not dead", // 不兼容已经不使用的浏览器
"not op_mini all" // 不兼容op_mini的所有浏览器
]
} */
loader: 'postcss-loader',
// 固定写法
ident: 'postcss',
options: {
postcssOptions: {
//或者将插件引入写在单独的配置js中
//config: './config/postcss.config.js',
plugins: () => [
require('postcss-preset-env')()
]
}
}
}
4.3 CSS代价压缩
直接借助optimize-css-assets-webpack-plugin插件压缩。
// 压缩css
new OptimizeCssAssetsWebpackPlugin(),
五、配置eslint和低版本浏览器兼容
5.1 配置eslint
为了统一编码规范,方便维护和更加美观,所以我们引入eslint来规范我们的代码。这里我们使用的主要有eslint的库、eslint-loader、以及eslint规范指南(github上有的开源库)来对我们的代码进行规范处理。
{
// 给webpack添加eslint校验
// 引入exlint库,然后为了webpack能够识别eslint,需要eslint-loader
// 校验eslint规则,需要引入制定的规则,可以自已配置也可以使用其他风格指南库
// 下载库后,在package.json中添加一项配置: eslintConfig,来使用库的eslint规则
test: /\.JS$/,
// 因为引入的eslint会对所以的匹配文件进行校验,而我们不需要对第三方库校验
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// 自动按照规则修复eslint规则
fix: true
}
},
5.2 低版本浏览器js代码兼容
针对部分浏览本浏览器不识别新语法(比如:ie不识别es6语法),我们需要做代码降级处理。主要是通过babel结合不同的loader和插件来进行处理的,这里整理了三种方案,推荐使用最后一种。
1、使用babel-loader、@babel/core、@babel/preset-env来进行简单的兼容处理。
优点:能对基本的js代码进行兼容,打包体积也小。
缺点:类似Promise这种高级语法,不支持兼容处理。
2、使用babel-loader、@babel-polyfill来进行处理
优点:直接在入口文件import引入就可,能够支持所有js代码的兼容
缺点:不管项目需要兼容的部分多或少,都会全部兼容处理,打包后的体积较大。
3、按需加载,需要兼容处理的才进行代码降级 使用babel-loader、core-js、@babel/preset-env来处理
优点:按需加载,可实现对部分代码进行兼容处理,能支持所有代码的降级处理,打包体积一般。
{
// js代码的兼容性处理,对一些低版本浏览器不能识别的语法进行降级处理,比如 IE不识别es6语法
// 使用babel-loader @babel/core @babel/preset-env能够做到基本的js代码兼容
// @babel/preset-env能够做到基本的js代码兼容,问题: 类似promise的不支持
// @babel/polyfill 能够处理所有的代码兼容,这个不是loader插件,直接在入口文件import引入就可以 问题:只需要解决部分的兼容,这里把所有的都处理了,打包体积太大
// 按需加载 需要兼容的才进行兼容处理 - core-js
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设: 告诉babel做怎样的兼容处理
presets: [
[
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定core-js的版本
core: {
version: 3,
},
// 指定兼容处理到某一版本浏览器
target: {
chrome: '60',
firefox: '60',
ie: '9',
}
}
]
],
}
}
5.3 压缩js和html代码
由于生产环境时webpack会自动启动一些插件,会自动压缩js代码,所以这里就不列举了。而对于html的压缩,我们可以使用HtmlWebpackPlugin来进行简单的压缩处理:
new HtmlWebpackPlugin({
// 不在创建空的html文件,而且使用配置路径提供的模板文件,复制该文件,并自动引入打包的模块资源(比如:css/js)
template: './src/index/html',
// 压缩html代码
minify: {
// 去掉空格
collapseWhitespace: true,
// 去掉注释
removeComments: true,
}
}),
六、Webpack性能优化
这里分为开发环境优化和生产环境优化,其中开发环境优化主要从打包速度和代码调试两个方面,而生产环境优化主要从打包速度和代码运行性能方面。
6.1 HMR 热模块替换
HMR(hot module replacement),只会打包修改过的模块,不会打包所有模块,大大的提高了打包速度。配置devSever的hot来启动热更新。
// HMR
// 功能: 只打包修改的模块,不会打包所有模块.
// 样式文件 由于style-loader已经内置了HMR,所以这里不需要对样式文件处理,主要要使用style-loader来处理css
// js文件: 默认不启动HMR,需要自己在入口js文件中进行配置热替换
/*
if (module.hot) {
module.hot.accept('要热更新的模块路径', () => {
//执行该模块
})
}
*/
// html文件: 默认不启动HMR,启动HMR后会导致html热更新失效,可以通过在entry入口添加html文件来解决,但是由于html文件只有一个 所以不需要HMR
6.2 source-map
由于打包后的代码是进行处理的,和源代码相差很大,当进行代码运行出错的时候,我们无法根据打包后代码定位到源代码,所以source-map提供了一种打包后代码与源代码的映射关系,当代码调试时,能帮助我们定位到源代码位置
可配置项:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map 可以任意组合(在devSever中配置devTools)
/**
* 外部: 在打包后的built.js文件夹下会生成.map文件, 内联: 不会在打包后的built.js文件夹下会生成.map文件
* 内联的打包速度比外部快
* source-map: 外部 提示错误代码的信息(精确到某行某列)和定位源代码位置
* hidden-source-map: 外部 提示错误代码的信息和不能定位源代码位置,只能提示到构建后代码位置
* nosources-source-map: 外部 提示错误代码的信息和没有源代码信息
* cheap-source-map: 外部 提示错误代码的粗略信息(精确到行)和定位源代码位置
* cheap-module-source-map: 外部 提示错误代码的粗略信息(精确到行)和定位源代码位置,module会将loader的source-map引入
*
* inline-source-map: 内联 只生成一个内联source-map 提示错误代码的信息(精确到某行某列)和定位源代码位置
* eval-source-map: 内联 每一个js文件都生成一个source-map,在eval函数中 提示错误代码的信息(精确到某行某列)和定位源代码位置
*
* 开发环境:打包快,调试好
* 打包速度: eval -> inline -> cheap> ...
* eval-cheap-source-map: 速度最快(eval和cheap的组合)
*
* 调试最好 source-map -> cheap-module-source-map -> cheap-source-map
*
* 所以一般打包快选择eval-source-map 调试快 eval-cheap-module-source-map
*
* 生产环境: 考虑隐藏源代码nosources-source-map,hidden-source-map ,内联会导致体积很大所以生产环境一般不使用内联,调试优、速度快 -> 推荐使用 source-map, cheap-module-source-map
*
*
*/
devTools: 'source-map',
6.3 oneOf
如果我们配置了10个loader,那么每个文件都需要通过这10个loader的处理,会影响构建速度,所以为了解决这个问题并且提高构建速度,引入了oneOf来对文件只进行一个loader处理。但是在oneOf中不能处理同一配置文件,需要把其中loader放置在oneOf之前,比如这里的eslint-loader。
{
// 给webpack添加eslint校验
// 引入exlint库,然后为了webpack能够识别eslint,需要eslint-loader
// 校验eslint规则,需要引入制定的规则,可以自已配置也可以使用其他风格指南库
// 下载库后,在package.json中添加一项配置: eslintConfig,来使用库的eslint规则
test: /\.JS$/,
// 因为引入的eslint会对所以的匹配文件进行校验,而我们不需要对第三方库校验
exclude: /node_modules/,
// 一般一种文件,只有一个loader进行处理
// 对于一种文件被多个loader处理,使用enfore来控制调整loader的执行顺序
// pre 优先处理 normal 正常处理(默认) inline 其次处理 post 最后处理
enforce: 'pre',
loader: 'eslint-loader',
options: {
// 自动按照规则修复eslint规则
fix: true
}
},
{
// oneOf 提升构建速度,一种文件只会匹配一种loader处理
// 在oneOf中不能处理同一配置文件,需要把其中loader放置在oneOf之前,比如这里的eslint-loader
oneOf: [
// 不同的文件需要使用不同的loader进行处理,每一个配置都会生成一个style标签,这里处理css、less就会在head中插入两个style标签,
{
// 一般为正则表达式 匹配对应文件,用于loader处理
test: /\.CSS$/,
// 对于匹配中的文件,使用那些loader进行处理
// 一般的loader执行顺序是由右向左,从下往上顺序执行
use: [
// 在head中创建Style标签,并将js中的样式插入进去
'style-loader',
// 这里插件将css文件从js文件中提取出来,并通过link的方式引入,所以使用该插件自带的loader之后,就不需要使用style-loader来创建style标签引入样式了
// MiniCssExtractPlugin.loader,
// 将css文件变成commonJs模块,并引入到Js中,字符串的形式
'css-loader',
]
},
}
]
6.4 缓存
这里只是例举了babel和文件资源的缓存
babel 缓存: 直接在babel-loader配置cgcheDirtory: true即可
文件资源缓存:当文件处于强缓存期间,我们要修复一个bug,这是就可以采用修改请求资源名来获取最新数据(这里通过hash的方式来或者不同资源的文件名)
- hash: 每次webpack打包都会生成一个位于hash值 问题: js和css等使用的同一个hash,如果重新打包,会导致所以缓存都失效(可能我只改动一个文件)
- chunkhash: 根据chunk来生成hash,如果打包来源一个chunk,那个这个hash就是一样的 问题: 由于css是有js进入的,所以这里的hash也是一样
- contenthash: 根据文件内容来生成hash,不同文件就有不同的hash值
6.5 tree shaking
tree shaking: 去除无用代码
前提: 使用es6模块化,设置环境为生产模式production
作用: 减少打包体积
在package.json中配置: “sideEffect”:false 表示所以代码都没有副作用,都可以进行tree shaking,这会导致打包后可能把css等资源漏打包了
可以在"sideEffect": ["*.css"] 来配置不对css文件进行tree shaking
6.5 code split
使用webpack中的配置optimize来进行代码分割,将代码分割为不同的chunk
optimize: {
// 将node_modules中的第三方库单个打包成一个chunk输出,与我们的代码分离
// 如果多个chunk中有公共文件,会自动拆分并打包为一个chunk
/**
* 如果想自定义把一些东西单独打包为一个chunk
* 1、使用多入口,自动打包为多个chunk(目前大多都是单页面应用,所以基本不会使用这个)
* 2、使用import动态引入,不使用es6的语法,这样会把test文件单独打包为一个chunk
* 由于默认打包只会的name是打包自动生成的id,所以这里可以通过注释添加webpackChunkName来定义打包后的chunk name
// import(/*webpackChunkName: 'test'*/'./test.js')
// .then(({ 解构test暴露的方法 }) => {
// 执行方法
// }).catch((e) => {
// // 引入test文件错误
// console.error(e);
// })
splitChunk: {
chunks: 'all',
}
}