本文首发于我的个人博客: https://teobler.com ,转载请注明出处
我们怎么使用JS
众所周知,我们在HTML文件中使用JavaScript只能通过script
标签来引入:
<script src="./index.js"></script>
<script>
console.log('Hello World');
</script>
如果只是这样用,有什么问题呢?这样引入JavaScript是没办法大量引入的,什么算大量呢?也不用多,加入某个页面我需要20个JavaScript文件,怎么办?好像我只能加20个script
标签,然后一个一个去请求。但是这样的话首先会使页面加载变慢,你需要在加载页面后去请求大量的script,更为重要的是,浏览器是有请求限制的:
浏览器只允许一定数量的请求能够 'fetch data',所以如果在同一时刻发起大量的请求的话,对于浏览器来说会有相当严重的性能问题。可能有人会说,9102年了,我用HTTP/2啊,没问题。是,对于大部分情况来说的确没有问题了,name对于特殊情况呢?比如Airbnb和MS的outlook,他们用超过3000个modules去进行构建。
咋办呢?那我把所有需要的JavaScript代码写到同一个文件里就好了嘛,是的,历史就是这么发展的,所以老程序员们可能都见到过那种一万十万行的巨大的JavaScript文件。这种办法的确缓解了上述问题,可是,这样的文件,我还需要讲缺点嘛?不说别的,假如我用到的某个function有问题,我想debug一下,咋整呢?闹呢?我在第一行定义了一个变量,我要去第一万行找它,然后这他么还是一个全局变量,咋玩呢?
然后呢,万能的前端同学们想到了一个办法 — IIFE 啥是IIFE呢 — immediately invoked function expression,中文是立即执行函数,它可以干啥用呢:
var outerScope = 1;
const whatever = (function(dataWillUsedInside){
var outerScope = 0;
return {
someAttribute: 'youWantThis'
}
})(0);
// 1
console.log(outerScope);
这下游戏规则好像变简单了一点了,我们可以自己写很多JS文件,然后把它们封装成一个个的IIFE,然后封装到一个文件里面,好了,解决了一个问题,至少变量污染的问题解决了嘛。然后呢,开始进入工具时代了,这个时候大家开始寻找各种各样的工具 — make, grunt, gulp, broccoli...
那么新的问题又来了 — 如果我想修改下某个文件,然后呢,我需要编译所有的文件,包括那些我都没有动过的。第二个问题是,比如我想引某个库,按需引用?不存在的,我只能一坨的引进来,这就有点可怕了,尤其还是以当时的网络状况来看。而且说不定你还需要引好多个库呢,那也只能全部放进来。而且这样编写成IIFE的“文件”由于各种原因会导致你的网页加载很慢。
JS的模块化
所以这个时候前端的同学们又开始想办法了,我们得有模块化这个东西呀。Node其实就是把V8“拿”到了server端,那么问题来了,没有了browser,没有了DOM,我们还怎么使用JS?所以Node.JS的出现带来了模块化,带来了CommonJS。
// index.js
const path = require('path');
const [add, subtract] = require(''./math');
/*
* math.js (has two named exports [add, subtract])
*/
const divideFn = require('./division');
exports.add = (first, second) => first + second;
exports.subtract = (first, second) => first - second;
exports.divide = divideFn;
/*
* dibision.js (has a exports 'divide')
*/
module.exports = (first, second) => first / second;
上面的代码是一个简单的CommonJS的例子,有三个文件,他们可以通过使用require
相互引用。你可以使用匿名的默认exports,也可以使用具名的导出。之后再在别的文件内以特定的语法导入。所以到现在我们就解决了作用域的问题,我们不再需要IFFE了,这个时候我们不用再担心变量污染的问题了,同时也解决了IFFE的缺点。而同时出现的NPM更是让各个开发者自己写的各种包能够让每一个人使用。
可是还有问题,这玩意儿是Node的,不支持浏览器。而且如果你是一个写过别的语言的程序员的话你也应该知道动态绑定,但是CommonJS并不支持动态绑定。于是自我引用和循环引用层出不穷。而且他的同步算法贼慢。又为了解决这些问题,出现了一些有意思的东西:browserify, requireJS, systemJS…这些工具的目的很简单,就是让你能够在浏览器里面使用CommonJS。但是它们并不支持静态引入,也就是说你要用某个库你还是只能全部引进来。而且也并不是所有人在写库的时候都会使用CommonJS,毕竟还有个AMD不是吗。所以可以说这套玩意儿不能算是'module system'。
于是ES Module出现了。据说与之相关的文档,在1998年就能看到,所以这是一个断断续续设计并开发了大概10多20年的玩意儿,不过他的语法更友好了些,至少不是一些require
之类的让人看到一头雾水的词语了:
import {uniq, forOf, bar} from 'lodash-es';
import * as utils from 'utils';
export const uniqConst = uniq([1, 2, 3, 4]);
现在看起来我们好像有了一个完整的module system了,重用,封装这些基本特性看起来都还不错。但是呢?几个问题又来了:这玩意儿好像挺难使用在Node里面的 — 这也是现在好多团队正在努力的方向;而且它直接在浏览器里面使用的话 — 非常非常慢,慢到你以为网站挂了,而且不用多,10个modules就能达到这个效果。因为在浏览器从上到下读取这个JS文件的时候,首先遇到import
,然后去找这个包在哪,找到相应的路径,然后验证一下这些东西还能不能用,最后把这个文件读进来,然后继续在这个文件里重复这个步骤 — 一直到所有的依赖都读完了。需要注意的是,这一切都是在runtime
完成的,也就是在加载你的网页的时候。
webpack横空出世
之前我们提到过,一个NPM中的包可能是用的不同的module format,你不能说哪一种是不对的不能使用的,所以在面对不同的方法的时候你也需要使用不同的使用方法。而且需要说明的是,上面我们说的所有东西都只是关于JS的,别忘了一个web app还有CSS还有各种静态资源。我们需要的是一个支持所有module format,并且同时还能支持除了JS以外的别的文件的一个“系统”或者说工具。
webpack是什么?
webpack is a module bundler lets you write any module format(mixed also), compiles then for the browser. And it supports static async bundling.
很简单有很强大的定义对吧,它几乎解决了上面所有的问题,那么它是怎么被创造出来的呢?这是一个有意思的小故事。
2012年,一个叫做Tobias的,在Newberg(美国一个城市)读master的德国人要写一片学位论文。他之前是写c#的,从来没有写过一个web界面。他在一些特定的场景需要用到Google Web Toolkit中的一个叫做code splitting的功能。而在他的论文中他需要写一个web app,他就想找一个包含这个功能的库来用。他找到的这个库叫webmake,这也是一个bundler。但是却没有code splitting这个功能,于是他提了一个issue,并且写了一堆如何实现这个功能的代码,希望维护者能够加入这个功能。在一番讨论过后维护者拒绝了他,于是在经过同意之后,他把这个库fork到了了过去并自己加上了这个功能,给新的库取名为webpack。
2014年,Dan Abramov在Stack Overflow上提了一个关于hot module replacement的问题,Tobias用很大的篇幅给他介绍了这个还在开发的功能,详细解释了这个功能怎么在webpack里工作的,以及这个功能有多棒,你可以不用刷新浏览器了!
2015年,这时在Instagram工作的Pete Hunt通过一次演讲告诉了世界他们是如何使用webpack打包发布他们的react app的。然后你懂得,webpack就火了。像Facebook这样的公司也开始使用webpack了。但是其实Tobias只是每周大概花5 6个小时在webpack中。
是的,在这两个讨论中,webpack彻底火了,走向了世界。
中国有句话叫做“以史为鉴,可以知兴替”。在了解这些历史的时候除了觉得很有意思,也会有一些思考:在历史的大潮里,有多少人是可以做那个改变方向的人?又有多少人死在了历史的长河里?要想不被淹死,只有奋力向上。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。