一、项目中的工程化
1.什么是前端模块化
前端开发和其他开发工作的主要区别,首先是前端是基于多语言、多层次的编码和组织工作,其次前端产品的交付是基于浏览器,这些资源是通过增量加载的方式运行到浏览器端,如何在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统,这个理想中的模块化系统是前端工程师多年来一直探索的难题。
所谓的前端模块化开发,我的理解就是,在开发的时候,把不同的资源文件按照他的具体用途进行分类管理,在使用的时候利用CommonJS、AMD、CMD等规范将这些资源文件引入到当前文件中。然后在测试或者最后上线的时候,将这些资源文件按照一定的要求进行压缩合并再加上版本控制处理。
可能这样的理解或者说法值得商榷,但是个人还是觉得模块化就是对内容的管理,是为了解耦合。
2. 前端模块化包含哪些方面
- node.js 运行时,npm 包管理
- expressjs 服务端框架
- babel 编译 ES2015+ 代码到 ES5
- webpack打包和压缩源码
- eslint 检查代码规范(腾讯那套)
- prettier.js 代码自动美化排版
- git hook规范代码提交格式(按照anjular.js)
- mocha 单元测试
二、webpack使用和工作原理
webpack是前端项目打包工具,它是现代前端模块化开发的基石,它的主要工作:分析项目结构,找到js模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
1. 工作原理
webpack启动后,会在entry里配置的module开始递归解析entry所依赖的所有module,每找到一个module, 就会根据配置的loader去找相应的转换规则,对module进行转换后在解析当前module所依赖的module,这些模块会以entry为分组,一个entry和所有相依赖的module也就是一个chunk,最后webpack会把所有chunk转换成文件输出,在整个流程中webpack会在恰当的时机执行plugin的逻辑。
loader是什么?loader是webpack最重要的部分之一,通过使用不同的Loader,我们能够调用外部的脚本或者工具,实现对不同格式文件的处理,loader需要在webpack.config.js里边单独用module进行配置。
1、实现对不同格式的文件的处理,比如说将scss转换为css,或者typescript转化为js
2、转换这些文件,从而使其能够被添加到依赖图中。
loader和plugin的区别:
plugins和loader很容易搞混,说都是外部引用有什么区别呢? 事实上他们是两个完全不同的东西。这么说loaders负责的是处理源文件的如css、jsx,一次处理一个文件。而plugins并不是直接操作单个文件,它直接对整个构建过程起作用。
2. webpack打包优化
- happypack,多线程编译,加快编译速度(加快loader的编译速度), 解决webpck单线程的弊端。
- 分包,使用splitChucks抽取公共代码,重点是缓存组cacheGroups的配置
- 在loader的options中配置cacheDirectory,当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process),cacheDirectory会缓存JavaScript的编译结果。如果遇到不能刷新代码的情况,可以过来看看是不是走了缓存
- 先运行一个dll配置文件,预先把一些固定模块编译,它会在第一次编译的时候将配置好的需要预先编译的模块编译在缓存中,第二次编译的时候,解析到这些模块就直接使用缓存,而不是去编译这些模块(DllPlugin);将预先编译好的模块关联到当前编译中,当 webpack 解析到这些模块时,会直接使用预先编译好的模块(DllReferencePlugin),,这块暂时没在项目使用。
二、gulp
gulp是项目自动任务运行器,充分借鉴了unix的pipe思想,它的使用也很简单:
1. gulp api
-
gulp.src: 用于产生数据流,参数为符合所提供的匹配模式(glob)/匹配数组的文件.glob模式类似于正则表达式,具体使用可以参考官网。
-
gulp.dest: 管道的输出写入文件,可多次调用
-
gulp.task: 定义具体的gulp任务,第一个参数是task名字,第二个是任务函数,task方法还可以指定按顺序运行的一组任务,下面例子,build会在hello,greet任务执行完才执行。
gulp.task('build', ['hello','greet']); -
gulp.watch: 用于监视符合所提供的匹配模式(glob)/匹配数组的文件.一旦这些文件发生变动,就运行指定任务。
gulp.task('watch', function () {
gulp.watch('templates/*.tmpl.html', ['build']);
});
2. gulp使用的最佳实践
- 定义一个task目录,每一个文件是一个task任务
- 构建gulp配置文件,每个配置文件包含所有子任务需要的参数
- 抽离和子任务无关的工具函数(日志/时间/错误处理)
- 项目根目录配置gulpfile.js入口文件,引入这些task任务/配置文件
3. gulp和webpack
gulp和webpack一个很大的不同是对资源管理的方式的不同,gulp中的task可以对所有符合所提供的匹配模式(glob)/匹配数组的文件进行预处理。Webpack则不是这样管理资源的,它是根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
- gulp是为了规范前端开发流程,实现前后端分离、模块化开发、版本控制、文件合并、压缩、Mock数据等功能的一个前端自动化构建工具。强调的是前端开发的工作流程,我们可以通过配置一系列的task(Gulp中的gulp.task()方法配置),定义task处理的事务(例如文件压缩合并、雪碧图、启动server、sass/less预编译、版本控制等)然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。核心是 task 。
- webpack是当下最热门的前端资源模块化管理和打包工具,成为模块打包机。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、AMD 模块、ES6 模块、CSS、图片、JSON、Coffeescript、Less 等。
Gulp和Webpack基本都能满足前端自动化构建工具的任务,但还是看出两个工具的侧重点,Gulp侧重整个过程的控制,Webpack在模块打包方面有特别出众。
参考文献:
https://juejin.im/post/593cf6efac502e006b3e2bc0
https://www.jianshu.com/p/2cc6a22c9ecc
https://www.cnblogs.com/theblogs/p/10472900.html
https://www.cnblogs.com/RuMengkai/p/6667321.html
三、babel使用和工作原理
1. babel的工作流程
babel是一个编译器,其编译过程分为解析、转换、生成三个阶段。babel本身所做的就是把代码解析,如果不借助外界插件对代码进行转换,那babel会输出同样的代码,所以如果想babel做一些实质工作,需要借助插件。
- 解析:把代码转化为抽象语法树(AST),每个js引擎都有自己的AST解析器,而babel是通过babylon实现的。
- 转换:babel借助插件,对AST树进行转换
- 生成:将已经转换完成的AST树通过babel-generator在转换为js代码,其过程就是深度优先遍历整个AST, 然后构建转换后代码的字符串。
2. babel的配置文件
- plugin:用于转换你的代码,插件处理代码的顺序是从前往后
- presets:preset 可以作为 Babel 插件的组合,它在插件之后运行,从后向前。它告诉babel要转换的源码使用了哪些新的语法特性,设置转码规则,其中最重要的是babel-preset-env。
3. babel-preset-env介绍(必须配置)
3.1 babel-preset-env出现的背景
babel-preset-env取代了babel-preset-latest,latest是多个preset的集合,并且随着ECMA规范的跟新更增加了它的内容,随着时间的推移,babel-preset-latest 包含的插件越来越多,这带来了如下问题:1是版本增多,编译变慢;2是随着用户浏览器的升级,ECMA规范逐渐被支持,编译到低版本就没必要了,为了解决这些问题,babel推出了babel-preset-env,它可以根据配置按需加载插件(配置包括平台和版本)
3.2 babel-preset-env具体使用
babel-preset-env如果不配置,就相当于babel-preset-latest,前面提到它支持更精细的配置,以提升编译速度,举个例子:
// index.js
async function foo () {}
为了对上面的代码进行转换,我们可以使用babel-preset-env,如果使用默认配置,会转换成一堆代码。但是如果我们知道我们的代码是跑在node9以上的版本上,那上面的兼容代码就是多余的了,因为node9已经支持async/await,没有必要转换了,所以我们对babel-preset-env进行配置target。
targets:{
"node": "8.9.3"
}
presets具体配置如下
const presets = [
[
"@babel/preset-env",
{
targets: {
//转码后支持的浏览器:市场份额>1%, ie版本大于9
"browsers":["> 1%", "ie >= 9"],
},
// node版本
"node": "8.9.3"
// usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加。
useBuiltIns: "usage",
corejs: 3,
// 不需要模块化,一般用webpack做
modules:false
}
],
"@babel/preset-react",
"@babel/preset-typescript",
]
4. babel polyfill (必须配置)
Babel 把 Javascript 语法 分为 syntax 和 api
- API: api 指那些我们可以通过函数重新覆盖的语法 ,类似 includes,map,includes,Promise,凡是我们能想到重写的都可以归属到 api。
- Syntax :像 箭头函数,let,const,class,依赖注入 Decorators,等等这些. Javascript 在运行时是无法重写的这些的,想象下,在不支持的浏览器里不管怎么样,你都用不了 let 这个关键字
babel 编译时只转换语法,但并不会转化BOM里面不兼容的API,Babel 把API的转换单独放在了 polyfill 这个模块处理。
Babel 这个设计非常好, 把 Javascript 语法抽象成2个方面的, syntax 和 polyfill 独立开来,分而治理,6to5 一开始设计是把二者放在一起的,大家想想 polyfill 随着浏览器的不同,差异是非常大的,2个要是在一起 代码的耦合性就太大了,到处都是if else。
polyfill 目前有两种方式:
- babel-runtime
- babel-polyfill
- babel-plugin-transform-runtime
4.1 babel-runtime
就是core-js和regenerator,前者转换一些内置类(promise/symbol)和静态方法;后者作为前者的补充,主要是对 generator/yield 和 async/await 两组的支持。
transform-runtime一方面解决重复定义,比如在对await/async进行转译的时候,如果多个文件用到,会被独立解析多次,这样解析之后的代码存在与多个文件中,出现重复,完全提前定义好转换方法(babel-runtime/regenerator),在使用的时候直接引入即可;另一方面不会污染全局变量(转化函数名是单独的,与原生不同)
4.2 babel-plugin-transform-runtime
@babel/plugin-transform-runtime配置到babel配置文件的plugin中。它只是为了方便使用babel-runtime 的,它会分析当前AST树中是否有引用babel-rumtime 中的垫片,如果有,会自动在当前模块插入我们需要的垫片。
4.3 babel-polyfill
它同样是引用了 core-js 和 regenerator,垫片支持是一样的。babel-polyfill 是为了模拟一个完整的ES2015 +环境,旨在用于应用程序而不是库/工具。它是以重载全局变量 (Promise), 还有原型和类上的静态方法(Array.prototype.reduce/Array.form),从而达到对 es6+ 的支持。它是一次性引入项目的。开启babel-polyfill的方式,可以直接在代码中require,或者在webpack的entry中添加,也可以在babel的env中设置useBuildins为true来开启。(缺点:包大,污染全局变量)。
4.4 babel-polyfill和transform-runtime 对比
- babel-polyfill 是当前环境注入这些 es6+ 标准的垫片,好处是引用一次,不再担心兼容,而且它就是全局下的包,代码的任何地方都可以使用。缺点也很明显,它可能会污染原生的一些方法而把原生的方法重写。而且一次性引入这么一个包,会大大增加体积。适用于大型应用。
- transform-runtime 是利用 plugin(babel-plugin-transform-runtime) 自动识别并替换代码中的新特性,你不需要再引入,只需要装好 babel-runtime 和 配好 plugin 就可以了。好处是按需替换,检测到你需要哪个,就引入哪个 polyfill,如果只用了一部分,打包完的文件体积对比 babel-polyfill 会小很多。而且 transform-runtime 不会污染原生的对象,方法,也不会对其他 polyfill 产生影响。所以 transform-runtime 的方式更适合开发工具包,库,一方面是体积够小,另一方面是用户(开发者)不会因为引用了我们的工具,包而污染了全局的原生方法,产生副作用,还是应该留给用户自己去选择。缺点是随着应用的增大,相同的 polyfill 每个模块都要做重复的工作(检测,替换),虽然 polyfill 只是引用,编译效率不够高效。
总结下, Babel 只是转换 syntax 层语法,所有需要 @babel/polyfill 来处理API兼容,又因为 polyfill 体积太大,所以通过 preset的 useBuiltIns 来实现按需加载, 再接着为了满足 npm 组件开发的需要出现了@babel/runtime 来做隔离。
5. 常用的babel插件
-
babel-cli:命令行插件
-
babel-node:在node环境中,直接运行 es6不需要转码
-
babel-register:在require上加上一个钩子,每次执行require时候,需要先进行转码,babel-register只会对 require 命令加载的文件转码,而 不会对当前文件转码。
-
babel-core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。像webpack的babel-loader就是调用这些API来完成转译过程的。
-
babylon:js的词法解析器
-
babel-traverse:用于对AST的遍历,主要给plugin用。
-
babel-generator:根据AST生成代码
-
babel-polyfill:看上面
参考文献:
https://juejin.im/post/5ab35c3cf265da23771951a2
https://juejin.im/post/59b9ffa8f265da06710d8e89
https://juejin.im/post/5aefe0a6f265da0b9e64fa54
https://juejin.im/post/5c21b584e51d4548ac6f6c99
https://www.cnblogs.com/chyingp/p/understanding-babel-preset-env.html
https://www.babeljs.cn/docs/babel-polyfill
https://www.jianshu.com/p/7bc7b0fadfc2
本文深入探讨前端项目中的工程化,详细介绍了webpack、gulp和babel的使用和工作原理。webpack作为现代前端模块化开发的基石,通过loader和plugin处理不同格式的文件,实现模块打包。gulp则作为自动化任务运行器,通过task管理开发流程。babel则是编译ES2015+代码到ES5的关键工具,通过preset和plugin实现按需转换。

1197

被折叠的 条评论
为什么被折叠?



