一. 工程化的意义
- 工程化:前端开发的管理工具
- 宏观上:把各个层面的管理工具汇集到一起,进行有效的组织的过程就是前端工程化;
- 微观上:通过各种开发管理工具的整合,保证所有开发人员能够按照既定的规则和流程做开发;
- 降低开发成本,提升开发效率;
二. 模块化和包管理
-
模块化
- 将处理事物的逻辑细化拆解,在使用时再聚合,像函数的抽象,在真正处理逻辑时再进行聚合
- 在模块化上,文件和函数要达成的目标是一致的,都需要将同类逻辑拆解,再聚合;在没有引入模块化的情况下,最开始前端在将代码分文件保存的时候出现了全局污染和依赖混乱的问题,当一个html文件引入了多个js文件,js文件会互相污染全局,会有变量的覆盖,会造成依赖的混乱;
- 当提出了模块化标准后,会解决上述的问题,而模块化也有不同的标准:
-
CommonJs(CJS)
-
AMD
-
CMD
-
UMD
-
Ecmascript Module(ESM)-官方模块化标准
-
目前最主流的两种就是CJS(社区)和ESM(官方),下面是关于两种模块化方案的对比
CJS ESM 标准来源 社区 官方 实现方式 运行时 编译时 语法 使用 require函数导入模块,使用module.exports或exports导出模块使用 import关键字导入模块,使用export关键字导出模块模块加载 同步加载,模块加载会阻塞代码执行 支持异步加载,可以在编译时进行静态分析 模块作用域 每个模块都有自己的作用域,模块中的变量和函数不会污染全局作用域 模块中的变量和函数默认不会污染全局作用域,但可以通过 export关键字暴露给其他模块模块缓存 模块会被缓存,多次 require同一个模块只会执行一次模块也会被缓存,但缓存机制与 CJS 不同 兼容性 广泛支持,Node.js 默认使用 CJS 较新的标准,需要较新的浏览器或 Node.js 版本支持 动态导入 支持动态导入 支持动态导入,可以在运行时加载模块
-
-
包管理
- 包
package,一个系列模块的集合 - 怎么对包进行管理?下载,升级,卸载,发布,版本控制?
- npm:对包进行管理
- pnpm:work space(可以做monorepo项目)
- cnpm
- yarn
- 包
-
函数封装,模块化和包管理,都是为了对逻辑进行拆解和聚合,有这些解决方案,前端就具备了应对各种复杂项目的能力;
三. 工具链
当有了模块化,包管理后,前端语言本身对于应对大型问题的能力还有欠缺,所以社区有很多解决办法:
- html-通过了一些工具增强了html语法,例如
haml,或是vue,react这种库和框架帮我们做了很多复杂工作; - css-通过预编译语言来增强css,例如
sass/less/stylus,postcss,tailwindcss,css-in-js,styled-component等等; - js-
jsx,ts和一系列转换工具;
以上的方案,用到了很多不同的方法来弥补语言缺陷,下面介绍一下这些方法和工具:
-
js
- 兼容问题
- api兼容 polyfill:当版本之间存在差异,则尽量在版本间补全缺失的api,代表性的库core-js
- 语法兼容 syntax transformer(运行时):当有语法想要兼容,需要安装相应的工具来转换,例如async,await这种语法,需要用
regenerater来转换,通过regenerater的转换方法,可以把文件中的async,await这种语法,转换成兼容性更强的写法。
- 语言增强
- 语言增强: 语法兼容的方式,可不可以用作语法增强呢?答案是可以的,当我编写一个转换
jsx语法的工具,我就可以用jsx来开发,这样大幅度的增强js的功能,在编译时,通过工具来变异成为兼容的语法,同理Typescript也一样,只要提供相应的转换工具,就可以转换成能够运行的js:tsc。
- 语言增强: 语法兼容的方式,可不可以用作语法增强呢?答案是可以的,当我编写一个转换
- 工具链:
- 市面上有各种各样的转换工具,完成了各种目标的转换,在使用时我要把它们整合到一起吗?答案是不需要,因为这种集成工具已经存在,像
babel,SWC等等,将语言解析为抽象语法树AST,再将抽象语法树,通过插件中的各种规则,最后生成目标的js代码;
- 市面上有各种各样的转换工具,完成了各种目标的转换,在使用时我要把它们整合到一起吗?答案是不需要,因为这种集成工具已经存在,像
- 兼容问题
-
css
-
语言问题
- 语法缺失:css本身不具有循环,判断,拼接等语法,在复杂样式开发上受限
- 功能缺失:没有颜色函数,数学函数,自定义函数等功能
-
解决方案:
- css预编译器:
sass,less等预编译器,使用特殊的语法开发样式,通过预编译器把代码转换为纯css的代码; - css后处理器:使用预编译器可以把复杂代码简单化,编译成css代码后还有问题需要解决吗?答案是有的,我们可能需要做兼容性处理,加一些厂商前缀例如
-webkit,-moz,-o;css代码压缩;代码剪枝,将没有使用的样式代码删除掉;这些问题也可以通过工具来自动生成,比如自动生成厂商前缀的autoprefixer,压缩代码的cssnano,代码剪枝的purgecss,解决命名冲突的css module等等问题;
- css预编译器:
-
工具链:
- 和js工具链一样,我们同样可以使用一个工具,添加各种语言处理的插件,最后生成一套处理后的css代码,例如将sass语言转换为css,然后对css进行剪枝,压缩,这种工具例如postcss,同样将语言解析成抽象语法树,通过一系列预设的规则,转换为目标css,可以理解
postcss是css语言的babel;
- 和js工具链一样,我们同样可以使用一个工具,添加各种语言处理的插件,最后生成一套处理后的css代码,例如将sass语言转换为css,然后对css进行剪枝,压缩,这种工具例如postcss,同样将语言解析成抽象语法树,通过一系列预设的规则,转换为目标css,可以理解
-
四. 构建工具和脚手架
无论是js还是css,上述工具链中始终是代码层面的转换,前端工程怎么从html,js,css变成了vue这种工程呢,或者说vue的工程又怎么转换成了html,js和css的,这时就需要工程级别的转换而不是代码级别的转换了,工程化只是vue,react,angular这些吗,应该说任何代码都可以做工程化,就算是jquery这种版本T0也是可以做工程化的;
-
为什么需要工程化:
- 语言层面,开发时,我们写出的代码和真正运行时候的代码是不一致的;开发时为了使用语言的新特性,为了开发更加快速便捷,为了使用增强后的语言而使用的一系列方法,在真正运行的时候是不支持的,所以需要将单个文件转换为浏览器能读懂,兼容性强的文件;
- 工程层面,和语言一样,在开发时候的工程结构和在运行时候的工程结构不同,开发时在node环境下载依赖,运行工程,运行时在浏览器环境,所以需要做工程的转换,将开发工程,转换为运行的工程,完成这些任务的工具,我们称之为构建工具;
-
构建工具:
-
构建工具完成工程的转换,需要弄明白:
- 哪种工程更适合开发和维护
- 哪种工程更适合运行时
- 如何转换(打包)
-
由于上述问题没有标准的解决方案,所以在不同的需求下,不同角度出发,着力点不一样,所以市面上出现了各种构建工具:
-
webpack怎么定义工程化
- 1.开发和维护阶段:一切皆为模块(图片,视频,html,js,css,node_modules中文件),各种文件都可以当做模块来导入
- 2.运行时阶段:传统工程,html,js,css
- 3.如何打包:以入口文件为起点,寻找文件依赖关系,深度遍历,最后进行打包合并
-
项目通过构建工具在开发环境运行
介绍过打包流程之后,工程的开发,转换打包和输出的过程已经大概了解,那么开发过程中,怎么调试呢,项目应该可以运行起来以供开发调试的,webpack是通过启动开发服务器来运行工程化的项目的:
- 1.启动开发服务器:当运行webpack serve命令后,会启动一个开发服务器(此处webpack依赖于另外的库webpack-dev-server,dev-server又依赖express)
- 2.打包:和build不一样,不会生成实际的打包文件,而是当前的开发服务在内存中形成打包结果
- 3.渲染:此时可以在浏览器访问服务器开放的端口,去请求到服务打包的结果,拿到相应的html,js,css文件并在浏览器中执行解析和渲染
- 4.监听文件变化:当工程内容变化,会触发服务重新打包,并通过ws通知浏览器刷新或热更新(hmr)
- 5.源码地图(source-map):打包产生的文件除了
xxx.css和xxx.js,还会有xxx.css.map和xxx.js.map,源码地图有什么作用呢,打包后的文件很难读懂,出现问题也很难定位,如果有源码地图文件,可以将压缩前后的代码关联起来,出现问题后可以在生产环境中对源码进行调试和断点
-
-
脚手架
熟悉了工程化,当前端需要开发某个需求,初始化工程的时候,需要自己搭建工程吗,通过引入定制化的工具,处理器,解析器和插件,对前端工程进行运行和打包,测试,预览,文档生成等工作?大部分情况下是不需要的,我们需要一个集成工具来做项目的初始化和维护,工具需要有界面交互(GUI或CLI)帮助我们生成项目,并且有一套基础的前端项目架构,例如vue-cli和vite,说到这里,大家就会很熟悉了。
工程化的知识远不止这些,上述只是简单介绍,想要精通或有能力架构,需要更深入的学习和应用。