我对工程化的理解
随着互联网的发展,软件工程师出现了!他们不用一砖一瓦,也不用尺子电钻,计算机是他们的施工现场,代码是他们的工程部件,键盘之上的指尖跃动是他们的工程活动,在你看不见的地方运行着的一项项服务,操作系统上你看得见的app,网页等是他们的工程产出。
然而有行业的地方就有江湖,正如不是每一个会盖房子的都是建筑工程师,同样的也不是每一个会编程的都可以叫做软件工程师。那普通程序员和软件工程师的区别在哪里?首先我认为工程的本质是让工作内容机械化,只需要靠重复就可以完成工作。说起来可能会觉得比较枯燥,然而实际上正是这种枯燥保障了工作的效率和正确率。基于此软件工程师的主要服务对象其实是其他程序员,他们的职能本质是通过上层的规范和设计让工程降低对开发者水平的敏感度。
什么是"前端工程化"?
目前来说,web业务日益复杂化和多元化,前端开发从WebPage模式为主转变为WebApp模式为主了。前端的开发工作在一些场景下被认为只是日常的一项简单工作,或只是某个项目的"附属品",并没有被当做一个"软件"而认真对待(无论是产品负责人还是开发者)。
在模式的转变下,前端都已经不是过去的拼几个页面和搞几个jq插件就能完成。当工程复杂就会产生许多问题,比如:
- 如何进行高效的多人协作?
- 如何保证项目的可维护性?
- 如何提高项目的开发质量?
- 如何降低项目生产的风险?
- ...
前端工程化是使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化,其主要目的为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间,而前端工程本质上是软件工程的一种,因此我们应该从软件工程的角度来研究前端工程。
"前端工程化"里面的工程指软件工程,和我们一般说的工程是两个完全不同的概念。
- 工程是个很泛泛的概念,甚至可以认为建了一个git仓库就相对于新建了一个工程;
- 软件工程的定义是: "应用计算机科学理论和技术以及工程管理原则和方法,按预算和进度,实现满足用户要求的软件产品的定义、开发、和维护的工程或进行研究的学科"(GB/T11457-2006《信息技术 软件工程术语》)。
如何做"前端工程化"?
前端工程化就是为了让前端开发能够“自成体系”,个人认为主要应该从模块化、组件化、规范化、自动化四个方面思考。
模块化
简单来说,模块化就是将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。
-
JS的模块化
在ES6之前,JavaScript一直没有模块系统,这对开发大型复杂的前端工程造成了巨大的障碍。对此社区制定了一些模块加载方案,如CommonJS、AMD和CMD等。
现在ES6已经在语言层面上规定了模块系统,完全可以取代现有的CommonJS和AMD规范,而且使用起来相当简洁,并且有静态加载的特性。
- 用++Webpack + Babel++将所有模块打包成一个文件同步加载,也可以搭乘多个chunk异步加载;
- 用++System+Babel++主要是分模块异步加载;
- 用浏览器的<script type="module">加载。
-
css的模块化
虽然SASS、LESS、Stylus等预处理器实现了CSS的文件拆分,但没有解决CSS模块化的一个重要问题:选择器的全局污染问题。
按道理,一个模块化的文件应该要隐藏内部作用域,只暴露少量接口给使用者。而按照目前预处理器的方式,导入一个CSS模块后,已存在的样式有被覆盖的风险。虽然重写样式是CSS的一个优势,但这并不利于多人协作。
为了避免全局选择器的冲突,需要制定CSS命名风格:
- BEM风格
- Bootstrap风格
- ...
但是这毕竟是弱约束。所以很赞同一句话:
与其费尽心思地告诉别人要遵守某种规则,以规避某种痛苦,倒不如从工具层面就消灭这种痛苦。
从工具层面,社区又创造出Shadow DOM、CSS in JS和CSS Modules三种解决方案。
- Shadow DOM是WebComponents的标准。它能解决全局污染问题,但目前很多浏览器不兼容,对我们来说还很久远;
- CSS in JS是彻底抛弃CSS,使用JS或JSON来写样式。这种方法很激进,不能利用现有的CSS技术,而且处理伪类等问题比较困难;
- CSS Modules仍然使用CSS,只是让JS来管理依赖。它能够最大化地结合CSS生态和JS模块化能力,目前来看是最好的解决方案。Vue的scoped style也算是一种。
-
资源的模块化
Webpack的强大之处不仅仅在于它统一了JS的各种模块系统,取代了Browserify、RequireJS、SeaJS的工作。更重要的是它的万能模块加载理念,即所有的资源都可以且也应该模块化。
资源模块化后,优点是:
- 依赖关系单一化。所有CSS和图片等资源的依赖关系统一走JS路线,无需额外处理CSS预处理器的依赖关系,也不需处理代码迁移时的图片合并、字体图片等路径问题;
- 资源处理集成化。现在可以用loader对各种资源做各种事情,比如复杂的vue-loader等等;
- 项目结构清晰化。使用Webpack后,你的项目结构总可以表示成这样的函数: dest = webpack(src, config)。
组件化
从UI拆分下来的每个包含模板(HTML)+样式(CSS)+逻辑(JS)功能完备的结构单元,我们称之为组件。
组件化≠模块化。模块化只是在文件层面上,对代码或资源的拆分;而组件化是在设计层面上,对UI(用户界面)的拆分。
其实,组件化更重要是一种分治思想。
Keep Simple. www.jintianxuesha.com www.yacuangyl.com Everything can be www.yixingylzc.cn component.
页面上所有的东西都是组件。页面是个大型组件,可以拆成若干个中型组件,然后中型组件还可以再拆,拆成若干个小型组件,小型组件也可以再拆,直到拆成DOM元素为止。DOM元素可以看成是浏览器自身的组件,作为组件的基本单元。
传统前端框架/类库的思想是先组织DOM,然后把某些可复用的逻辑封装成组件来操作DOM,是DOM优先;而组件化框架/类库的思想是先来构思组件,然后用DOM这种基本单元结合相应逻辑来实现组件,是组件优先。这是两者本质的区别。
其次,组件化实际上是一种按照模板(HTML)+样式(CSS)+逻辑(JS)三位一体的形式对面向对象的进一步抽象。
所以我们除了封装组件本身,还要合理处理组件之间的关系,比如 (逻辑)继承、(样式)扩展、(模板)嵌套和包含等,这些关系都可以归为依赖。
目前市面上的组件化框架很多,主要的有Vue、React、Angular。Vue文档中的对比其他框架一文已经讲得很详细了。
规范化
规范化其实是工程化中很重要的一个部分,项目初期规范制定的好坏会直接影响到后期的开发质量。谈到规范,就不得不提一句在各行各业都快传烂的话即约定大于实践。
因为工程的实践过程必然要涉及到协作,而人作为协作的主体是不可靠的,没有好的约定再好的设计都是纸上谈兵。基于此软件工程里最好的协作状态应当是每一个人都可以不生产代码,只当个快乐的代码搬运工。怎么搬运,去哪搬运,如何摆放,这些需要约定去保障。
那如何评判约定的好坏?我的套路是开启假设性原则,假设你是刚入职过来的萌新,约定可不可以像游戏新手帮助那样引导你去开始工作,去贯彻前人造轮后人开车的理念,而不是重复造轮子。同时你的加入也不会让项目代码看起来不再像是一个人写的。当然,这里并不反对再配合一些必要的项目培训。
那约定包括了哪些内容?这里我从前端工程的角度来说下
一、工具约定
这个属于强制约定,基于人的不可靠性,首先就应当把可机器约束的部分抽出来。
比如各种 lint 工具,变量词库,它们可以更有效地约束代码风格,命名规范。
然后 npm,webpack,typescript 等等这些工具其实也属于约定的范畴,甚至这些工具本身正是工程化思维的体现。它们让工程的构建,调试,测试,发布,任务切分都可以在一个确定的范式下进行。而且这些全家桶本身就是提炼自业内的各类工程实践,你一旦用了就自动享受到了最先进的技术红利。所以哪怕你是一个普通程序员,通过使用一些xx全家桶,也可以输出一份还不错的工程产出。
举些实际点的例子,在前端模块化方案出现之前,页面内的脚本只能通过文档,口头约定等这些相对宽松的形式来组织,做不到静态检测,做不到更安全的封装,甚至编辑器也做不到更精确的代码提示。而现在,几乎模块化开发已经成为了一种习惯,开发者们可以更好地各司其职。然后也都习惯了在发布之前再通过一些确定的简单命令去执行代码压缩,合并,tree shaking等构建任务,这些习惯本身不正是约定的作用么?
再补充一点,业务中造的各种轮子和使用的各种组件库其实也不只是为了少写代码,从某种意义上来说它们也都是工具约定,举个最简单的例子:你希望项目中的每一个提交按钮在点击提交之后都变成 loading 状态。这种靠开发者每次都手动去实现显然是不靠谱的,因为团队中你不可能保证每一个人都有强迫症,每一个人都那么注重用户体验,当然也有忙起来忘记了的时候。那么如果有个实现了上述规范的按钮轮子,提交什么数据你来定,怎么维护 loading 它来定,是不是就稳妥多了?
所以,工具约定能让项目质量得到基本的保障。
二、文档约定
有人说,真正好的代码不需要文档,这个我认同。然后我要补充的是真正好的项目一定需要文档!我认为它的主要作用是作为你的工程设计的使用说明书,目的是去引导团队成员去使用你的设计。
比如,页面中的各类部件,你设计了一个组件库出来,它们有一套统一的交互逻辑,色调,动画等等,然后在你的设想中它们也分门别类地对应了各类业务需求,哪些地方应该用Panel,哪些地方应该有个标题,哪类操作需要放到ActionBar中。那你就必须要用文档去把这些设想表述出来,告诉他们你要的这个组件直接来复制这里的代码,你要的这个样式直接来这里复制我定义好的类名。去引导其他开发者去遵循,否则真正开发的时候大家还是两眼一抹黑,稍微负责点的同学会尝试从已经做好的页面中去模仿,更多的情况是去猜,比如这里的边距应该是10px,那里的颜色应该是“#xxx”。这样一方面浪费了更多的时间,一方面很难产出一个质量稳定的产品。其他的,你封装了一些工具类,Mixin库,定义了一些开发模式等等,这些东西都需要文档中的api说明和最佳实践来指导其他人去用。
说出来感觉这样好复杂,写文档占用工程师的时间会超过写代码本身,然而之后团队其他成员节省下来的时间一定会远远超过这里损失的时间。你会发现团队加班的情况越来越少了,项目出事故的概率越来越小了,成员们开始有了更多的时间去学习和实践其他技术来反哺项目本身,这才是一个良性循环。而且一份好的工程实践是可以复用的,这些才是团队积累下来的真正宝贵的财富!甚至还可以做开源,去帮助更多的团队,哪些xxx全家桶,xxx组件库,不就是这么来的么?我们很多时候爱说,“没关系,之后可以再重构”,“没事,这里先这么实现吧“!我告诉你坑只会越来越多,最终形成破窗效应。
最后,来点实在的,说说我是怎么写项目文档的。现在很多团队都有内部代码托管平台,所以实际上文档的形式就是README,而打开项目的首页,首先看到的其实就是根目录下的README,它是整个项目文档的索引,我认为应该包括如下内容:
- 项目名称+简单介绍+演示地址
- 项目的安装,启动,调试,发布的完整步骤
- 技术栈:方言(ts,sass),框架(vue/react..),三方库(package.json中dependencies下的所有)。并附上以上所有的链接,方便团队成员去补习一些前置知识
- 如果有IDE的要求,最好附上一份通用的配置,方便其他成员导入就可以使用
- 相关规范,比如:文件/文件夹的创建和命名规范即工程结构、Vue组件内的标签顺序(template>script>style)、常用变量的命名词库等
- 其他。这里主要强调一下纪律,告诉成员们项目中的每个文件夹下面都有一份README在等着你,你得去看,全部看完了才可以开始开发,它们告诉了你每个文件夹里放的是什么,具体有什么作用,项目中已经有了哪些轮子,造轮子之前请三思!
然后通过这个索引,去引导他们再去打开一个个的文件夹去里面读各个轮子的README,这种文档就没什么好解释的了,只强调一下一些业务相关或模式相关的轮子要有最佳实践的链接或Demo,你可以直接链接到具体的页面代码中去;UI相关的,要有样式预览和适用场景说明
谁需要前端工程化?
初中级前端工程师
对于初级中级前端工程师来说,由于工作经验较少,技术的宽度和广度都不足,对前端工程化的认知其实也是不够的,一上来整体掌握前端工程化肯定是有困难的。对于这部分同学来讲,首要的事情要学会去"用”,循序渐进地去了解其中的原理。例如在开发之余,自己学一学如何实现一个简单的脚手架工具,了解一下日常开发必备的脚手架是如何实现的,以点带面地学习里面用到的技术点。
期望晋升的前端工程师
前端工程化能力也是一个资深前端的必备技能。工作好多年了,如果连前端工程化都知之甚少,甚至连一个基本的脚手架都不能自己搭建,这是不及格的。如果将来带团队,怎么能指导团队同学,怎么能带领团队进步呢?如果去参加公司的晋升,也是没有说服力的。因为想要晋升高 T,必然需要在效率和性能优化等方面有深厚的积累和贡献。
所以,无论你是处在什么阶段,深入了解一下前端工程化都是极有必要的。
前端应用越来越复杂,对前端工程化的要求越来越高。脚手架作为目前前端工程化的一个重要的组成部分,在开发过程中扮演了至关重要的角色。现在几乎所有主流前端几框架都有自己配套的脚手架,无疑给开发者带来了极大的便利。但正如上文所述,很多开发者由于本身入行较短,或者很多工作多年的开发者由于公司业务的原因,没有机会接触到前端工程化领域。
最后
希望大家都能理解脚手架背后蕴藏的工程化思想,并且可以亲手完成一个脚手架的搭建。也希望大家在理解了前端工程化思想后,探索更多的方法,赋能自己的团队,共同推动前端工程化的发展。