前端工程化发展历史(译)

毕业前对前端工程化一直没有什么切身的体会,现在工作也有半年多了,体会也越来越深,npmyarnWebpackgulpBabelESlintTypeScript 最近准备一一去深入了解一下,看到一篇不错的关于前端工程化的发展过程,就翻译了一下,How it feels to learn JavaScript in 2016,虽然说是 2016 年的文章,但现在看来依旧不过时,也侧面说明了前端也渐渐趋于稳定了。

以下是全文:


hi,我准备写一个网页项目,但是说实话我已经很多年没有碰过代码了,听说现在行业变化很大。你是我们这里最与时俱进的网页开发者(web dev)了吧。

对的,但更准确的说我是前端工程师(Front End engineer)。可视化、音乐播放器、足球游戏,凡是你能想到的都属于前端开发。我刚刚从JS 大会(JsConf)和 React 大会(ReactConf)回来,因此我知道创造 Web apps 最新的技术。

太棒了!我现在需要写一个展示用户活动的页面,我需要通过 RESTful 接口获取数据,然后展示到可筛选的表格中。我是不是可以用 jQuery 去获取数据和展示?

天哪,不不不,已经没有人再用 jQuery 了。你应该去学习 React,现在已经 2016 年了!

啊,好吧,React 是什么呢?

它是由 Facebook 几个大神创造的一个非常 cool 的框架,它能帮助你轻松的控制视图,更好的管理项目,提升性能。

听起来不错,那我能使用 React 去展示来自服务端的数据吗?

可以的,但你首先得在你的页面中引入 ReactReact Dom 这两个库。

啥?为啥是两个库?

React 是它的核心库,而 React Dom 是用来操控 Dom 的,通常你需要用 JSX 去描述 DOM

JSXJSX 又是啥?

JSX 是一种 JavaScript 的语法扩展,看起来更像 XML。它是描述 DOM 的一种新的方式,比 HTML 会更好。

HTML 表示很无辜。

孩子,已经 2016 年了,没有人直接去写 HTML 了。

好吧,如果我添加了这两个库,是不是就能使用 React 了?

emmm,还不太行。你还需要添加 Babel 这个库。

又一个库?Babel 是啥

Babel 是一个可以帮助你把任意版本的 JavaScript 代码转换成你要的版本。但如果你坚持只使用 ES5 的语法,Babel 也可以不引入。但现实点吧,现在是 2016 年了,你应该向大家一样,使用 ES2016+ 的语法了。

ES5ES2016+?我晕了,它们是些什么。

ES5 代表 ECMAScript 5,它是使用人数最多的一个版本,几乎所有浏览器都支持 ES5 的语法。

ECMAScript

它是在 1999 年提出的一个语言规范,JavaScript 属于其中的一种实现。 JavaScript1995 年提出的,之前还叫过 Livescript,仅仅运行在网景的浏览器中。之前这些非常混乱,现在由于有 ECMAScript7 个版本,一切都变得很清晰了,

7 个版本?那 ES5ES2016+ 属于?

分别是第 5 个和第 7 个版本。

等等,第 6 个版本哪去了?

你的意思是 ES6?由于每个版本相当于之前版本的超集,所以如果使用 ES2016+,之前版本 ES6ES5 所有的特性你就都可以使用了。

好吧,那我可以用 ES6 来编程吗?

当然可以,但你不能使用一些最新的特性,比如 asyncawait。你只能通过 ES6 的生成器和协程来执行「同步」的形式异步请求,感兴趣的话可以看一下 co 框架。

完全听不懂你在说什么了,这些名词我都没有听说过。让我理一理,我只想从服务器加载一段数据,过去我是从 CDN 中拿到 jQuery ,然后通过 AJAX 请求数据就可以了,现在怎么变得那么复杂了?

大哥,已经 2016 年了,没有人再使用 jQuery 了,它只会让你写出意大利面条式的代码。

好吧,所以我需要引入 ReactReact DomBabel 这三个库来拉取数据和展示 HTML 表格吗?

是的,但你还需要用一个模块管理器把这三个库打包成一个文件。

哦哦,那模块管理器又是啥?

它的定义取决于语境,不过在 Web 中,只要支持 AMDCommonJS 模块的话就可以称为模块管理器了。

等等, AMDCommonJS 是?

按照定义来说,他们是描述不同的 javaScript 的库和类模块如何相互作用的不同规范,也就是常说的模块化。你听过 exportsrequire 吗?你可以通过 AMD 或者 CommonJS 编写不同的 js 模块,然后可以使用 Browserify 把这些文件打包起来。

听起来很有道理,但是 Browserify 是什么?

它是一个可以将我们工程依赖的、由 CommonJS 编写的 js 模块打包起来,使其可以运行在浏览器中的工具。之所以有这个工具,是因为我们所依赖的那些模块往往被发布在 npm registry 中。

npm registry

它是一个存放着世界各地的人们编写的代码模块的仓库。

就像是 CDN

不太一样。它更像一个中心仓库,人们可以在上边发布和下载模块。你可以把模块下载下来在本地使用,也可以把它们上传至 CDN 上然后使用。

明白了,就像是 Bower

是的,但现在是 2016 年了,没有人再使用 Bower 了。

哦哦,明白了,那我用 npm 下载所需要的库文件就行了。

是的,如果你想使用 React,你只需要下载 React 模块,然后 import 到你的代码中就可以了。你几乎可以使用 npm 下载现在所有流行的 javaScprit 库。

Angular 也在里边吧?

是的,不过 Augular2015 年的事情了。虽然 Augular 现在也还在用,但 2016 年有了 VueJS 或者 RxJS 这些新的库,你要学一学吗?

算了算了,还是用 React 吧,毕竟我们已经谈了这么多了。所以我如果想使用 React ,只需要从 npm 下载相应的库,然后用 Browserify 打包就可以了吧?

是的。

但这看起来很复杂,需要下载那么多库,然后再它们打包起来。

对,所以你需要使用一个任务管理器来自动化的运行 Browserify,例如 GruntGulp 或者 Broccoli ,甚至可以使用 Mimosa

GruntGulpBroccoliMimosa?我要疯了,这怎么一下这么多东西。

它们都是任务管理器,但现在看起来一点都不 cool 了。我们在 2015 年的时候使用它们,之后还用过 Makefiles ,但是现在我们通过 Webpack 把所有功能都集成在一起了。

Makefiles?这些一般用在 C/C++ 工程中吧?

是的,但是你懂的,在 Web 领域,我们总是喜欢先把事情搞复杂,然后再回归起点。这些年我们总是这样,你等着吧,再过一两年我们肯定就能在 web 上写汇编代码了。

唉,你刚刚讲的 Webpack 是什么呀?

它是另一种浏览器的模块管理器,同时也是一种任务执行器(task runner)。他更像是 Browserify 的升级版。

好吧,它是比 Browserify 更好吗?

也许吧,它可以帮你更好的管理模块之间的依赖。Webpack 允许你使用不同的模块管理器,除了 CommonJS 类型的模块,最新的 ES6 的模块也是支持的。

我完全被 CommonJS/ES6 这些东西搞晕了。

大家都是这样,但通过 SystemJS 的话你就不用关心它们了。

苍天啊,又一个 js 名词,所以 SystemJS 是啥?

Browserify 以及 Webpack 1.x 不同,SystemJS 可以动态加载模块,允许你将不同模块打包成不同文件,而不是打包到一个大文件中。

等等,我认为我们就是应该把所有库打包到一个大文件中,然后加载啊。

是的,但由于 HTTP/2 的时代要来临了,它会支持请求多路复用。

等等,所以我们不能只是把 React 依赖的库放到本地??

也不是。我的意思是我们可以把依赖的库作为外部的脚本从 CDN 中加载,但 Babel 库仍然需要加到本地的。

唉,这听起来是不是不太好。

对的,你需要引入整个 babel-core,对于线上环境来说效率很差。你需要做很多的前置动作才能让项目准备好,压缩资源、混淆代码、内联 css 、延迟加载 js,还有…

明白了,明白了。所以如果不用 CDN 去加载库的话,你会怎么做?

我会使用 Webpack + SystemJS + Babel 的组合从 TypeScript 转化。

TypeScript?我一直以为是用 javaScript 写代码。

TypeScript 就是 javaScript,更准确的说是 javaScript 的超集,或者说更具体点,是 ES6 版本的 javaScript 的超集。

ES2016+ 不已经是 ES6 的超集了,为什么我们还需要使用这个叫 TypeScript 的东西?

因为它允许我们写 javaScript 的时候定义类型,从而减少运行时的错误。现在已经是 2016 年了,是时候在 javaScript 代码中添加类型了。

哈哈,就像它的名字一样,TypeScript

虽然 TypeScriptjavaScript 的超集,但它还需要编译成 javaScript 才能在浏览器运行。而另一种工具 Flow 就仅仅做类型检查,无需编译。

等等,Flow 是啥?

它是 Facebook 的几个人开发的一个静态类型检查器,他们使用 OCaml 语言去写的,因为函数式编程看起来很酷。

OCaml?函数式编程?

这是如今那些 cool kids 使用的,函数式编程、高阶函数、柯里化、纯函数。

哎,我一个也没听过。

没有人一开始就会的。你只需要知道函数式编程比面向对象更好,并且这是 2016 该采取的方式就可以了。

不对吧,我在大学学的是面向对象,这个会更好些吧?

就像 javaOracle 收购前一样好,哈哈,我意思是面向对象过去很辉煌,当然现在依旧很多人在使用。但是现在很多人都意识到修改对象状态是一个太危险的事情了,所以大家都转向了不可变对象和函数式编程。Haskell 语言已经这么做很多年了,但不要和我提 Elm 那些人。幸运的是,原生 javaScript 也可以通过 Ramda 这样的库进行函数式编程。

你不要再罗列名词了,Ramnda 是什么呀?

不,是 Ramda,和 Lambda 表达式类似,它是 David Chambers 创建的库。

David?是谁啊?

David Chambers ,一个大神,喜欢玩 mean Coup game,是 Ramda 的贡献者之一。如果你想更深入的了解函数式编程,你还需要知道 Erik Meijer

Erik Meijer 是?

另一个函数式编程的大神,他有很多演讲抨击过敏捷编程。当然感兴趣的话你还可以去了解 Tj, Jash Kenas, Sindre Sorhus, Paul Irish, Addy Osmani--

等等,对不起打断一下。目前这些对于我来说应该用不到,我只想拉取数据然后展示出来。让我们回到 React,我怎么用 React 从服务器获得数据?

emmm,你不是用 React 获取数据,你只是用它展示数据。

阿西吧,那你通常用什么 fetch the data

你可以用 Fetch 去从服务器 fetch the data

啥?用 Fetch 去从服务器 fetch the data?起这个名字的人真够简单粗暴。

FetchXMLHttpRequests 一样是浏览器的原生实现,是为了从服务器获取数据。

那就是 AJAX 吧?

AJAX 只是基于 XMLHttpRequests 的封装,而 Fetch 可以让你使用 Promise 风格去异步请求数据,从而避免回调地狱。

回调地狱?

就是由于网络请求是异步的,你需要在回调函数里边去获取数据,如果此时又需要网络请求,那就需要在回调函数里再调用网络请求,然后再加回调函数,如果再请求网络…会变得越来越乱。

嗯嗯,我知道这个,所以 promise 可以解决这个问题吗?

是的,通过 promise 你可以更轻松的管理异步请求,写出易于理解的代码,同时调用多个网络请求。

也就是用 Fetch 去写?

是的,但是你得保证你用户的浏览器是最新的,否则你需要 Fetchpolyfill(兼容不能用 Fetch 的浏览器),或者使用 RequestBluebird 或者 Axios

苍天啊,我到底需要知道多少库,怎么还有啊。

这就是 javaScript,有成千上万个库去做同样的事情,当然我们可以从中选出一个最好用的。

那你刚才说的那些库是干什么的呀?

它们是基于 XMLHttpRequests 实现的 promise 风格的请求库。

jQueryAJAX 方法不是也开始返回 promise 了吗?

忘记 jQuery 吧,去用 Fetch + polyfill,或者 RequestBluebirdAxios。我们可以通过它们在 async 函数中 await 异步请求,就像顺序编程一样。

这是你第三次提到 await 了,但我完全不知道它是干啥的。

await 允许你阻塞异步请求,让你更好的控制异步请求,然后处理数据,大大增强了代码的可读性。这非常方便,但你记得要加 stage-3 presetBabel,或者通过 transform-async-to-generator 插件使用 syntax-async-functions

这也太麻烦了。

不不不,真正麻烦的地方在于首先要编译 Typescript 代码,然后再用 Babel 转化才能让 await 被浏览器认识。

啥,Typescript 不支持 await 吗?

1.7 是不支持的,它只会被编译成 ES6,预计下一个版本才会支持。所以你只能先把Typescript 编译成 ES6,然后再通过 Babel 把它转换成 ES5,以便兼容更多的浏览器。

我不知道我还能说什么。

其实挺简单的。就是用 Typescript 写代码,所有模块都用 Fetch 去请求,加上 Babelstage-3 preset ,然后使用 SystemJS 去加载它们。为了让 Fetch 兼容更多浏览器,记得加 polyfill,或者使用 RequestBluebird 或者 Axios,并且使用 await 去等待所有的 promise

我们对简单的定义可能不太一样,,,所以现在我拿到了数据,我就可以用 React 展示数据了吧?

你的应用要控制所有 state 的变化吗?

我觉得不用,我只是需要展示数据。

那太好了,不然我还得向你解释 Flux ,以及它的一些实现,比如 Flummox, Alt, Fluxible。但说实话, Redux 会更好用些。

我不想再知道新的名字了,我只是想展示数据。

哦哦,如果只是展示数据,你其实不需要 React,用一个模版引擎就好了。

你在逗我吗?你觉得这很有趣吗,唉,感情淡了。

我只是想告诉你你能用什么。

那请你不要说了。

其实如果用模版引擎的话,我还是推荐你继续使用 Typescript + SystemJS + Babel 的组合。

那你有推荐的模版吗?

有很多,你之前有用过什么吗?

不太记得名字了,隔的时间太久了。

jTemplates? jQote? PURE?

没有用过,还有吗?

Transparency? JSRender? MarkupJS? KnockoutJS? 这一个支持双向绑定。

还有吗?

PlatesJS? jQuery-tmpl? Handlebars?这些还有些人在用。

有和最后一个比较像的吗?

PlatesJS? jQuery-tmpl? Handlebars? 甚至 lodash 都有一个模版引擎,但这已经是 2014 年的事情了。

那有没有更新一些的?

Jade? DustJS?

没听说过。

DotJS? EJS?

没听说过。

Nunjucks? ECT?

没听说过。

对的,应该没有人喜欢 Coffeescript 的语法了。那 Jade?

你不是说过 Jade 了吗?

我的意思是 Pug,也是 Jade。现在 JadePug 了。

额,我想不起来我用过啥了,你现在用什么模版引擎?

也许会用 ES6 支持的原生模版字符串。

那我捋捋。只有 ES6 支持?

对的。

那我需要用 Babel 来兼容更多的浏览器。

对的。

我需要从 npm 加载它的核心库?

对的。

我还需要 Browerify 或者 Webpack 或者 SystemJS 来管理这些模块?

对的。

除非直接用 Webpack ,不然的话我还需要一个任务管理器。

对的。

由于我要用函数式编程以及强类型的语言,我还需要 Typescript 或者 Flow

对的。

如果要用 awaitBabel 需要进行相应的配置。

对的。

这样我就能使用 FetchPromise 这些神奇的东西了。

对,记得不要忘记给 Fetch 加上 polyfill,有些浏览器目前还不支持这个特性。

好吧,我疯了,今天到这里吧。我不要再碰 Web 了,不要再和我提 javsScript 了。

问题不大,也许未来我们就会使用 Elm 或者 WebAssembly 了。

我还是去写我的后端吧。我觉得我追不上这么多的变化,各种版本号,还有各种编译器和转换器。javaScript 社区真是太疯狂了,它觉得每个人能跟上这么快的变化吗。

哈哈,你应该去了解一下 Python 社区。

为什么?

听过 Python 3 吗?( python 3 没有向前兼容 pyhon 2,差异巨大)


总结一下,前端之所以发生这么大的变化,我觉得一个很关键的点就是 Node.js 的出现。它使得 js 可以脱离浏览器去运行,还提供了读写文件的能力。从而可以在本地进行编译、转换 js 文件,将打包完成的文件运行在浏览器中。

我们可以不去考虑浏览器支持的语法,各种模块化、ES 的新特性,放心大胆的用就可以了,大不了最后再转换就可以了。

此外 node.js 也使得 javaScript 可以写一些服务器端的应用,自己只用它写过一些 Web 接口,其他的了解不多。

windliang wechat