问题:
这些仅仅是前端工程化的一个缩影
当开发一个具有规模的程序,你将遇到非常多的非业务问题,这些问题包括:执行效率、兼容性、代码的可维护性可扩展性、团队协作、测试等等等等,我们将这些问题称之为工程问题。工程问题与业务无关,但它深刻的影响到开发进度,如果没有一个好的工具解决这些问题,将使得开发进度变得极其缓慢,同时也让开发者陷入技术的泥潭。
思考:上面提到的问题,为什么在node端没有那么明显,反而到了浏览器端变得如此严重呢?
答:在node端,运行的JS文件在本地,因此可以本地读取文件,它的效率比浏览器远程传输文件高的多
根本原因:在浏览器端,开发时态(devtime)和运行时态(runtime)的侧重点不一样
开发时态,devtime:
运行时态,runtime:
这种差异在小项目中表现的并不明显,可是一旦项目形成规模,就越来越明显,如果不解决这些问题,前端项目形成规模只能是空谈
既然开发时态和运行时态面临的局面有巨大的差异,因此,我们需要有一个工具,这个工具能够让开发者专心的在开发时态写代码,然后利用这个工具将开发时态编写的代码转换为运行时态需要的东西。
这样的工具,叫做构建工具

这样一来,开发者就可以专注于开发时态的代码结构,而不用担心运行时态遇到的问题了。
webpack官网:https://www.webpackjs.com/
全部东西视为模块
webpack是基于模块化的打包(构建)工具,它把一切视为模块
它通过一个开发时态的入口模块为起点,分析出所有的依赖关系,然后经过一系列的过程(压缩、合并),最终生成运行时态的文件。
webpack的特点:
理解:中间打包需要node环境,如果左边是浏览器环境,右边即浏览器;如果左边是node环境,右边即node
只打包依赖的东西
webpack通过npm安装,它提供了两个包:
安装方式:
先初始化npm init,在npm i -D webpack webpack-cli D开发依赖,而不是生产,右边要运行,右边已经构建完成,和webpack没关系了
webpack
默认情况下,webpack会以./src/index.js作为入口文件分析依赖关系,打包到./dist/main.js文件中
通过--mode选项可以控制webpack的打包结果的运行环境
npx webpack命令运行
src里面是开发时候的代码,dist里面是运行时候的代码. src 里面如果语法错误,也不报错,因为根本就不运行
打包完成的代码要在什么环境内运行?
开发环境配置:npx webpack --mode=development
生产环境配置:npx webpack --mode=production
由于webpack同时支持CommonJS和ES6 module,因此需要理解它们互操作时webpack是如何处理的
如果导出和导入使用的是同一种模块化标准,打包后的效果和之前学习的模块化没有任何差异


不同的模块化标准,webpack按照如下的方式处理


命令实现:es6导出,commonjs导入
package.json配置好
写好代码
index.js
es6a.js
运行webpack : npm run dev
es6导出,commonjs导入
common:
index
代码编写最忌讳的是精神分裂,选择一个合适的模块化标准,然后贯彻整个开发阶段。
命令npx webpack打包
通过命令给与参数:npx webpack --mode=development也能完成打包、
webpack提供的cli支持很多的参数,例如--mode,但更多的时候,我们会使用更加灵活的配置文件来控制webpack的行为
默认情况下,webpack会读取webpack.config.js文件作为配置文件,但也可以通过CLI参数--config来指定某个配置文件
命令:npx webpack --config 123.js
配置文件中通过CommonJS模块导出一个对象,不能用es6(面试题)对象中的各种属性对应不同的webpack配置
因为中间打包的过程中要读取配置文件的内容
打包的过程中要运行配置文件
src下面main.js就算有错误,打包过程也不报错,因为打包和main.js无关
注意:配置文件中的代码,必须是有效的node代码
当命令行参数与配置文件中的配置出现冲突时,以命令行参数为准。
基本配置:
在webpack.config.js里
默认./src/index.js
默认./dist/main.js
本小节的知识与 webpack 无关
前端发展到现阶段,很多时候都不会直接运行源代码,可能需要对源代码进行合并、压缩、转换等操作,真正运行的是转换后的代码

这就给调试带来了困难,因为当运行发生错误的时候,我们更加希望能看到源代码中的错误,而不是转换后代码的错误
jquery压缩后的代码:https://code.jquery.com/jquery-3.4.1.min.js
为了解决这一问题,chrome浏览器率先支持了source map,其他浏览器纷纷效仿,目前,几乎所有新版浏览器都支持了source map
source map实际上是一个配置,配置中不仅记录了所有源码内容,还记录了和转换后的代码的对应关系
下面是浏览器处理source map的原理


最佳实践:
使用 webpack 编译后的代码难以调试,可以通过 devtool 配置来优化调试体验
具体的配置见文档:https://www.webpackjs.com/configuration/devtool/
使用开发环境:
浏览器自带
eval是简易版的source map
生产环境production就不行,要想有则需要 devtool 配置
webpack 的作用是将源代码编译(构建、打包)成最终代码

整个过程大致分为三个步骤
初始化
编译
输出

此阶段,webpack会将CLI参数、配置文件、默认配置进行融合,形成一个最终的配置对象。
对配置的处理过程是依托一个第三方库yargs完成的
此阶段相对比较简单,主要是为接下来的编译阶段做必要的准备
目前,可以简单的理解为,初始化阶段主要用于产生一个最终的配置
chunk是webpack在内部构建过程中的一个概念,译为块,它表示通过某个入口找到的所有依赖的统称。
根据入口模块(默认为./src/index.js)创建一个chunk

每个chunk都有至少两个属性:


形成表格

递归加载./src/a.js

记录下来,保存到模块列表中

递归加载./src/b.js。这里加载的b是由于a依赖b,实际上b还没有加载

记录下来,保存到模块列表中

a加载完毕,继续加载index,到了b

AST在线测试工具:https://astexplorer.net/
简图

在第二步完成后,chunk中会产生一个模块列表,列表中包含了模块id和模块转换后的代码
接下来,webpack会根据配置为chunk生成一个资源列表,即chunk assets,资源列表可以理解为是生成到最终文件的文件名和文件内容

chunk hash是根据所有chunk assets的内容生成的一个hash字符串
hash:一种算法,具体有很多分类,特点是将一个任意长度的字符串转换为一个固定长度的字符串,而且可以保证原始内容不变,产生的hash字符串就不变
简图

将多个chunk的assets合并到一起,并产生一个总的hash

此步骤非常简单,webpack将利用node中的fs模块(文件处理模块),根据编译产生的总的assets,生成相应的文件。



涉及术语
module:模块,分割的代码单元,webpack中的模块可以是任何内容的文件,不仅限于JS
chunk:webpack内部构建模块的块,一个chunk中包含多个模块,这些模块是从入口模块通过依赖分析得来的
bundle:chunk构建好模块后会生成chunk的资源清单,清单中的每一项就是一个bundle,可以认为bundle就是最终生成的文件
hash:最终的资源清单所有内容联合生成的hash值
chunkhash:chunk生成的资源清单内容联合生成的hash值
chunkname:chunk的名称,如果没有配置则使用main
id:通常指chunk的唯一编号,如果在开发环境下构建,和chunkname相同;如果是生产环境下构建,则使用一个从0开始的数字进行编号

node内置模块 - path: https://nodejs.org/dist/latest-v12.x/docs/api/path.html
出口
这里的出口是针对资源列表的文件名或路径的配置
出口通过output进行配置
入口
入口真正配置的是chunk
入口通过entry进行配置
规则:
name:chunkname
hash: 总的资源hash,通常用于解决缓存问题。哈希就是根据文件内容生成出来的。内容变化,哈希变化。反之,就算文件内容有所改变,浏览器会用缓存,对于更新的文件无动于衷
chunkhash: 使用chunkhash
id: 使用chunkid,不推荐。会导致生产环境和开发环境的名字不一致。
具体情况具体分析
下面是一些经典场景

源码结构
webpack配置
这种方式适用于页面之间的功能差异巨大、公共代码较少的情况,这种情况下打包出来的最终代码不会有太多重复
面试题:打包出来的js,里面会有公共代码,这里代码的重复会造成什么影响?
不好维护。并不存在这种问题,因为写的并不是打包出来的js。自己写的并没有重复代码。
导致传输量增加。


源码结构
webpack配置
这种方式适用于页面之间有一些独立、相同的功能,专门使用一个chunk抽离这部分JS有利于浏览器更好的缓存这部分内容。
思考:为什么不使用多启动模块的方式?
所谓单页应用,是指整个网站(或网站的某一个功能块)只有一个页面,页面中的内容全部靠JS创建和控制。 vue和react都是实现单页应用的利器。

源码结构
webpack配置
webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。
更多的功能需要借助webpack loaders和webpack plugins完成。
webpack loader: loader本质上是一个函数,它的作用是将某个源码字符串转换成另一个源码字符串返回。

loader函数的将在模块解析的过程中被调用,以得到最终的源码。
全流程:

chunk中解析模块的流程:

chunk中解析模块的更详细流程:

处理loaders流程:

loader配置:
完整配置
简化配置
loader的功能定位是转换代码,而一些其他的操作难以使用loader完成,比如:
当webpack生成文件时,顺便多生成一个说明描述文件
当webpack编译启动时,控制台输出一句话表示webpack启动了
当xxxx时,xxxx
这种类似的功能需要把功能嵌入到webpack的编译流程中,而这种事情的实现是依托于plugin的

plugin的本质是一个带有apply方法的对象
通常,习惯上,我们会将该对象写成构造函数的模式
要将插件应用到webpack,需要把插件对象配置到webpack的plugins数组中,如下:
apply函数会在初始化阶段,创建好Compiler对象后运行。
compiler对象是在初始化阶段构建的,整个webpack打包期间只有一个compiler对象,后续完成打包工作的是compiler对象内部创建的compilation
apply方法会在创建好compiler对象后调用,并向方法传入一个compiler对象

compiler只有一个,compilation可能多个
compiler对象提供了大量的钩子函数(hooks,可以理解为事件),plugin的开发者可以注册这些钩子函数,参与webpack编译和生成。
你可以在apply方法中使用下面的代码注册钩子函数:
事件名称
即要监听的事件名,即钩子名,所有的钩子:https://www.webpackjs.com/api/compiler-hooks
事件类型
这一部分使用的是 Tapable API,这个小型的库是一个专门用于钩子函数监听的库。
它提供了一些事件类型:
tap:注册一个同步的钩子函数,函数运行完毕则表示事件处理结束
tapAsync:注册一个基于回调的异步的钩子函数,函数通过调用一个回调表示事件处理结束
tapPromise:注册一个基于Promise的异步的钩子函数,函数通过返回的Promise进入已决状态表示事件处理结束
处理函数
处理函数有一个事件参数compilation
有些时候,我们需要针对生产环境和开发环境分别书写webpack配置
以前可以用这种方式来区分:

为了更好的适应这种要求,webpack允许配置不仅可以是一个对象,还可以是一个函数
在开始构建时,webpack如果发现配置是一个函数,会调用该函数,将函数返回的对象作为配置内容,因此,开发者可以根据不同的环境返回不同的对象
在调用webpack函数时,webpack会向函数传入一个参数env,该参数的值来自于webpack命令中给env指定的值,例如
这样一来,我们就可以在命令中指定环境,在代码中进行判断,根据环境返回不同的配置结果。
该配置会影响入口和loaders的解析,入口和loaders的相对路径会以context的配置作为基准路径,这样,你的配置会独立于CWD(current working directory 当前执行路径)
这样一来,打包后的结果中,会将自执行函数的执行结果暴露给abc
该配置可以更加精细的控制如何暴露入口包的导出结果
其他可用的值有:
var:默认值,暴露给一个普通变量
window:暴露给window对象的一个属性
this:暴露给this的一个属性
global:暴露给global的一个属性
commonjs:暴露给exports的一个属性
其他:https://www.webpackjs.com/configuration/output/#output-librarytarget
设置打包结果最终要运行的环境,常用值有
web: 打包后的代码运行在web环境中
node:打包后的代码运行在node环境中
不解析正则表达式匹配的模块,通常用它来忽略那些大型的单模块库,以提高构建性能。和运行性能无关。
resolve的相关配置主要用于控制模块解析过程
当解析模块时,如果遇到导入语句,require("test"),webpack会从下面的位置寻找依赖的模块
当前目录下的node_modules目录
上级目录下的node_modules目录
...
当解析模块时,遇到无具体后缀的导入语句,例如<font style="color:#F5222D;">require("test")</font>,会依次测试它的后缀名
webpack自动补全后缀名,而不是node
test.js
test.json
有了alias(别名)后,导入语句中可以加入配置的键名,例如require("@/abc.js"),webpack会将其看作是require(src的绝对路径+"/abc.js")。
在大型系统中,源码结构往往比较深和复杂,别名配置可以让我们更加方便的导入依赖
从最终的bundle中排除掉配置的配置的源码,例如,入口模块是
生成的bundle是:
但有了上面的配置后,则变成了
这比较适用于一些第三方库来自于外部CDN的情况,这样一来,即可以在页面中使用CDN,又让bundle的体积变得更小,还不影响源码的编写
stats控制的是构建过程中控制台的输出内容
好东西
https://tailwindcss.com/docs文档
https://play.tailwindcss.com/?ref=producthunt
配合postcss使用的
vscode插件:
智能提示:tailwind css intellisense
查看文档:tailwind docs
https://www.tailwindcss.cn/docs
https://github.com/yjisme/boilerplate-tranditional-proj
vscode插件:Tailwind css intellisense智能提示