前端进阶:一文轻松搞定webpack基础知识、进阶与调优
写在前面
- 本文知识来源于作者对《webpack实战 入门、进阶与调优》的知识整理,为获得更好的阅读和观看体验,推荐访问我在wolai的读书笔记。webpack知识笔记
Webpack简介
-
何为webpack?
-
模块打包工具:其核心功能是解决模块之间的依赖,把各个模块按照特定的规则和顺序组织在一起,最终合并为一个或多个js文件。
-
如果在一个页面中引入多个js文件会有什么缺点?
- 需要手动维护js文件的加载顺序,如果文件之间有隐性的依赖关系,则很容易出现问题。
- 每个
script
标签都会向服务器请求一次静态资源,过多的请求会严重拖慢网页的渲染速度。 - 每个
script
标签中,顶层作用域都是全局作用域,很容易造成作用域污染和命名冲突。
模块化可以有效的解决上述问题。- 通过模块的导入导出语句可以清晰地看到模块之间的依赖关系。
- 模块可以借助打包工具,将多个资源合并后加载。
- 模块之间作用域是相互隔离的,不会存在命名冲突。
-
webpack的优势?
-
支持多种模块标准:AMD / CommonJS / ES6模块。
-
有完备的代码分割解决方案,分割打包后的资源,首屏只加载必要的部分,提升首页渲染速度。
-
可以处理各种类型的资源:css / 图片 等。
-
庞大的社区支持。
-
模块打包
-
CommonJS
-
最初为服务端设计,node.js版本。
-
每个文件即使一个文件,拥有独立的作用域。
-
导出是一个模块向外暴露自己的唯一方式。CommonJS中通过module.exports导出模块中的内容。
-
CommonJS中使用require进行模块的导入。
如果导入的模块是第一次被加载,这时会首先执行该模块,然后导出执行后的内容。
如果模块曾经被加载过,则直接导出第一次加载时执行后的内容。(相当于是一个静态值了)
-
-
ES6模块
-
每个文件作为一个模块,每个模块拥有独立的作用域。
-
通过exports导出
命名导出:exports { a, b }
默认导出:exports default a; (只能导出一个对象) -
通过**import **导入,默认导出的变量,导入时可以随意命名,命名导出方式,导入时名称必须一致,可以使用as 重命名。
-
-
AMD
AMD 与CommonJS以及ES6模块的最大区别在于AMD的模块加载方式是异步的。
-
AMD 中使用define函数来定义模块,使用require来引用模块。
-
模块可以并行加载,并不会阻塞浏览器。
-
缺点:语法冗长,回调地狱
-
-
UMD
-
并非模块标准,而是一组模块形式的集合,目标是使一个模块能够运行在各种环境。其手段是根据当前的全局对象中的值判断处于那种环境。当前是AMD环境,就以AMD的形式导出,当前是CommonJS就已CommonJS的形式导出。
-
UMD一般先判断是否AMD环境。
-
-
CommonJS 与ES6模块的区别
CommonJS 对模块依赖的解决是动态的,而ES6模块是静态的。
模块导入时:CommonJS是值拷贝,而ES6则是只读的动态映射。动态:模块的依赖关系建立在代码运行阶段
静态:模块的依赖关系建立在代码编译阶段- CommonJS引入模块时可以动态指定,例如使用if等条件语句引入不同的模块。
-
ES6模块相比CommonJS的优势
-
死代码检测和排除:通过静态分析工具检测出哪些模块没有被调用过。从而在打包时去掉未使用的模块,以减少资源包的体积。
-
模块变量类型检查:JS是动态类型语言,不会在代码执行前检查类型错误,ES6模块属于静态类型模块,有助于确保模块之间的传递的值或者接口类型是正确的。
-
编译器优化:CommonJS无论采用哪种方式,导入的都是一个对象,而ES6模块直接导入变量,减少应用层级,程序效率更高。
-
-
值拷贝与动态映射
-
在导入一个模块时,CommonJS导入的是一份导出值得拷贝,允许对导入的值进行修改。
-
ES6导出的则是值得动态映射,且该值是只读的。(一改全改,但只能在模块内部改动)
-
资源输入输出
-
webpack 资源入口的作用
-
确定入口模块位置,告诉webpack从哪里开始打包
-
定义chunk name 。 如果只有一个入口,那么默认为“main”,如果有多个入口,则需要为每个入口定义chunk name 作为唯一标识。
-
-
context 的作用
- 资源入口的路径前缀,在配置时必须使用绝对路径的形式。使得entry的配置更加简洁。context可以省略,默认为当前项目的根目录。
-
如何配置entry
-
字符串类型入口:
entry:'./src/index.js'
-
数组类型入口:
entry:['babel-polyfill','./src/index.js']
( 多个资源预先合并,数组的最后一个元素作为实际的入口路径 ) -
对象类型入口:如果要定义多入口,则必须使用对象的形式.对象属性名是**chunk name **, 对象的属性值是 入口路径
entry:{ index:['babel-polyfill','./src/index.js'], lib:'./src/lib.js' }
-
函数类型入口: 返回上述任意一种类型即可. ( 使用函数的优点在于可以设置动态的逻辑来获取工程入口,同时函数支持返回一个Promise对象来进行异步操作 )
-
-
如何配置资源出口:output对象
-
filename: 控制输出资源的文件名,可以是一个相对路径.
模板变量:用于动态的为filename 命名.
作用1: 当有多个chunk存在时对不同的chunk进行区分 . 如[name]/ [chunkname] / [id] 对每个chunk来说都是不同的.
作用2: 控制客户端缓存,chunk改变或引起资源的重新加载,从而获取最新内容.
-
变量名称 | 功能描述 |
[hash] | 指代webpack此次打包所有资源生成的hash |
[chunkhash] | 指代当前chunk内容的hash |
[id] | 指代当前chunk的id |
[query] | 指代filename配置项中的query |
[name] | 指代chunk name ,最常用 |
-
path: 指定资源输出的位置,要求值必须为绝对路径,webpack4之后默认为dist目录
-
❗publicPath: 指定资源的请求位置,注意与path区分.
> path: 资源的输出位置:是打包完成后资源产生的目录.通常为dist目录
publicPath: 资源的请求位置:指定间接资源的请求位置.
路径 | 说明 | 示例 |
---|---|---|
HTML相关 | 以当前页面HTML 所在的路径加上相对路径,构成资源请求的实际url | // 假定当前HTML页面的路径为https://exmple.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath:"" // 实际路径为 https://exmple.com/app/0.chunk.js publicPath:"./js" // 实际路径为 https://exmple.com/app/js/0.chunk.js publicPath:"…/assets/" // 实际路径为 https://exmple.com/assets/0.chunk.js |
Host相关 | 若当前publicPath以/开始,则表示以当前host name 为基础路径 | // 假定当前HTML页面的路径为https://exmple.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath:"/" // 实际路径为 https://exmple.com/0.chunk.js publicPath:"/js/" // 实际路径为 https://exmple.com/js/0.chunk.js publicPath:"/assets/" // 实际路径为 https://exmple.com/assets/0.chunk.js |
CDN相关 | 使用绝对路径配置publicPath. | // 假定当前HTML页面的路径为https://exmple.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath:“http://cdn.com” // 实际路径为 http://cdn.com/0.chunk.js |
-
示例:
-
html 示例
// 假定当前HTML页面的路径为https://exmple.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath:"" // 实际路径为 https://exmple.com/app/0.chunk.js publicPath:"./js" // 实际路径为 https://exmple.com/app/js/0.chunk.js publicPath:"../assets/" // 实际路径为 https://exmple.com/assets/0.chunk.js
-
Host示例
// 假定当前HTML页面的路径为https://exmple.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath:"/" // 实际路径为 https://exmple.com/0.chunk.js publicPath:"/js/" // 实际路径为 https://exmple.com/js/0.chunk.js publicPath:"/assets/" // 实际路径为 https://exmple.com/assets/0.chunk.js
-
CDN 相关
// 假定当前HTML页面的路径为https://exmple.com/app/index.html // 异步加载的资源名为 0.chunk.js publicPath:"http://cdn.com" // 实际路径为 http://cdn.com/0.chunk.js
-
预处理器
-
loader 概述
- loader 本质上是一个函数
-
如何引入loader?
-
modules.rules代表了模块的处理规则.
// 所有css文件都用css-loader/style-loader处理处理 module:{ rules:[{ text:/\.css$/, use:['style-loader','css-loader'] //先使用css-loader,然后再用style-loader处理,即从右往左处理 }] }
-
-
loader的常用配置
-
exclude: 用来排除指定的内容,可以使用字符串或者正则,优先级比include高
-
include:用来包含指定的内容,只能使用正则.
-
issuer: 指定模块的加载者
-
enforce: 指定loader的执行顺序,默认为normal.
-
类型 | 说明 |
---|---|
pre | 在所有loader之前执行 |
post | 在所有loader之后执行 |
-
babel-loader
-
用途:用来将ES6+代码转换为ES5,使得我们可以在编码中使用最新的特效而不必担心平台兼容性问题。
-
安装:
npm install babel-loader @babel/core @babel/preset-env
-
babel-loader:使Babel与webpack协同工作的模块
-
@babel/core:Babel编译器的核心代码
-
@babel/preset-env: Babel官方推荐的预置器,可以根据用户设置的目标环境自动添加所需要的插件和补丁来编译ES6代码。
-
-
注意事项:
-
通过exclude 排除对node_modules的编译
-
使用缓存,避免重复编译
-
禁用模块化转化(不警用会将ES6模块转化为CommonJS。这将导致Webpack中的tree-shaking特性无法工作)
-
-
// babel 的配置示例
rules:{
test:/\.js$/, // 匹配所有的js
exclude:/node_modules/, // 忽略node_modules
use:{
laoder:"babel-loader",// 指定编译器
options:{
cacheDirectory:true,// 开启缓存
presets:[
'env',
{modules:fasle} // 禁用模块转化
]
}
}
}
代码分片
-
代码分片作用?
实现代码的按需加载,提升首屏渲染速度。
-
开发过程减少模块的重复打包,提升开发速度。
-
减少整体资源的体积。
-
分片后的代码可以更好的利用客户端缓存。
-
-
通过入口划分代码。
将一些通用的库和不常变动的工具放到一个单独的入口中,由于资源不常变化,因此可以有效的利用缓存,减少资源请求。
-
CommonsChunkPlugin
webpack 4 之前的版本可用,webpack 4 之后的版本改用SplitChunks
-
提取Vendor: 将Vue/react等框架代码提取出来。
-
设置提取范围:通过chunks配置入口模块。
-
设置提取规则:通过minChunks配置提取规则。
// 配置案例 const webpack = require('webpack'); module.exports = { entry:{ app:'./app.js', vendor:['react'], }, output:{ filename:'[name].js', }, plugins:[ new webpack.optimize.commonsChunkPlugin({ name:"vendor", // 指定公共chunk的名字 filename:"vendor.js", // 指定提后后的资源文件名 chunks:['a','b'], // 设置提取范围 minChunks: 3, // 只有该模块被引入3次才会被提取为公共模块 }) ] }
-
-
❗SplitChunks
-
使用异步加载/按需加载
-
延时加载暂时用不到的模块。
-
webpack中延时加载的两种方式: import函数(推荐)和require.ensure。
-
import:通过js在页面的head标签中动态插入一个script标签,从而实现动态加载的效果。
// webpack 中import函数的使用方法,注意和ES6模块的import语法做区分 import('./bar.js').then(({add})=>{ console.log(add(2,3)); })
-
生产环境配置
-
如何让webpack根据不同的环境采用不同的配置?
-
使用相同的配置文件:在构建开始前将当前所属的环境作为一个变量传递进去,然后再webpack.config.js中通过条件判断语句使用不同的配置
// package.json { ... "script":{ "dev":"ENV=development webpack-dev-server", "build":"ENV=production webpack" } }
// webpack.config.js const ENV = process.env.ENV; const isProd = ENV==='production'; module.exports = { output:{ filename:isProd?'bundle@[chunkhase].js':'bundle.js' }, mode:ENV }
-
为不同的环境创建不同的配置文件:将配置新进不同的配置文件中,根据环境加载对应的配置文件。
// 开发环境配置: webpack.dev.config.js // 生产环境配置: webpack.pro.config.js // package.json { ... // 通过--congig 读取不同的配置文件 "scripts":{ "dev":"webpack-dev-server --config=webpack.dev.config.js", "build":"webpack --config= webpack.pro.config.js" } }
-
-
如何开启production模式?
webpack 4 以上提供了**mode **参数,可以通过它直接切换打包模式。
-
如何为生产环境和本地环境添加不同的环境变量?
webpack中可以使用DefinePlugin进行设置
// webpack.config.js const webpack = require('webpack'); module.exports={ entry:'./app.js', output:{ filename:'bundle.js' }, mode:'production', plugins:[ new webpack.DefinePlugin({ ENV:JSON.stringfy('prodution') }) ] }
上述代码中,必须使用
JSON.stringfy
,因为替换环境变量时是对字符串类型的值进行完全替换。加入不使用JSON.stringfy
,在替换后就会成为变量名而不是字符串。 -
source-map
-
源码映射,帮助调试和定位错误,通常后缀
.map
-
打开浏览器开发者工具时就会加载对应的源码文件,不打开则不加载
- webpack 如何配置source map
module.exports = { ... devtool:"source-map" // 开启源码视图 }
-
-
JS资源压缩
-
JS压缩工具:UglifyJS (webpack 3 已集成) / terser (webpack 4 已集成)
// webpack 4 配置压缩 module.exports = { entry:"./app.js", output:{ filename:"bundle.js", }, optimization:{ minimize:true // 启用压缩 } }
-
-
CSS资源压缩
-
压缩css的步骤:压缩css的前提是使用 extract-text-webpack-plugin / mini-css-extract-plugin 提取样式,然后使用
optimize-css-assets-webapck-plugin
进行压缩 。const ExtractTextPlugin = require('extract-text-webpack-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webapck-plugin'); module.exports = { module:{ rules:[ { test:/\.css$/, use:ExtractTextPlugin.extract({ fallback:'style-loader', user:'css-loader', }) } ] }, plugins:[new OptimizeCssAssetsPlugin({ assetNameRegExp:/\.optimize\.css$/, // 生效范围 cssProcessor:require('cssnano'), // 压缩处理器,默认为cssnano cssProcessorOptions:{ // 压缩处理器的配置 discardComments:{ remove:all } }, canPrint:true // 使是否显示log })] }
-
参考文章:
本文首发于G众号"前端知识营地",点击关注,获取更多优质有趣的内容。
(完)