webpack 相关
output
path/publicPath
path 代表发布文件的根目录,比如path下的资源, 这里必须是绝对路径。
output/
- a.js
- b.html
publicPath 设置的是 html 和文件 和静态资源文件的位置关系。默认情况下,静态资源和html在同一个目录下,因此静态资源文件采用相对路径引用,此时publicPath值为/
一般情况下,代码部署到服务器时,都是动静分离的,即静态资源部署到一个服务器,html部署到另一个服务器。这时,html里引用的相对路径就失效了。
而publicPath的作用就是在打包的时候,给这个相对路径(/static/a.js)的前面加上其他路径,可以是完整的网络路径(CDN)也可以是新的相对路径(比如同一个服务器,动静资源的目录位置发生变化)
publicPath 是指资源文件的根路径,webpack 往 html 注入静态资源,默认是从根路径下寻找资源(/xxx),与html自身的位置没有关系。
entry
entry: { chunk: String|Array }
String 代表一个入口文件的路径,Array可以指定多个文件入口打进一个chunk中
省略模式:
entry: String|Array
这种写法相当于 chunk 默认为 main,相当于:
entry{ main: String|Array }
entry path 可以配置成文件名或者文件路径,对于路径,webpack会在项目根目录(npm执行路径)下查找,对于名字,会直接到node_modules下去查找。
比如:vendor: ['jquery'] | 'jquery'
这句话只是单独为jquery打了一个包,生成了一个独立的 chunk,并没有执行提取操作。
要把jquery提取出来,还需要配置插件,把该chunk作为chunk的配置项才能提取。
提取plugin 的 name 和 entry 的 name 没有任何关联关系。chunks 字段 才和 entry的chunk有关系,默认是扫描entry中的所有chunk
特殊情况:如果和entry的名字相同,webpack就把entry的chunk和这个commons合并
如果a文件引用了jquery,另外在verder: 'jquery', plugins也可以提取jquery,这是因为jquery在 entry里的chunk中出现了两次,所以plugins才会提取,如果配置成3就不会。
webpack 打包以entry chunk作为分割点,对该入口下的文件先通过配置的modules/rules规则去解析(vue/es6/less等等)
然后把这些相关文件打入一个“块”中,这个“块”至少包含一个js文件,还有可能包含css/png文件(如果提取的话),这些文件都隶属于这个chunk
打包完成后,整个项目被分成了几大“块”,每一块都有一个chunk名字,里面包含js/css/png
最后通过htmlplugin插件把所有的“chunk”都注入到这个html页面中
这个插件可以配置chunks, 这个可以指定哪些chunk注入html页面中。
注意,当你选择了某个chunk,那么它里面包含的js和css都会同时注入到html页面:
- css的块隶属于js所在的chunk,因此会和js一起注入,其中css会被注入到 html 的 head 中,js被注入到body后。
- 图片资源一般是css文件引用的,因此还是在css文件里引用,只做路径替换。
resolve
resolve: { alias: { 'vue$': 'vue/dist/esm.vue.js', // 匹配 node_modules/vue/dist/esm.vue.js '@': __dirname + 'src/' // 用./src 替换导入语句中的@标识 } , extensions: ['js', 'jsx', 'json'], enforceExtension: true, enforceModuleExtension: false mainFileds: ['browers', 'module', 'main'] modules: ['node_modules'] descriptionFiles: ['package.json'] }
resolve 主要处理 import require 导入语句的路径问题
alias 指定一个别名,用于替换导入语句的某个name值,比如 import '@/reset.less' @就会被替换成 './src'
vue$ 表示精准匹配,即导入语句里只有 vue 才会被匹配到。也就是专门匹配 import Vue from 'vue'
extensions: 当导入文件没有带后缀的时候,webpack 按照列出的扩展名依次匹配查找,一旦匹配到就返回,都没有匹配到就报错
enforceExtension 强制所有的导入语句都带扩展
enforceModuleExtension 强制modules里的导入语句都带扩展,当enforceExtension 设置true的时候,该字段设置为false 来进行兼容
mainFileds webpack 在解析第三方模块时,默认会依次从package.json 里的 browers、module、main字段读取导入的文件路径,package 一般只配置main
modules: webpack 在解析第三方模块时搜索的目录,默认是 node_modules
descriptionFiles: 指定解析第三方模块时的模块描述文件,即 package.json
noParse
这是module中的一个属性,作用:不去解析属性值代表的库的依赖
防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有
import
,require
,define
的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。
我们对类似jq这类依赖库,一般会认为不会引用其他的包(特殊除外,自行判断)。所以,对于这类不引用其他的包的库,我们在打包的时候就没有必要去解析,这样能够增加打包速率。
{ module:{ noParse: /jquery|lodash/, //接收参数 正则表达式 或函数 noParse:function(contentPath){ return /jquery|lodash/.test(contentPath); }, rules: [] } }
externals 、 library、libraryTarget 的区别
externals 可以指某些模块不进行打包,从外部环境加载。 但在打包时,只为该模块生成一个引用模块,对应的包需要开发者从script方式挂载到全局。
这个和公共模块提取不冲突,该引用模块也可以通过公共模块来提取,只是提取的公共模块只是一个引用,不包括实际的内容。
function(module, exports) { module.exports = jQuery/ require('jQuery') / define(['jQuery'], fn) }
这个模块作为webpack模块被正常打包,只是真是内容从外部引用。
外部环境如何引用,取决于 libraryTarget
libraryTarget 用以指定模块的打包类型,默认是 var,常用的有 amd/umd/commonjs 等等,library 用于指定导出的模块挂载到对象的名字。
不配置 libraryTarget 和 library 则已常规的打包输入,不做任何导出,当配置 libraryTarget 为 var 且不配置 library时,libraryTarget 的配置无效。
因此,
如果指定的 libraryTarget 为 var 时,外部环境就是window.xxx,
如果是 commonjs,则外部环境为 require('xxxx'),
如果是 amd, 则外部环境是 define(['jQuery'], function() {})
webpack中的chunk 有三种方式生成:
1、entry chunk
2、commons chunk
3、code split/ children chunk
如果不设置children,异步chunk里的common不会被分离,直接和每个chunk打包在一起,设置为true,则会对common进行提取。
如何提取公共模块,取决于async的取值:
如果设置 async=false ,则会把common部分打包到parent chunk 中。在name字段指定从哪个chunk中 提取 children chunk的 commons。
如果设置 async=true,则表示该common需要异步加载,则单独打到一个文件中,文件命名使用chunkFilename中配置的模板,[name]部分替换为数字序号(0,1,2)
如果设置 async=string,则文件命名使用chunkFilename中配置的模板,[name]部分替换为async的取值
commons chunk 是以文件为单位进行提取(就是以require、import 作为分割点),而不是以某个代码块为单位提取。(公共代码片段已测试)
在 commons chunk 中,name 代表的是 chunk的名字,如果这个name 在entry里存在,就把公共文件合并到entry里,如果不存在,则创新一个新的chunk,命名为 name
chunks 代表可以从哪些地方提取,默认从entry中提取
name: [a,b] 表示,先提取a,然后在从a中提取b
minChunks:
- 数字 默认值和entry chunk的数量有关, 如果一个模块(文件)minChunks 个 chunk 都引用(import) 过,就把该文件提取为公共模块。同一个chunk里引用多次也算一次。 只要被 import 的文件就算是引用过。(import 多次测试)
- 函数 可以指定 特定的条件(module.source, count) count 是module.resource 文件被引用的chunk数量
- Initnity: 通过这个参数可以把webpack的runtime模块从common中分离出来。 runtime 里维护了chunk的hash ,因此当某个文件内容发生了变化,该runtime就会变,因此会导致common的hash发生变化。
⚠️minChunks 的默认值和 entry chunk 的数量有关。
- 如果entry 只有1个chunk,则默认值是2
- 如果entry 只有2个chunk,则默认值是2
- 如果 entry 有 3 个chunk,那么默认是3
minChunks = max(2, entryChunks.length)
因此当entry chunk 大于2的时候,需要手动指定 minChunks
webpack
- entry: 定义从哪里开始打包、打几个块
- output:打包好的文件放在哪里
- reslove: 全局变量命名
- externals: 指定某个模块不打包,从外部环境加载
- modules: 文件解析时的规则,css 提取时的[name]就是它所在的chunk 名字,而 png生成时指定的[name]是它图片自己的名字
- plugins:定义全局变量、提取公共js/css、压缩、合成html
hash/chunkhash/contenthash
- hash: 所有文件内容的校验值,只要有文件变化,hash就会不同。
- chunkhash: 当前chunk所包含的文件内容的校验值,该chunk里有文件更新,hash就会不同。
- contenthash: 当前文件内容的校验值,只有当前文件内容变化hash才会不同。
其应用场景是:chunk 分离出来的css/ img文件等,当js发生变化,css 和img没有变化时,仍然希望缓存css文件就用contenthash
extract-text-webpack-plugin
该插件提取的不是公共模块,而是从 js/html 中分离css模块,因此与minChunks没关系。
css 的提取不仅仅是以(@)import为分割点,还可以是内联style样式。 这个和 js提取公共模块不是一回事。
- allChunks: false
默认情况下,该插件会以 chunk 为单元提取 entry chunk 和 commons chunk 里的样式信息,然后会为每个chunk 分别生成一个css文件,此处的[name]指的是chunk的名字。
提取规则:
- 有几个chunk,就会生成几个css文件,如果每个chunk里有css定义。
- 对于chunk的提取包括 js中import 引入的css、style中@import的css文件、vue内联样式。
- 默认不提取 children chunk 里的样式,无论是否开启async模式。
- 设置 allChunks: true,该插件会按照正常的提取规则,把 children chunk 里的样式全部提取出来,合并到 parent chunk 对应的css文件中,而不是单独生成一个css文件。
loaders
主要变化:
- loaders 改成 rules
- 不支持缩写 a!b?c
- query改成options
- 增加use,支持多loader的情况
webpack1中
module: { loaders: [ { test: '.less$', loader: 'style!css!less?query' } ] }
webpack2中
module: { rules: [ { test: '.less$', use: [ 'style-loader', 'css-loader', 'less-loader' ] }, { test: '.less$', use: ExtractTextPlugin.extract({ use: [ 'css-loader', 'less-loader' ], fallback: 'style-loader' }) } ] }
loader 里的include 和exclude
loader 解析对应文件的范围不固定,只要entry里引用对应的资源,就会进行解析,哪怕该资源来自于node_modules。
exclude 的作用就是忽略特定文件的解析,比如node_modules下的文件就不需要babel来解析。
include 的作用是只解析特定文件。没指定的不解析。
exclude/include 是对待解析文件路径做过滤,可以是正则,也可以是完整路径。
webpack工作机制
webpack 的工作机制分「编译」和「运行时」两个阶段。
编译
- 解析文件,应用loaders
- 每个模块用闭包包裹,加载到内存中
- 分析模块之间的依赖关系
- 按照entry chunk、commoms chunk 和 children chunks 定义的chunks 对模块进行打包,并存储到以chunk name 命名的文件中
- 把这些文件整理到发布目录下
- 最后把这些打包好的chunks在注入到html模块,并输出html文件
运行时
- webpack 在运行时维护了一个modules数组,用于保存所有的modules 。
- 当chunk文件被载入时会立即执行脚本
- 首先会调用运行时的函数来注册该chunk,同步模块则以chunkId为键,0为值,保存到installedChunks 中。0表示该chunk已经成功加载。
- 然后把该chunk里包含的modules 保存进运行时的modules 中
- 调用webpack_require加载指定的module
- 如果该module里使用了require.ensure异步请求模块
- 则请求运行时的webpack_require.e函数
- 首先会定义一个promise,然后把reslove和reject 作为值,chunkId作为键,注册到installedChunks中。代表该chunk正在加载中。
- 创建脚本,把chunk文件的路径作为src,然后定义onload onerror 回调函数,最后将脚本插入html中进行加载
- 返回promise
当脚本被载入成功后,会立即执行该chunk脚本,此时onload回调会被放入异步队列中。
chunk脚本里的结构和普通chunk一样,也是先去runtime 中注册chunk,
首先检查installedChunk 里有没有promise回调,如果有的话,就把resolve放入全局resloves中,然后把 installedChunks 中的值设置为0,代表该chunk加载成功
然后把该chunk中的modules加到runtime的modules中,最后执行 resolves中的reslove回调,此时执行流进入then的回调,然后再通过webpack_require 从 modules中加载对应的module。
执行完同步代码后,onload会被执行,加载成功的情况下不做任何操作。
当下次加载同一个chunk时,installedChunk 是否为0 ,如果为0 则 直接resolve一个promise返回给ensure,代表该chunk已经被加载,可以直接加载里面的模块了
如果 installedChunk 值为 true,则说明该chunk正在加载中,还没返回,直接把上次的promise返回给ensure,当脚本返回时,会同时执行每个ensure的then方法,避免重复请求该脚本。
当脚本加载失败的情况下,直接回调onload, 判断installedChunk.chunkId !=0 , 调用installedChunkData中缓存的promise中的reject 抛出异常。将installedChunk设置为undefined,代表此次加载已经结束但未成功,下次可以重新加载
分包总结:
webpack 的 分包质上没有做到按需加载,而是一次性载入所有的脚本,只是分成了多个小块同时加载,每个chunk加载完成后,会主动把自己所包含的模块注册到runtime的modules中去,然后在需要时通过webpack_require 去load 该module
而真正做到按需加载的是 require.ensure,它的作用是在执行到这一句的时候,才去请求这个文件,然后通过promise 获取这个chunk,当resolve之后,说明该包被注册到了runtime运行时,此时就可以正常的 webpack_require 了。
babel-polyfill/babel-plugin-transform-runtime/babel-runtime/es2015/stage-2
- babal-polyfill 转换es6 api
-babel-plugin-transform-runtime 和 babel-runtime 一起使用,前者抽离babel定义的重复变量,后者是存这些变量的包:
当你使用 generators/async 函数时,自动引入 babel-runtime/regenerator (使用 regenerator 运行时而不会污染当前环境) 。
自动引入 babel-runtime/core-js 并映射 ES6 静态方法和内置插件(实现polyfill的功能且无全局污染,但是实例方法无法正常使用,如 “foobar”.includes(“foo”) )。
移除内联的 Babel helper 并使用模块 babel-runtime/helpers 代替(提取babel转换语法的代码)。
- es2015 就是支持es6
- stage 支持es7的新语法
- env babel preset 将基于你的实际浏览器及运行环境,自动的确定babel插件及polyfills,转译ES2015及此版本以上的语言,在没有配置项的情况下,babel-preset-env表现的同babel-preset-latest一样(或者可以说同babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017结合到一起,表现的一致)
{ "presets": [ ["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] } }] ] }
总结: babel-loader 通知 babel-core工作,在 babel-core 中调用各种插件:
1、transform 和 babel-runtime一起工作抽离公共变量
2、presets 定义的是如何将代码转成指定的版本:
- es2015 就是es6,
- stage-2 就是es7
- env 则是根据浏览器的实际情况进行转换,而不是全量转换
loader:
1、postcss-loader
用于css添加前缀,注意 编写css时要携程默认不带前缀的,比如 display: flex 不能写成 -webkit-box,这个loader 不能单独工作,需要插件来配合。autoprefixer 就是实际添加前缀的插件
这个插件可以传一个对象参数,里面可以定义 browsers: ['last 2 version'] 根据 can i use 来设置要兼容的浏览器版本。 如果没有设置这个参数,autoprefixer 会读取 .browserslistrc 配置文件
browserlist 可以单独创建一份兼容性的配置文件来共享这些配置,autoprefixer 和 preset-env 默认都会读取这个配置
2、babel-loader
总共有三个部分:
- 解析 生成语法树 ast
- 转换 生成新的语法树
- 生成 新的es5代码
loader的第2步需要插件来配合才能完成, 如果不给loader 配置插件,则loader 不会做任何转换,原样输出
es6 转 es5 有两件事要做:分别是语法转化 和 api 转换,分别对应 preset 和 plugins
preset 是 webpack 预设的转换插件,用于将es6 es7 转换成es6, 对应的插件有:babel-preset-es2015、babel-preset-es2016、babel-preset-es2017 以及babel-stage-0123
这几个插件只转换对应年份确定的规范
还有一个babel-preset-env,将基于你的实际浏览器及运行环境,自动的确定babel插件及polyfills,转译ES2015及此版本以上的语言,在没有配置项的情况下,babel-preset-env表现的同babel-preset-latest一样(或者可以说同babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017结合到一起,表现的一致)
同样 env 可以配置options来设置你要兼容的浏览器列表,如果不配置,就读取 .browserslistrc 配置项
env 只处理已确定的规范(2015-2016-2017),对于草案中的 babel-stage-0123 的语法需要单独的插件来处理。
stage0 包括 1 2 3 里面的能力
stage1 包括 2 3 里的能力
stage 包括 3 的能力
stage3 里有aync await
语言规范: https://blog.csdn.net/Sourcemyx/article/details/79126016
api 转换 由于 babel-plugin-transform-runtime 和 babel-runtime 来完成,它主要做了3件事:
- polyfill 转换 新增的原生对象 和静态方法,比如 Promise/Set/Symbol ,它不会污染全局变量,直接调用 babel-runtime/core-js 的 api 来完成对应的功能。 但不会转换实例方法,因为实例不存在了。
- regenerator 转换 generator 和 async ,会调用 babel-runtime/regenerator 来实现。
- helpers babel 会为每个文件顶部生成helper函数,它的作用是移除内联的 Babel helper 并使用模块 babel-runtime/helpers 代替(提取babel转换语法的代码)。
也就是说 babel-runtime 由 core-js regenerator 和 helpers 构成。
babel-polyfill / es6-promise
babel-polyfill 为ES6新API 提供原生环境,也就是说在window挂载新的api,比如 Set/Proxy/Symbol, 这种会污染全局对象,因此不适用于 library 和 插件。
es6-promise 仅提供 Promise 的原生环境, 小于 babel-polyfill 的作用范围。
正常情况下通过transform-runtime 转化就可以处理新api了,但由于 webpack运行时需要 Promise 对象 ,因此会引入这个polyfill
file-loader 和 url-loader
url-loader 工作分两种情况:
1.文件大小小于limit参数,url-loader 将会把文件转为 DataURL;
2.文件大小大于limit,url-loader 会调用 file-loader 进行处理,参数也会直接传给file-loader。此时需要依赖 file-loader 因此,也需要安装file-loader
html-webpac-plugin
template: 是根目录
filename: 发布目录的根目录
chunkSort 就是对chunks里指定的chunk进行依赖排序。
htmlPlugin 的template默认支持ejs 语法,因此可以在template引用plugin里指定的变量。
但如果全局引用了html-loader导致模版的ejs语法失效
process.env
Node 中process.env 获取系统环境变量
这里的环境变量只能在node环境里获取到,在js环境里这些变量不存在,因此需要在webpack里定义全局变量做替换,html模版无法引用这些全局变量。
ProvidePlugin定义的变量可以在JS中直接引用,process.env 不是必须的,而是表明该变量是环境变量,属于多层变量
package.json bin 会在node_modules/.bin下建立软链,在shell环境下直接可以调用
main 是require 导入的入口文件,
这两者并存,解决不同的问题
npm run 会创建一个shell,然后把.bin的命令挂在到PATH环境变量下,可以直接调用对应的npm包
npm install xxx 会先安装对应的包,然后再执行这个包的package 里定义的install 命令,这样做的好处是用户在安装时就能执行一些任务。
install 是npm 内置的钩子命令之一,执行完npm install后会触发该命令执行,类似的还有publish uninstall prestop 等等
eslint: parser 把文件代码解析成ast
plugin: 定义新的规则集,eslint没有涉及到的规则,比如eslint-plugin-react
插件一般以eslint-plugin- 开头,在配置时可以省略前缀
extends 允许多个配置文件进行merge,它是在已有的插件基础上做了自定义配置,而不是定义新的规则。本身的结构就是一个配置文件,当前配置覆盖extends里的配置。多个extends下面的覆盖上面的。一般以eslint-config-开头,可以省略前缀
stage0 初始,stage 1 议案stage 2 草案stage 3 候选,stage4 完成即将写入规范
stage 4 代表已经通过,即将写入最新规范中。
从es2015开始,每年一个版本,从es7以后每个版本内容很少,es10 已经发布,目前es2020 es11 目前还在草案中,2020年正式发布