前言
本文来自极客前端训练营的主题公开课,非原创。
桑世龙(狼叔),阿里巴巴前端技术专家,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)等等,各种框架格式如雨后春笋一样冒出来,以前可能半年甚至更长时间才出一个框架,现在可能几周就有新的框架诞生,前端进入了空前的爆发阶段。
现在前端开发也复杂到了一定程度,对比之前的开发方式,可以称为现代Web开发。这里我来做一个总结,前端发展经历了4个阶段:
目前整个大前端还属于发展期,没有形成固定模式,所以从趋势上看是上升期,复杂、混乱、多样性的结果也导致现在的前端比较吃香,被大家戏称为“钱端”。很多同学会问,学习前端技术是采用渐进式方式,还是上来就直接学React/Vue.js等框架呢?实际上,我认为这两种方式都存在,如果有经济压力就向“钱“看,可以直接学习React/Vue.js等框架,但学会了之后,一定要把其他3个阶段补充学习完;如果没有经济压力,又有时间和耐心的话,循序渐进的学习是最好的方式。编程没有捷径,无论是哪种,都需要脚踏实地多多练习。
当你代码写多了,就会发现:
于是,前端开发变得复杂,并进入现代Web开发时代。
对于Node.js来说,所有前端框架都是视图层(View)的展现技术而已,可以非常方便地和各种框架集成,并按照业务需求予以更好的实现。另外,所有的前端框架都采用Node.js和NPM作为辅助开发工具,使用了大量Node.js模块,但前端框架使用的模块大同小异,如果熟练掌握了Node.js和NPM,对于学习前端技术来说,你要学的只是纯前端的部分而已,复用价值非常高。
和Node.js相关的前端开发模块实在是太多了,这里简单列举一些:
由这些模块,也就引出了我们今天要聊得话题——大前端工程化实践与思考。
先来说说构建工具。预处理器是前端高级玩法。
我经常开玩笑说以前HTML/JavaScript/CSS的时代太纯洁了,现在随便写哪样都要编译/转译。好处是可以借助高级特性,提高开发效率;缺点也是极其明显的,就是人脑要有转换思维。这其实是蛮痛苦的,本来HTML/JavaScript/CSS就不够熟练,再转一次,对于新手来说需要一个适应过程。所以这件事还是要辩证地看,福祸相依。
说起构建工具,大概都会想到Make、Ant、Rake、Gradle等,其实Node.js世界里有更多实现。
选用构建工具最主要的目的是为了自动化。对于需要反复重复的任务,例如压缩(minification)、编译、单元测试、linting等,自动化工具可以减轻你的劳动,简化你的工作。尤其是工程越复杂,自动化的价值就越大。这里的编译包含模板、CSS预处理语言、JavaScript友好语言等编译,在源码编写时用的是高级玩法。
除了编码编译外,还有测试、代码风格检查、上线前优化(合并、压缩、混淆),可以说,构建系统在整个软件工程里无处不在。
Grunt除了配置复杂外,还有一个问题就性能,可能很多同学遇不到性能问题。Grunt是读写文件的,所以在多文件、大文件处理时是有性能瓶颈的。
Grunt的设计还是挺精巧的,但配置起来让人抓狂,自然会有很多不爽的人,于是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开发里经过了很多尝试,也做过很多龌蹉的事儿,比如
如果说加载比较恶心,那么脚本顺序更恶心,而且JavaScript有个“特性”,一处报错,所有后面的都会崩溃,所以开发会很苦的维护脚本加载的顺序。
很明显,直接进行文件操作是非常低效率的做法,对于版本、依赖都没法做更好的处理。于是就出现了Bower、NPM等包管理器,所有模块升级,依赖都有包管理负责,可以说很大程度上省去了前端的重复性工作。
模块化加载的本质是按需加载。
模块加载器需要实现两个基本功能:
常见的比如RequireJS、Sea.js和SystemJS。以AMD的模块加载器RequireJS为例,通常使用RequireJS的话,我们只需要导入RequireJS即可,不需要显式导入其他的JS库,因为这个工作会交给RequireJS来做。
对于开发来说,只需要关注业务模块,不需要了解模块加载器和构建过程,很明显这是非常理想的,也因此产生了webpack这样的模块打包器。
Gulp作为通用构建工具,它已经非常完美了。但技术变革太快了,应用各种预处理器、前端组件化,导致前端无比复杂,而webpack的出现刚刚好,完美解决了前端工程化的问题。
webpack是Node编写的著名模块,是打包器(bundler),不只是支持CommonJS模块,而且还支持更潮的ES6模块,是目前使用极其广泛的打包器,像前端组件化的框架(React、Vue)大多都是使用webpack的。
它提供了两个极其好的机制:loaders和plugins。
loaders:webpack认为每个文件都是资源模块,针对打包构建过程中用来处理源文件的(JSX、SCSS、Less..)统称为loader。
plugins:插件可以完成更多loader不能完成的功能。插件并不直接操作单个文件,它直接对整个构建过程起作用,大多数内容功能都是基于这个插件系统运行的;当然还可以开发和使用开源的webpack插件,来满足各式各样的需求。比如知名插件autoprefixer、html-webpack-plugin、webpack-dev-middleware、webpack-hot-middleware。
(1)通过加载webpack打包后的文件
如果对比着理解webpack打包过程和浏览器加载过程,可以对前端模块化的演讲过程能够有更好的了解。从定义模块规范,到模块化系统的运行时环境(加载器),再到更高级的打包器,这个演进的过程,也让前端开发越来越简单,这才是打包器越来越火爆的原因。
事实上从结果来看也是这样的,工程化最终目的是让开发者专注于写业务模块,其他的事情让打包器来做。从这个角度讲,webpack还是比Gulp更成功。
但是webpack学习曲线也是比较陡峭的,以点打面、不断深究,是非常好的学习方式。从基本用法、概念,到tree-shaking、code spit,再到如何打包、浏览器如何解包,到工程化,到大规模构建如何优化,考验你的是基础是否扎实、原理是否真懂、是否了解优化思路和源码。作为类似模块与Gulp进行对比,到Stream(Gulp核心),到event(Stream基类),到HTTP(Stream在HTTP里应用),到eventloop,到C++实现的libuv和v8,太可怕了,几乎把《狼书》的所有内容都涵盖了。
af-webpack是支付宝定制的webpack,把webpack-dev-server等Node.js模块直接打包进去,同时对配置做了更好的处理,以及插件化。
ykit是去哪儿开源的webpack,内置Connect作为Web server,结合dev和hot中间件,对于多项目构建提效明显,对版本文件发布有不错的实践。
easywebpack也是插件化,但对解决方案如boilerplate等做了非常多的集成,比如egg的ssr是有深入思考的,尽管我不赞同这种做法。
在Create React App(CRA)项目里使用的是react-scripts作为启动脚本,它和egg-scripts类似,也都是通过约定,隐藏具体实现细节,让开发者不需要关注构建。
UMI和CRA类似,它是一套基于蚂蚁金服技术栈总结的最佳实践,是一套零配置“约定高于配制“,开箱即用,按最佳实践进行开发的前端框架:React全家桶 + dva + jest + antd (mobile) + less + eslint。
UMI思考得相对全面,从技术选型、构建,到多端输出、性能优化、发布等方面进行了拆分,使得UMI的边界更为清晰,作为前端最佳实践没啥毛病,目前大多数前端组也都是类似的实现方式。说白了就是现有技术栈的组合,封装细节,让开发者用起来更简单。在未来,类似的封装还会有更多,毕竟框架层面创新不那么多,大家就会将精力慢慢转到应用层面。
它的问题在于包的依赖有点大,对于强迫症开发者来说接受不了。于是Yargs这类的模块应运而生,强大且依赖极少。
核心是ownload-git-repo,然后对里面的文件进行模板处理。这种方式的灵活度非常高,是目前比较主流的方式。
前后端分离,即使用JavaScript运行在客户端,通过请求获取服务端接口数据,借助如JQuery、Angular、React、Vue等前端框架操作或生成页面DOM,充分利用客户端资源,减少服务端压力,前后端分工明确,一直到现在仍是最常用的开发方式。
同构开发,如Meteor、Next.js、Nuxt.js等框架,都提供了不同的适用场景和开发方式,但目的都是为了同一套代码能同时应用于服务端和客户端。
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… 。它的写法是:
核心要点有:
在未来,我们希望所有服务都走Serverless,这样用户只需要关注React写法即可。构建是本地做的,类似于UMI。egg不需要关系,因为走的是Serverless的运行时环境。
CSR和SSR写法一致,又可以上Serverless,它的优点有:
上了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(API Proxy),独立的API层的出现原因有很多:
移动端兴起,大家都开始面向API开发,但却忽略了大前端不只只有移动端,还有PC Client和PC/H5 Web。主要是屏幕大小不同而导致UI/UE上有明显差别。
后端开发API的时候,不喜欢同时维护多套API。
除了沟通成本,还有对前端实现不了解而产生的误解。
简单理解,BFF就是各端的API独立提供了。但是,这样做很明显不经济。那么,是不是要把架构升级一下呢?
GraphQL是Facebook于2012年在内部开发的数据查询语言,在2015年开源,旨在提供RESTful架构体系的替代方案,既是一种用于API的图表化(可视化)查询语言,也是一个满足你数据查询的运行时。
传统Web应用通过开发服务给客户端提供接口是很常见的场景。而当需求或数据发生变化时,应用需要修改或者重新创建新的接口。长此以后,会造成服务器代码的不断增长,接口内部逻辑复杂难以维护。而GraphQL则通过以下特性解决这个问题:
声明式。查询的结果格式由请求方(即客户端)决定而非响应方(即服务器端)决定。你不需要编写很多额外的接口来适配客户端请求。
可组合。GraphQL的查询结构可以自由组合来满足需求。
强类型。每个GraphQL查询必须遵循其设定的类型才会被执行。
也就是说,通过以上的三个特性,当需求发生变化,客户端只需要编写能满足新需求的查询结构,如果服务端能提供的数据满足需求,服务端代码几乎不需要做任何的修改。
GraphQL通过模型定义,对前后端都非常友好,无疑是一种非常好的解决方案。当然,它也有问题,约定本身就不是件容易的事儿,另外对于前后端成本还是有的。
Node.js成也Eventloop败也Eventloop,本身Eventloop是黑盒,开发将什么样的代码堆进去你是很难全部覆盖的,偶尔会出现Eventloop阻塞的情况,排查起来是极为痛苦的。
利用Serverless,可以有效防止Eventloop阻塞。比如加密是常见场景,但本身执行效率是非常慢的。如果加解密和你的其他任务放到一起,是很容易导致Eventloop阻塞的。
本地开发一个函数,然后通过CLI发布到Serverless云上,一切都那么简单,必然是未来趋势。
可以看下面这张图。
CSR和SSR统一,同构开发,本地CLI打包构建,发布到Serverless云上,简单高效。
API基于Serverless极其简单,不需要了解Web框架,简单组成API即可,无运维,不怕高并发场景。
这里要提一下,在Serverless这种大环境下,如果API解决了,前端自然是可以做所有业务的。前端能做的事儿越来越多,未来前端应用层也会更多。
未来随着经济和业务发展,比如会有更多应用也会更多端,对前端来说是好事,因为能做的更多,价值就能够得到非常好的体现。
所以作为一个坚定的Web信仰者,我相信未来前端的前景一片大好。
热 文 推 荐
☞ Webpack 4 动态切割JS注入文件名
☞ 你在公司项目里面看过哪些操蛋的代码?
☞ CSS的一些你可能不知道的强大技巧
☞ 为什么视频网站的视频链接地址是blob?
☞ 搭建一个 nodejs 脚手架
你也“在看”吗?