JavaScript开发工具简明历史

译者按: JavaScript 开发要用到的工具越来越多,越来越复杂,为什么呢?你真的弄明白了吗?

本文采用意译,版权归原作者所有

如果你不是老司机,面对众多 JavaScript 开发工具,也许会有些搞不清楚状况。因为,JavaScript 的生态系统在迅速的变化,新手很难理解这些工具的功能以及它们所解决的问题。对此,我深有体会。

我是 1998 开始编程的,但是我直到 2014 才开始学习 JavaScript。当我第一次接触Browserify时,有这样一句介绍:

通过将依赖打包,Browserify 让你可以在浏览器中使用 require(‘modules’)

当时,我完全无法理解这句话,也不知道 Browserify 到底有什么用。

这篇博客将从历史演进的角度,告诉大家今天的 JavaScript 开发工具是怎样发展而来,以及它们到底有什么作用。首先,我们将介绍一个非常简单的网页示例,它是由最原始的 HTML 与 JavaScript 写的。然后,我们会逐步介绍不同的工具,它们可以解决不同的问题。

原始的 JavaScript 使用方式

最原始的网页,是用 HTML 和 JavaScript 编写的,没有那么多幺蛾子。不过,我们需要手动下载并载入依赖的 JavaScript 文件。如下,index.html中载入 1 个 JavaScript 文件:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JavaScript Example</title>
<script src="index.js"></script>
</head>
<body>
<h1>Hello from HTML!</h1>
</body>
</html>

<script src="index.js"></script> 载入了同目录的index.js文件:

// index.js
console.log("Hello from JavaScript!");

这样,一个简单的网页就写好了!

现在,假设你需要使用一个第三方库比如moment.js,这个库可以帮助我们处理时间数据。比如:

moment()
.startOf("day")
.fromNow(); // 20 hours ago

我们需要在的官网下载moment.min.js,放到同一个目录中,然后在index.html中载入:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Example</title>
<link rel="stylesheet" href="index.css" />
<script src="moment.min.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>Hello from HTML!</h1>
</body>
</html>

可知,moment.min.js先于index.js载入,这样我们就可以在index.js中调用 moment 了:

// index.js
console.log("Hello from JavaScript!");
console.log(
moment()
.startOf("day")
.fromNow()
);

总结: 直接使用 HTML 和 JavaScript 库编写网页非常简单,也很容易理解;然而,当 JavaScript 库更新时,我们需要手动下载并载入新版本,这样确实很烦…

npm:包管理工具

大概 2010 开始,数个 JavaScript 包管理工具诞生了,它们旨在通过一个中央仓库,使得下载和更新 JavaScript 库更加自动化。2013 年时,Bower可能是最流行的;到了 2015 年, npm逐渐占据统治地位;而 2016 年,yarn开始逐渐引起关注,但是它的底层是基于 npm 的。还有一点,npm 最初是为node.js开发的,并不是为了前端。因此,使用 npm 管理前端的依赖库显得有点奇怪。

现在,我们来看看如何使用 npm 安装 moment.js 吧。

如果你已经安装了 nodejs,则 npm 也应该安装好了。这时,进入index.html所在目录,执行以下命令:

$ npm init

终端会出现数个问题,仅需使用 enter 键选择默认配置就好了。命令执行之后,会生成一个package.json文件,npm 使用这个文件保存所有的项目信息。默认的package.json是这样的:

{
"name": "your-project-name",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

使用一下命令,即可安装 moment.js:

$ npm install moment --save

这个命令会做两件事情:首先,它会下载 moment.js,将其保存到node_modules目录中;然后,它会更新package.json,保存 moment 安装信息。

{
"name": "modern-javascript-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.19.1"
}
}

这样,当我们需要与其他人分享这个项目时,就不需要将node_modules发送给对方了,而只需要给它package.json文件,因为它可以使用npm install安装所有依赖库。

moment.min.js文件位于node_modules/moment/min目录中,因此我们可以在index.html中直接载入moment.min.js

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JavaScript Example</title>
<script src="node_modules/moment/min/moment.min.js"></script>
<script src="index.js"></script>
</head>
<body>
<h1>Hello from HTML!</h1>
</body>
</html>

总结: 现在,我们不需要手动下载 moment.js 了,而可以通过 npm 自动下载以及更新,这样方便很多;但是,我们需要在node_modules中找到对应的 JS 文件,然后将它载入 HTML,这样很不方便。

顺便分享一个好东西: 如果你需要监控线上 JavaScript 代码的错误的话,欢迎免费使用Fundebug!

webpack - 打包工具

大多数编程语言都提供了模块管理功能,可以在一个文件中导入另一个文件的代码。然而,JavaScript 最初并没有支持这种方式。很长时间以来,组织多个 JavaScript 文件的代码时,需要使用全局变量。我们在载入moment.min.js时,实际上也定义了一个moment全局变量,因此所有之后载入的 JS 文件,都可以使用它。

2009 年,一个叫做 CommenJS 的项目出现了,它为 JavaScript 模块化定义了一个规范,从而允许 JavaScript 能够和其他编程语言一样在不同文件中引入模块。Node.js 是支持 CommenJS 规范的,它可以使用 require 直接引用模块:

// index.js
var moment = require("moment");

console.log("Hello from JavaScript!");
console.log(
moment()
.startOf("day")
.fromNow()
);

这样写非常方便,然而,如果你在浏览器中执行上面的代码,则会收到报错,因为”require 未定义”。

这时,我们就需要打包工具了,它们可以将源代码构建成为兼容浏览器的代码,来避免上面提到的问题。简单地说,打包工具可以找到所有require语句,然后将它们替代为各个 JS 文件中代码,最终生成的一个单独的 JS 文件。

Browserify是 2011 年发布,曾经是最流行的打包工具;到了 2015 年, webpack逐渐成为了最主流的打包工具。

现在,我们来看看如何让require('moment')可以在浏览器中执行。首先,我们需要安装 webpack:

$ npm install webpack --save-dev

--save-dev选项表示 webpack 模块时开发环境中需要的依赖库,而生产环境中并不需要。package.json更新之后是这样的:

{
"name": "modern-javascript-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.19.1"
},
"devDependencies": {
"webpack": "^3.7.1"
}
}

使用一下命令运行 webpack:

$ ./node_modules/.bin/webpack index.js bundle.js

bundle.js为生成的打包文件,可以直接在浏览器中使用:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>JavaScript Example</title>
<script src="bundle.js"></script>
</head>
<body>
<h1>Hello from HTML!</h1>
</body>
</html>

每次修改index.js之后,我们都需要执行 webpack。webpack 的命令比较长,这样很麻烦,尤其是我们需要使用一些高级选项时。这时,我们可以将 webpack 的配置选项写入webpack.config.js文件:

// webpack.config.js
module.exports = {
entry: "./index.js",
output: {
filename: "bundle.js"
}
};

这样,我们直接运行 wepack,而不需要指定任何配置选项,就可以进行打包了:

$ ./node_modules/.bin/webpack

总结: 使用打包工具之后,对于第三方 JS 库,我们不再需要在 HTML 中使用<script>载入,也不需要定义全局变量了,而是直接在 JS 代码中使用require语句。另外,将多个 JS 文件打包成为一个单独的文件也有利于提高网页性能。然而,每次更新代码时,我们都需要手动运行 webpack,这很不方便。

Babel - 新语法特性转码器

转码器可以将代码由一个语言转换为另一个语言,它对于前端开发来说非常重要。浏览器对于语言的新特性支持通常很慢,我们使用新语言特性编写的代码需要转换为兼容的代码才能正常运行。

对于 CSS,转码器有SassLess,以及Stylus。对于 JavaScript,CoffeeScript 曾经是最流行的,而现在用的最多的是babelTypeScript。CoffeeScript 是一门可以编译到 JavaScript 的语言,旨在优化 JavaScript。Typescript 也是一门语言,支持最新的 ECMAScript,并且支持静态类型检查。而 Babel 并非一门语言,而只是一个转码器,可以将 ES6 以及更高版本的 JavaScript 代码转为 ES5 代码,从而兼容各个浏览器。很多人选择 babel,因为它最接近原生的 JavaScript。

现在,我们来看看如何使用 Babel。

首先,我们需要安装 babel:

$ npm install babel-core babel-preset-env babel-loader --save-dev

我们一共安装了 3 个模块:babel-core是 Babel 的核心部分;babel-preset-env定义了转码规则;babel-loader是 Babel 的 webpack 插件。

然后,在webpack.config.js中配置babel-loader即可:

// webpack.config.js
module.exports = {
entry: "./index.js",
output: {
filename: "bundle.js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["env"]
}
}
}
]
}
};

webpack 的配置文件看着有点晕,大致含义是这样的:告诉 webpack 找到所有 js 文件(除了 node_modules 目录中的文件),根据babel-preset-env中的转码规则,使用babel-loader进行转码。至于 webpack 配置的细节,可以查看文档

现在,我们可以开始使用 ES2015 特性编程了。index.js中使用了模板字符串

// index.js
var moment = require("moment");

console.log("Hello from JavaScript!");
console.log(
moment()
.startOf("day")
.fromNow()
);

var name = "Bob",
time = "today";
console.log(`Hello ${name}, how are you ${time}?`);

我们也可以使用import来代替require

// index.js
import moment from "moment";

console.log("Hello from JavaScript!");
console.log(
moment()
.startOf("day")
.fromNow()
);

var name = "Bob",
time = "today";
console.log(`Hello ${name}, how are you ${time}?`);

修改index.js之后,运行 webpack 重新构建代码:

$ ./node_modules/.bin/webpack

其实,现在大多数浏览器都支持了 ES2015 特性,所以你可以测试一下 IE9。在bundle.js中,我们可以看到转码后的代码:

// bundle.js
// ...
console.log("Hello " + name + ", how are you " + time + "?");
// ...

总结: 有了 Babel,我们就可以放心地使用最新的 JavaScript 语法了。但是使用模板字符串这样简单的语法显然没什么意思,所以不妨试试async/await。不过,现在我们还有两个问题需要解决:bundle.js应该需要压缩,这样才能提高性能,这一点很简单;每次修改代码,都需要手动运行 webpack,这样很不方便,下一步我们来解决这个问题。

npm scripts - 任务管理工具

任务管理工具可以将一些重复性的任务自动化,比如合并文件、压缩代码、优化图片以及运行测试等。

2013 年时,Grunt 是最流行的任务管理工具,其次是 Gulp。现在,直接使用 npm 的 scripts 功能的开发者似乎越来越多了,这样不需要安装额外的插件。

修改package.json,即可配置 npm scripts:

{
"name": "modern-javascript-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --progress -p",
"watch": "webpack --progress --watch"
},
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.19.1"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"webpack": "^3.7.1"
}
}

我们定义了 2 个 scripts,即 build 和 watch。

运行 build,即可构建代码了(- -progress 选项可以显示构建进程,-p 选项可以压缩代码):

$ npm run build

运行 watch,则一旦 javascript 修改了,就会自动重新运行 wepback,这样开发就方便多了:

$ npm run watch

还有,我们可以 webpack-dev-server,它可以提供一个网页服务器,而且能够自动重载页面:

$ npm install webpack-dev-server --save-dev

修改package.json:

{
"name": "modern-javascript-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --progress -p",
"watch": "webpack --progress --watch",
"server": "webpack-dev-server --open"
},
"author": "",
"license": "ISC",
"dependencies": {
"moment": "^2.19.1"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1",
"webpack": "^3.7.1"
}
}

运行:

$ npm run server

这时,浏览器会自动打开localhost:8080,并访问index.html。当我们修改index.js时,代码会自动重新构建,并且页面也会自动刷新。这样我们修改代码之后,就可以看到浏览器中的效果,而不需要任何额外的操作。

正如前文提到过,npm scripts 或者其他任务管理工具可以做的事情还有很多,感兴趣的话,可以看看这个视频

结论

简单总结一下:刚开始我们用HTML 和 JS写代码;后来我们用包管理工具来安装第三方库;然后我们用打包工具实现模块化;再后来我们用转码器从而使用最新语法;最后我们用任务管理工具来自动化一些重复的任务。对于新手来说,这一切都显得非常头疼,更头疼的是这一切还在不断变化之中。

当然也有好消息,各个框架为了方便初学者,都会提供工具,把所有配置都弄好: Ember 有ember-cli,Angular 有angular-cli, React 有create-react-app, Vue 有vue-cli。这样,似乎你什么都不用管,只需要写代码就可以了。然而,现实是残酷的,总有一天你需要对 Babel 或者 Webpack 进行一些个性化配置。因此,理解每一个工具的作用还是非常有必要的,希望这篇博客可以帮助大家。

关于Fundebug

Fundebug专注于JavaScript、微信小程序、支付宝小程序线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了80亿+错误事件。欢迎大家免费试用

版权声明

转载时请注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/29/history-of-javascript-tools/

您的用户遇到BUG了吗?

体验Demo 免费使用