分享狼叔关于《大前端工程化的实践与思考》

18,729 阅读22分钟

前言

本文来自极客前端训练营的主题公开课,非原创。

作者简介

桑世龙(狼叔),阿里巴巴前端技术专家,nodejs《狼书》作者。

快速发展的大背景

前端发展太快了,在2004年之前,大概只要会网页三剑客(一套强大的网页编辑工具,最初是Macromedia公司开发的,由Dreamweaver、Fireworks、Flash三个软件组成)就很牛了,那时候前端还比较“纯洁“。在进入以Ajax为代表的异步刷新改进用户体验的Web 2.0时代后,开始涌现出大量的库,比如Prototype、jQuery、MooTools、YUI、Ext JS、kissy等,它们都还只是对浏览器兼容性和工具类函数的封装,可是从Backbone、Angular 1.x相继出现后,前端就开始热闹起来了,出现MVC、MVVM、IOC(控制反转,Java著名框架Spring里的概念)、前端路由(类似于Express、Koa等框架的路由)、Virtual DOM(虚拟DOM,通过DOM Diff算法,减少对DOM操作)、JSON API(接口规范)、JavaScript Runtime(Coffee、Babel、TypeScrpt)等等,各种框架格式如雨后春笋一样冒出来,以前可能半年甚至更长时间才出一个框架,现在可能几周就有新的框架诞生,前端进入了空前的爆发阶段。

图解2004-2016变化

image.png

前端经历过的几个阶段

现在前端开发也复杂到了一定程度,对比之前的开发方式,可以称为现代Web开发。这里我来做一个总结,前端发展经历了4个阶段:
1.HTML/CSS/JavaScript(基础)
2.jQuery、jQuery UI、Ext JS(曾经流行)
3.Backbone(MVC)、AngularJS、Vue.js(曾经流行)
4.组件化React、Vue.js(流行趋势)

目前前端的发展状态

目前整个大前端还属于发展期,没有形成固定模式,所以从趋势上看是上升期,复杂、混乱、多样性的结果也导致现在的前端比较吃香,被大家戏称为“钱端”。很多同学会问,学习前端技术是采用渐进式方式,还是上来就直接学React/Vue.js等框架呢?实际上,我认为这两种方式都存在,如果有经济压力就向“钱“看,可以直接学习React/Vue.js等框架,但学会了之后,一定要把其他3个阶段补充学习完;如果没有经济压力,又有时间和耐心的话,循序渐进的学习是最好的方式。编程没有捷径,无论是哪种,都需要脚踏实地多多练习。

当你代码写多了,就会发现:
当大家HTML写烦了,开始引入模板引擎。
当大家CSS写烦了,比如不支持嵌套,开始引入CSS预处理器SASS、Less、Stylus、PostCSS等。
当大家JavaScript写烦了,开始引入更友好的语言,熟悉Ruby的使用CoffeeScript、熟悉Java/C#的使用TypeScript。

于是,前端开发变得复杂,并进入现代Web开发时代。

image.png

nodejs

对于Node.js来说,所有前端框架都是视图层(View)的展现技术而已,可以非常方便地和各种框架集成,并按照业务需求予以更好的实现。另外,所有的前端框架都采用Node.js和NPM作为辅助开发工具,使用了大量Node.js模块,但前端框架使用的模块大同小异,如果熟练掌握了Node.js和NPM,对于学习前端技术来说,你要学的只是纯前端的部分而已,复用价值非常高。

和Node.js相关的前端开发模块实在是太多了,这里简单列举一些:

image.png

由这些模块,也就引出了我们今天要聊得话题——大前端工程化实践与思考。

构建工具

先来说说构建工具。预处理器是前端高级玩法。

image.png

我经常开玩笑说以前HTML/JavaScript/CSS的时代太纯洁了,现在随便写哪样都要编译/转译。好处是可以借助高级特性,提高开发效率;缺点也是极其明显的,就是人脑要有转换思维。这其实是蛮痛苦的,本来HTML/JavaScript/CSS就不够熟练,再转一次,对于新手来说需要一个适应过程。所以这件事还是要辩证地看,福祸相依。

说起构建工具,大概都会想到Make、Ant、Rake、Gradle等,其实Node.js世界里有更多实现。

image.png


构建工具的源码都不是特别复杂,所以Node.js世界里有非常多的实现,还有人写过Node.js版本的Make呢,玩得很嗨!

选用构建工具最主要的目的是为了自动化。对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。尤其是工程越复杂,自动化的价值就越大。这里的编译包含模板、CSS预处理语言、JavaScript友好语言等编译,在源码编写时用的是高级玩法。

除了编码编译外,还有测试、代码风格检查、上线前优化(合并、压缩、混淆),可以说,构建系统在整个软件工程里无处不在。

grunt


Grunt是前端领域第一个流行的DSL(领域定义语言)风格的构建工具,它的出现对于前端工程化起到了非常好的引导作用。它通过Gruntfile来描述task,同时通过插件机制来扩展各种工程能力。
当你在Gruntfile文件正确配置好了任务,任务运行器就会自动帮你或你的小组完成大部分无聊的工作。

Grunt除了配置复杂外,还有一个问题就性能,可能很多同学遇不到性能问题。Grunt是读写文件的,所以在多文件、大文件处理时是有性能瓶颈的。

Grunt的设计还是挺精巧的,但配置起来让人抓狂,自然会有很多不爽的人,于是Gulp就慢慢崛起了。

Gulp

简单来讲,Gulp是一个Node.js写的构建工具,基于Stream的流式构建工具,它包含大量插件。Orchestrator是Gulp底层依赖的task相关的核心库,它定义了task执行方式和依赖,而且支持最大可能的并发,Gulp的高效即来源于此。本身Stream对大文件读写就非常棒,再加上上面说的种种特性,使得Gulp流行成为必然。

Gulp的应用场景非常广,前端项目或Node项目都可以使用,哪怕是webpack也可以和Gulp搭配使用,通过gulp-webpack模块即可。如果想深入学习Gulp,可以看一下stuq-gulp(github.com/i5ting/stuq…),文中以WeUI里Gulp用法为例,由浅入深,从用法到原理进行了阐述。

模块化

解耦是软件开发领域永恒的主题,而模块化是目前最好的解耦方式,所以从无到有、从有到成熟的演化,必然要经历很长的路。如今的发展,源于1993年HTML创建、1995年诞生JavaScript、1996年发布CSS1,之后就进入了原始而野蛮的开发阶段。从互联网诞生到2000年泡沫破灭,Web技术才算真正崛起。那时候特别纯洁,会写的人就很牛了,之后Flash也曾超级火爆。在之后就到了Web 2.0时代,开始出现Ajax,开始有了各种兼容浏览器的库,然后开始模块化,在2009年诞生了Node.js,彻底改变了JavaScript以及前端开发领域的开发方式,从浏览器端的Backbone支持MVC模式,到AngluarJS支持的MVVM,到现在的React/Vue和webpack等。

这里我尝试从更宏观的视角来进行归类,大致分5个阶段,分别是原始阶段、包管理器、模块规范、模块加载器、模块打包器。下面我来一一说明。

原始阶段

脚本加载还都比较原始,方式如下:使用多个script标签加载、手动管理顺序、手动管理加载哪些。

在Web开发里经过了很多尝试,也做过很多龌蹉的事儿,比如
动态创建Script标签XHR Eval、XHR Injection、$.getScript()、 Script in Iframe、Script DOM Element、Script Defer。

如果说加载比较恶心,那么脚本顺序更恶心,而且JavaScript有个“特性”,一处报错,所有后面的都会崩溃,所以开发会很苦的维护脚本加载的顺序。

包管理器


如果代码存在一个问题,更新jQuery UI版本怎么做呢?jQuery UI依赖jQuery,先下载jQuery UI代码,然后找到依赖的jQuery版本,再替换已有文件,之后测试,如果没问题就直接替换了。

很明显,直接进行文件操作是非常低效率的做法,对于版本、依赖都没法做更好的处理。于是就出现了Bower、NPM等包管理器,所有模块升级,依赖都有包管理负责,可以说很大程度上省去了前端的重复性工作。

模块规范

模块化加载的本质是按需加载。
比较常见的规范有3个:(1)AMD、CommonJS和ES6 Modules;(2)使用标准的模块系统来处理依赖和导出
每个文件是一个模块;(3)使用模块加载器或打包器进行处理

模块加载器

模块加载器需要实现两个基本功能:
1 实现模块定义规范,这是模块系统的基础
2 模块系统的启动与运行

常见的比如RequireJS、Sea.js和SystemJS。以AMD的模块加载器RequireJS为例,通常使用RequireJS的话,我们只需要导入RequireJS即可,不需要显式导入其他的JS库,因为这个工作会交给RequireJS来做。

模块打包器


模块加载器提供运行环境,能够让遵守模块规范的代码在上面跑,所以模块化的好处是很明显的。但对于真实项目来说,你还需要构建、打包等操作。

对于开发来说,只需要关注业务模块,不需要了解模块加载器和构建过程,很明显这是非常理想的,也因此产生了webpack这样的模块打包器。

Gulp作为通用构建工具,它已经非常完美了。但技术变革太快了,应用各种预处理器、前端组件化,导致前端无比复杂,而webpack的出现刚刚好,完美解决了前端工程化的问题。

webpack基本介绍

webpack是Node编写的著名模块,是打包器(bundler),不只是支持CommonJS模块,而且还支持更潮的ES6模块,是目前使用极其广泛的打包器,像前端组件化的框架(React、Vue)大多都是使用webpack的。

loader && plugins

它提供了两个极其好的机制:loaders和plugins。

loaders:webpack认为每个文件都是资源模块,针对打包构建过程中用来处理源文件的(JSX、SCSS、Less..)统称为loader。

plugins:插件可以完成更多loader不能完成的功能。插件并不直接操作单个文件,它直接对整个构建过程起作用,大多数内容功能都是基于这个插件系统运行的;当然还可以开发和使用开源的webpack插件,来满足各式各样的需求。比如知名插件autoprefixer、html-webpack-plugin、webpack-dev-middleware、webpack-hot-middleware。

webpack打包过程


(1)从配置文件里找到entry point
(2)解析模块系统
(3)解决依赖
(4)解决依赖管理(读取、解析、解决)
(5)合并所有使用的模块
(6)合并模块系统的运行时环境
(7)产生打包后的文件

浏览器加载过程

(1)通过<script>加载webpack打包后的文件
(2)加载模块运行时环境
(3)加载entry point
(4)读取依赖
(5)解决依赖
(6)执行entry point

如果对比着理解webpack打包过程和浏览器加载过程,可以对前端模块化的演讲过程能够有更好的了解。从定义模块规范,到模块化系统的运行时环境(加载器),再到更高级的打包器,这个演进的过程,也让前端开发越来越简单,这才是打包器越来越火爆的原因。

理解webpack的功能

image.png

事实上从结果来看也是这样的,工程化最终目的是让开发者专注于写业务模块,其他的事情让打包器来做。从这个角度讲,webpack还是比Gulp更成功。

但是webpack学习曲线也是比较陡峭的,以点打面、不断深究,是非常好的学习方式。从基本用法、概念,到tree-shaking、code spit,再到如何打包、浏览器如何解包,到工程化,到大规模构建如何优化,考验你的是基础是否扎实、原理是否真懂、是否了解优化思路和源码。作为类似模块与Gulp进行对比,到Stream(Gulp核心),到event(Stream基类),到HTTP(Stream在HTTP里应用),到eventloop,到C++实现的libuv和v8,太可怕了,几乎把《狼书》的所有内容都涵盖了。

webpack构建大型项目

webpack封装


关于webpack 的封装实践有很多,比如知名的af-webpack、ykit、easywebpack。

af-webpack是支付宝定制的webpack,把webpack-dev-server等Node.js模块直接打包进去,同时对配置做了更好的处理,以及插件化。

ykit是去哪儿开源的webpack,内置Connect作为Web server,结合dev和hot中间件,对于多项目构建提效明显,对版本文件发布有不错的实践。

easywebpack也是插件化,但对解决方案如boilerplate等做了非常多的集成,比如egg的ssr是有深入思考的,尽管我不赞同这种做法。

CRA和UMI


框架和基本探索稳定后,大家就开始想如何更好地使用、更简单地使用。各家大厂都在前端技术栈思考如何选型和降低成本,统一技术栈。

在Create React App(CRA)项目里使用的是react-scripts作为启动脚本,它和egg-scripts类似,也都是通过约定,隐藏具体实现细节,让开发者不需要关注构建。

UMI和CRA类似,它是一套基于蚂蚁金服技术栈总结的最佳实践,是一套零配置“约定高于配制“,开箱即用,按最佳实践进行开发的前端框架:React全家桶 + dva + jest + antd (mobile) + less + eslint。
UMI核心是chainwebpack,将webpack复杂的配置插件化,能够让多项目之间自由切换,消除各种webpack配置问题。

UMI思考得相对全面,从技术选型、构建,到多端输出、性能优化、发布等方面进行了拆分,使得UMI的边界更为清晰,作为前端最佳实践没啥毛病,目前大多数前端组也都是类似的实现方式。说白了就是现有技术栈的组合,封装细节,让开发者用起来更简单。在未来,类似的封装还会有更多,毕竟框架层面创新不那么多,大家就会将精力慢慢转到应用层面。

脚手架

cli参数处理


早期比较有名的是Commander,作者是TJ,其实是Ruby迁移到Node的版本,著名的express-generator就是基于这个模块编写的脚手架。

它的问题在于包的依赖有点大,对于强迫症开发者来说接受不了。于是Yargs这类的模块应运而生,强大且依赖极少。

模板方式


Yeoman是著名的脚手架模块。通过安装模板,在yo命令上进行扩展,很明显这是中心化思想的结果。优点是开发难度小,对于工程化收敛是有帮助的。

git仓库做模板


模板虽好,但还是有点麻烦。如果模板放到Github上,更新维护都非常方便。比较典型是Vue CLI(之前版本)、saojs。

核心是ownload-git-repo,然后对里面的文件进行模板处理。这种方式的灵活度非常高,是目前比较主流的方式。
总结一下,脚手架是在一步一步的演进,但整体来说,除了在工程化收敛上做了些优化,其他并没有特别大的优化,原因很简单,脚手架的复杂度做不上去。类似UMI够复杂吧,但CLI部分内容并不多,也不需要特别灵活的配置。
关于如何用Node构建脚手架,其实用Node.js写生成器是件非常简单的事儿。你可以参见《零基础十分钟教你用Node.js写生成器:你只需要5步》(github.com/i5ting/writ…)。

未来的端渲染和构建


下面看一下前端的演变过程:
最初的Java、PHP时代的纯服务端渲染时代。

前后端分离,即使用JavaScript运行在客户端,通过请求获取服务端接口数据,借助如JQuery、Angular、React、Vue等前端框架操作或生成页面DOM,充分利用客户端资源,减少服务端压力,前后端分工明确,一直到现在仍是最常用的开发方式。

同构开发,如Meteor、Next.js、Nuxt.js等框架,都提供了不同的适用场景和开发方式,但目的都是为了同一套代码能同时应用于服务端和客户端。

ssr的演变过程

JSP/ASP都算,当然Node渲染模板也算,Node世界模板最为丰富。

BigPipe,虽然很老了,但分块传输优点是非常明显的,且对浏览器友好。Facebook和微博、去哪儿都是受益者。Node天然支持,对res.write很友好。

基于组件写法的SSR,比如React SSR。时代变了,SSR也要跟上。vdom + hydrate玩的可以很嗨,连BigPipe也可以结合起来。UMI SSR和Rax SSR未来可期。

真正的同构,即CSR和SSR写法一致,未来不再区分概念,在Servless里,API和渲染都是函数。
近几年前端技术的变化可谓翻天覆地,在选择技术栈之前应该看清自己的应用场景,没有最好的框架,只有最适合应用场景的框架,同构开发方式也不例外,下面我介绍一下使用同构开发的优点和需要注意的问题。

举一个项目的例子, github.com/ykfe/egg-re… 。它的写法是:

image.png

核心要点有:
render是React的视图渲染方法
getInitialProps是获取数据方法,将返回值赋值给组件状态
CSR通过高阶组件实现
SSR通过Node执行

image.png


CSR,其实需要开发者关心React和webpack构建
Beidou是SSR解决方案,开发者关心React、egg和webpack构建
UMI SSR得益于UMI内置webpack,所以开发者只需要关注React编写即可,部署到egg上。

在未来,我们希望所有服务都走Serverless,这样用户只需要关注React写法即可。构建是本地做的,类似于UMI。egg不需要关系,因为走的是Serverless的运行时环境。

CSR和SSR写法一致,又可以上Serverless,它的优点有:
C端应用,直接SSR,可以提效,性能有保障,SEO友好。
中后台应用,采用这种方式可以提效,降低开发难度和代码维护难度。每个页面都是独立的,系统再造也是极其简单的。

上了Serverless之后,除了本地CLI构建外,还可以提供云构建。比如每个页面都是函数,那么多个页面如何合并呢?比如UMI典型的多页应用,通过react-loadable+react-router实现的代码按需加载,这种情况就可以先在Serverless上发布多个页面,继而在云构建上,配置多个页面,最终构建成一个多页应用。

另外,渲染层做轻,对于未来上Web IDE也是有极大便利的。可以畅想一下,未来有个流程就可以完成开发,是不是很美好?阿里将Serverless和IDE作为两大方向,确实是非常用心的考虑。

前后端协作

之后我们来聊聊前后端协作。前端本来的定义是指基于浏览器做开发的,随着浏览器应用范围越来越广,前端的界定也越来越模糊。目前前端已经涵盖PC、H5、移动端组件,几乎是所有和用户接触的带界面的都算前端,所以才有了大前端的概念,泛指所有和用户交互的终端开发。

一直以来,跨浏览器(Web View)都是开发领域折腾的方向,从Native到Hybrid,到React Native/Weex,再到Electron、PWA、小程序,你能看到大家都在往轻量级走,统一技术栈,降低成本。也就是说,端上希望统一,又以前端为开发核心,故而统称为大前端。

JavaScript已横跨三端,再说一下Node,覆盖工程化和服务端能力。很多大公司移动端和前端已经合并一个组进行管理,大前端局面已成定局。

前后端分离

前后端分离,即使用JavaScript运行在客户端,通过请求获取服务端接口数据,借助如JQuery、Angular、React、Vue等前端框架操作或生成页面DOM,充分利用客户端资源,减少服务端压力,前后端分工明确,一直到现在仍是最常用的开发方式。

BFF

BFF(API Proxy),独立的API层的出现原因有很多:

移动端兴起,大家都开始面向API开发,但却忽略了大前端不只只有移动端,还有PC Client和PC/H5 Web。主要是屏幕大小不同而导致UI/UE上有明显差别。

后端开发API的时候,不喜欢同时维护多套API。

除了沟通成本,还有对前端实现不了解而产生的误解。

简单理解,BFF就是各端的API独立提供了。但是,这样做很明显不经济。那么,是不是要把架构升级一下呢?
于是,分开的BFF变成了一个统一的服务器端API服务。这就和Ajax原理一样,因为多了一层XHR,导致前后端可以异步处理,促使了Web 2.0的诞生。BFF升级为统一的服务器端API服务,其实也是一样的,让前端和后端可以异步开发,各自做各自的工作,所有API都注册到API层就好了。这其实是前端架构上一次非常大的升级。

GraphQL

GraphQL是Facebook于2012年在内部开发的数据查询语言,在2015年开源,旨在提供RESTful架构体系的替代方案,既是一种用于API的图表化(可视化)查询语言,也是一个满足你数据查询的运行时。

传统Web应用通过开发服务给客户端提供接口是很常见的场景。而当需求或数据发生变化时,应用需要修改或者重新创建新的接口。长此以后,会造成服务器代码的不断增长,接口内部逻辑复杂难以维护。而GraphQL则通过以下特性解决这个问题:

声明式。查询的结果格式由请求方(即客户端)决定而非响应方(即服务器端)决定。你不需要编写很多额外的接口来适配客户端请求。

可组合。GraphQL的查询结构可以自由组合来满足需求。

强类型。每个GraphQL查询必须遵循其设定的类型才会被执行。

也就是说,通过以上的三个特性,当需求发生变化,客户端只需要编写能满足新需求的查询结构,如果服务端能提供的数据满足需求,服务端代码几乎不需要做任何的修改。

GraphQL通过模型定义,对前后端都非常友好,无疑是一种非常好的解决方案。当然,它也有问题,约定本身就不是件容易的事儿,另外对于前后端成本还是有的。

Serverless


那么,如何能够让前端真正解放呢?一不关心运维,二不关系扩容,三不关心Web框架。对于前端开发者而言,我只是想要个接口,或者是包装一个接口,为什么一定要了解Node Web框架呢?

Node.js成也Eventloop败也Eventloop,本身Eventloop是黑盒,开发将什么样的代码堆进去你是很难全部覆盖的,偶尔会出现Eventloop阻塞的情况,排查起来是极为痛苦的。

利用Serverless,可以有效防止Eventloop阻塞。比如加密是常见场景,但本身执行效率是非常慢的。如果加解密和你的其他任务放到一起,是很容易导致Eventloop阻塞的。

本地开发一个函数,然后通过CLI发布到Serverless云上,一切都那么简单,必然是未来趋势。

可以看下面这张图。

image.png

前端工程化未来的理解


CRA、UMI让我们不关心webpack配置,这是本地CLI的未来。

CSR和SSR统一,同构开发,本地CLI打包构建,发布到Serverless云上,简单高效。

API基于Serverless极其简单,不需要了解Web框架,简单组成API即可,无运维,不怕高并发场景。

这里要提一下,在Serverless这种大环境下,如果API解决了,前端自然是可以做所有业务的。前端能做的事儿越来越多,未来前端应用层也会更多。

未来随着经济和业务发展,比如会有更多应用也会更多端,对前端来说是好事,因为能做的更多,价值就能够得到非常好的体现。

所以作为一个坚定的Web信仰者,我相信未来前端的前景一片大好。

更多

原文语雀连接:www.yuque.com/robinson/fe…

如果你想了解更多关于前端工程化的文章,欢迎关注我的语雀专辑,持续收集整理中:www.yuque.com/robinson/fe…

申明:本文是在掘金平台写的最后一篇文章,以后都转移到语雀和博客了。