JavaScript開發工具簡明歷史

Fundebug發表於2017-12-05

譯者按: 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進行一些個性化配置。因此,理解每一個工具的作用還是非常有必要的,希望這篇部落格可以幫助大家。

JavaScript開發工具簡明歷史

版權宣告:
轉載時請註明作者Fundebug以及本文地址:
blog.fundebug.com/2017/11/29/…

相關文章