JavaScript開發(fā)工具簡明歷史

譯者按: JavaScript開發(fā)要用到的工具越來越多,越來越復雜,為什么呢?你真的弄明白了嗎?

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用于學習。

如果你不是老司機,面對眾多JavaScript開發(fā)工具,也許會有些搞不清楚狀況。因為,JavaScript的生態(tài)系統(tǒng)在迅速的變化,新手很難理解這些工具的功能以及它們所解決的問題。對此,我深有體會。

我是1998開始編程的,但是我直到2014才開始學習JavaScript。當我第一次接觸Browserify時,有這樣一句介紹:

通過將依賴打包,Browserify讓你可以在瀏覽器中使用require(‘modules’)

當時,我完全無法理解這句話,也不知道Browserify到底有什么用。

這篇博客將從歷史演進的角度,告訴大家今天的JavaScript開發(fā)工具是怎樣發(fā)展而來,以及它們到底有什么作用。首先,我們將介紹一個非常簡單的網(wǎng)頁示例,它是由最原始的HTML與JavaScript寫的。然后,我們會逐步介紹不同的工具,它們可以解決不同的問題。

原始的JavaScript使用方式

最原始的網(wǎng)頁,是用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!");

這樣,一個簡單的網(wǎng)頁就寫好了!

現(xiàn)在,假設你需要使用一個第三方庫比如moment.js,這個庫可以幫助我們處理時間數(shù)據(jù)。比如:

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

我們需要在的官網(wǎng)下載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庫編寫網(wǎng)頁非常簡單,也很容易理解;然而,當JavaScript庫更新時,我們需要手動下載并載入新版本,這樣確實很煩...

npm:包管理工具

大概2010開始,數(shù)個JavaScript包管理工具誕生了,它們旨在通過一個中央倉庫,使得下載和更新JavaScript庫更加自動化。2013年時,Bower可能是最流行的;到了2015年, npm逐漸占據(jù)統(tǒng)治地位;而2016年,yarn開始逐漸引起關注,但是它的底層是基于npm的。還有一點,npm最初是為node.js開發(fā)的,并不是為了前端。因此,使用npm管理前端的依賴庫顯得有點奇怪。

現(xiàn)在,我們來看看如何使用npm安裝moment.js吧。

如果你已經(jīng)安裝了nodejs,則npm也應該安裝好了。這時,進入index.html所在目錄,執(zhí)行以下命令:

$ npm init

終端會出現(xiàn)數(shù)個問題,僅需使用enter鍵選擇默認配置就好了。命令執(zhí)行之后,會生成一個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發(fā)送給對方了,而只需要給它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>

總結: 現(xiàn)在,我們不需要手動下載moment.js了,而可以通過npm自動下載以及更新,這樣方便很多;但是,我們需要在node_modules中找到對應的JS文件,然后將它載入HTML,這樣很不方便。

順便分享一個好東西: 如果你需要監(jiān)控線上JavaScript代碼的錯誤的話,歡迎免費使用Fundebug!

webpack - 打包工具

大多數(shù)編程語言都提供了模塊管理功能,可以在一個文件中導入另一個文件的代碼。然而,JavaScript最初并沒有支持這種方式。很長時間以來,組織多個JavaScript文件的代碼時,需要使用全局變量。我們在載入moment.min.js時,實際上也定義了一個moment全局變量,因此所有之后載入的JS文件,都可以使用它。

2009年,一個叫做CommenJS的項目出現(xiàn)了,它為JavaScript模塊化定義了一個規(guī)范,從而允許JavaScript能夠和其他編程語言一樣在不同文件中引入模塊。Node.js是支持CommenJS規(guī)范的,它可以使用require直接引用模塊:

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

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

這樣寫非常方便,然而,如果你在瀏覽器中執(zhí)行上面的代碼,則會收到報錯,因為"require未定義"。

這時,我們就需要打包工具了,它們可以將源代碼構建成為兼容瀏覽器的代碼,來避免上面提到的問題。簡單地說,打包工具可以找到所有require語句,然后將它們替代為各個JS文件中代碼,最終生成的一個單獨的JS文件。

Browserify是2011年發(fā)布,曾經(jīng)是最流行的打包工具;到了2015年, webpack逐漸成為了最主流的打包工具。

現(xiàn)在,我們來看看如何讓require('moment')可以在瀏覽器中執(zhí)行。首先,我們需要安裝webpack:

$ npm install webpack --save-dev

--save-dev選項表示webpack模塊時開發(fā)環(huán)境中需要的依賴庫,而生產(chǎn)環(huán)境中并不需要。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之后,我們都需要執(zhí)行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文件打包成為一個單獨的文件也有利于提高網(wǎng)頁性能。然而,每次更新代碼時,我們都需要手動運行webpack,這很不方便。

Babel - 新語法特性轉碼器

轉碼器可以將代碼由一個語言轉換為另一個語言,它對于前端開發(fā)來說非常重要。瀏覽器對于語言的新特性支持通常很慢,我們使用新語言特性編寫的代碼需要轉換為兼容的代碼才能正常運行。

對于CSS,轉碼器有SassLess,以及Stylus。對于JavaScript,CoffeeScript 曾經(jīng)是最流行的,而現(xiàn)在用的最多的是babelTypeScript。CoffeeScript是一門可以編譯到JavaScript的語言,旨在優(yōu)化JavaScript。Typescript也是一門語言,支持最新的ECMAScript,并且支持靜態(tài)類型檢查。而Babel并非一門語言,而只是一個轉碼器,可以將ES6以及更高版本的JavaScript代碼轉為ES5代碼,從而兼容各個瀏覽器。很多人選擇babel,因為它最接近原生的JavaScript。

現(xiàn)在,我們來看看如何使用Babel。

首先,我們需要安裝babel:

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

我們一共安裝了3個模塊:babel-core是Babel的核心部分;babel-preset-env定義了轉碼規(guī)則;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目錄中的文件),根據(jù)babel-preset-env中的轉碼規(guī)則,使用babel-loader進行轉碼。至于webpack配置的細節(jié),可以查看文檔

現(xiàn)在,我們可以開始使用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

其實,現(xiàn)在大多數(shù)瀏覽器都支持了ES2015特性,所以你可以測試一下IE9。在bundle.js中,我們可以看到轉碼后的代碼:

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

總結: 有了Babel,我們就可以放心地使用最新的JavaScript語法了。但是使用模板字符串這樣簡單的語法顯然沒什么意思,所以不妨試試async/await。不過,現(xiàn)在我們還有兩個問題需要解決:bundle.js應該需要壓縮,這樣才能提高性能,這一點很簡單;每次修改代碼,都需要手動運行webpack,這樣很不方便,下一步我們來解決這個問題。

npm scripts - 任務管理工具

任務管理工具可以將一些重復性的任務自動化,比如合并文件、壓縮代碼、優(yōu)化圖片以及運行測試等。

2013年時,Grunt是最流行的任務管理工具,其次是Gulp。現(xiàn)在,直接使用npm的scripts功能的開發(fā)者似乎越來越多了,這樣不需要安裝額外的插件。

修改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,這樣開發(fā)就方便多了:

$ npm run watch

還有,我們可以webpack-dev-server,它可以提供一個網(wǎng)頁服務器,而且能夠自動重載頁面:

$ 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寫代碼;后來我們用包管理工具來安裝第三方庫;然后我們用打包工具實現(xiàn)模塊化;再后來我們用轉碼器從而使用最新語法;最后我們用任務管理工具來自動化一些重復的任務。對于新手來說,這一切都顯得非常頭疼,更頭疼的是這一切還在不斷變化之中。

當然也有好消息,各個框架為了方便初學者,都會提供工具,把所有配置都弄好: Ember有ember-cli,Angular有angular-cli, React有create-react-app, Vue有vue-cli。這樣,似乎你什么都不用管,只需要寫代碼就可以了。然而,現(xiàn)實是殘酷的,總有一天你需要對Babel或者Webpack進行一些個性化配置。因此,理解每一個工具的作用還是非常有必要的,希望這篇博客可以幫助大家。

版權聲明:
轉載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/11/29/history-of-javascript-tools/

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容