ECMAScript 6 是 JavaScript 的最新版本,它帶來了很多新特性——一些是社群乞求已久的,一些是頗具爭議的。但不管你如何看待 ES6,都應該學習並使用它——因為就是這麼酷。由於能在伺服器端控制 JavaScript 版本,所以 ES6 在 node.js 或其它服務端框架執行良好,但在客戶端中則不盡人意了。從一個複雜的 angular 或 ember 應用到一個簡單的靜態頁面基本都含有幾行 jQuery 程式碼。
難道這就讓我們向命運低頭嗎?難道註定要因為瀏覽器不得不接受舊版本的 JavaScript 嗎?不,我們不必接受這種限制!我們還有別的選擇——現在最佳的選擇是編寫 ES6 程式碼,並將其轉譯(transpile)或編譯為 ES5 程式碼(相容所有現代瀏覽器,如IE9+)。Babel 是主流的轉譯工具,它讓轉譯過程變得簡單,我們打算讓這過程自動化,所以我們甚至可以不用考慮上述的限制!
長話短說
如果你只想要簡潔的描述和本文所用到的純程式碼,下面就是:
我們將會建立一個 gulpfile 去監聽檔案的更改,並用 Babel 將 ES6 程式碼自動轉換到 CommonJS,然後用 Browserify 將 CommonJS 轉為 合法的 ES5。當然,我們也會新增額外的外掛,如 UglifyJS (為了最小化),source maps 和 livereload —— 因為這些工具都超級有用。
簡單粗暴,下面是完整的 gulpfile 檔案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var gulp = require('gulp'); var browserify = require('browserify'); var babelify = require('babelify'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); var livereload = require('gulp-livereload'); gulp.task('build', function () { // app.js is your main JS file with all your module inclusions return browserify({entries: './src/js/app.js', debug: true}) .transform("babelify", { presets: ["es2015"] }) .bundle() .pipe(source('app.js')) .pipe(buffer()) .pipe(sourcemaps.init()) .pipe(uglify()) .pipe(sourcemaps.write('./maps')) .pipe(gulp.dest('./dist/js')) .pipe(livereload()); }); gulp.task('watch', ['build'], function () { livereload.listen(); gulp.watch('./src/js/*.js', ['build']); }); gulp.task('default', ['watch']); |
這沒你想象中糟糕吧!我們在上面嘗試了很多不同的東西。如果你有興趣學習它們,我會鼓勵你繼續往下閱讀,去了解它們是如何執行和為什麼會這樣執行的,這無疑會為瀏覽器構建出更可靠的 ES6 程式碼。
開始我們的 Gulpfile
我們將選擇 gulp 作為自動化構建工具。gulp 是基於 JavaScript 編寫的工具,它能執行特定任務,並監聽檔案是否被更改。當然,這取決於你在本地專案的目錄下的 gulpfile.js 配置檔案。除了 gulp,還有其它自動化構建工具(grunt 也是流行的構建工具),但由於 gulp 基於 node 的流(streams)實現,因此速度更快。
首先,我們初始化 package.json,並本地安裝 gulp(如果你未曾安裝,也可全域性安裝):
1 2 |
npm init # Answer all the questions npm install --save-dev gulp |
然後,建立一個空白的 gulpfile.js,並填充如下骨架:
1 2 3 4 5 6 7 8 9 10 11 |
var gulp = require('gulp'); gulp.task('build', function () { }); gulp.task('watch', ['build'], function () { gulp.watch('./src/js/*.js', ['build']); }); gulp.task('default', ['build', 'watch']); |
上面沒什麼特別的地方。我們建立了空的 build、watch 和 default 任務。build 任務應包含所有構建 JavaScript 程式碼的邏輯。watch 任務監控 src/js 資料夾下的檔案是否被更改(構建後的檔案會放在 dist/js 下)。
執行以上這些任務:
1 2 3 |
gulp build # Build gulp watch # Watch gulp # Default |
到目前為止一切良好,對吧?下面就實際新增 ES6-to-ES5 構建處理。
執行 Babel 和 Browserify
在“長話短說”中我們簡單討論到,結合使用 babel 和 browserify 將 ES6 程式碼轉成瀏覽器能執行的 ES5 程式碼。Babel 能將 ES6 轉為 CommonJS,而 CommonJS 是現今 JavaScript 最常用的模組模式(特別是在 node.js)。但現今瀏覽器仍不能識別 CommonJS。因此,我們需要使用 browserify 將 CommonJS 轉為合法的 ES5。有了這兩個工具,我們能走得更順利。
然而我們不能直接使用 babel。取而代之的是 babelify 庫,它是作為 browserify 的轉換器——這意味著在 browserify 編譯前,它會預處理 JavaScript。那我們先安裝這些包(package):
1 |
npm install --save-dev babelify babel-preset-es2015 browserify vinyl-source-stream |
現在,為了構建 ES6 程式碼,我們將 gulpfile 檔案更改為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var gulp = require('gulp'); var browserify = require('browserify'); var babelify = require('babelify'); var source = require('vinyl-source-stream'); gulp.task('build', function () { return browserify({entries: './src/js/app.js', debug: true}) .transform("babelify", { presets: ["es2015"] }) .bundle() .pipe(source('app.js')) .pipe(gulp.dest('./dist/js')); }); gulp.task('watch', ['build'], function () { gulp.watch('./src/js/*.js', ['build']); }); gulp.task('default', ['build', 'watch']); |
這就是構建 JavaScript 程式碼的全部東西。我們簡單地告訴 browserify,我們想改變的檔案(此案例中,是 src/js/app.js),另外在程式碼被 browserify 處理(打包)前,使用預設值為 “es2015” 的 babel 對程式碼進行轉譯。自 babel v6.0.0 版本後,babel 需要指定預設值,然後根據預設值對 JavaScript 程式碼進行相應轉譯。 儘管這操作看起來有點麻煩,但這讓開發者對整個處理過程有更多的控制權。由於我們想根據 es2015 的標準進行構建處理,我們需要通過 NPM 安裝 babel-preset-es2015。
在檔案被 browserify 處理後,將它們打包進一個單獨檔案(此案例中,也只有一個檔案)。你可能想知道 vinyl-source-stream 是什麼。gulp 是基於 node 流(stream)的,更具體地說,是基於 vinyl streams 的,這是一種虛擬的檔案格式。Browserify 返回一個可讀的 stream,但不是 vinyl stream。因此我們必須用 vinyl-source-stream 對 stream 進行相應轉化,以確保 gulp 後續邏輯能繼續執行。這是一個額外的外掛,但它小巧且職責單一。
在 gulpfile 僅 20 行程式碼下,我們就能正式構建 ES6 程式碼為合法的 ES5 程式碼了。如果要編寫生產環境的 JavaScript 程式碼,那麼在這之前,還要 uglify(醜化壓縮)、source maps 和 livereload。
新增 Uglify、Source Maps 和 LiveReload
將 ES6 程式碼轉為合法的 ES5 程式碼只是一件事,但將其投入到生產環境則需確保程式碼是被壓縮的和擁有 source map 檔案。為了新增這些功能,我們要為 gulpfile 檔案新增以下工具:
- UglifyJS:醜化並壓縮 JavaScript 檔案。
- Source Maps:這會有助於為壓縮後的指令碼進行除錯。Source maps 將壓縮後的檔案程式碼對映到未壓縮檔案的具體位置。這對生產環境下的程式碼進行除錯(debugging)變得更容易了。當然,source maps 檔案只會在開發者工具的 console 視窗開啟時才會下載,所以普通使用者是不會下載它們的。
- LiveReload:這工具在開發中特別有用。若其監聽的檔案發生變化時,它會自動重新整理你的瀏覽器。還有個比較受歡迎的替代工具是 BrowserSync 。
先新增 UglifyJS。我們需要安裝以下包(packages):
1 |
npm install --save-dev vinyl-buffer gulp-uglify |
並修改 gulpfile 檔案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var gulp = require('gulp'); var browserify = require('browserify'); var babelify = require('babelify'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var uglify = require('gulp-uglify'); gulp.task('build', function () { return browserify({entries: './src/js/app.js', debug: true}) .transform("babelify", { presets: ["es2015"] }) .bundle() .pipe(source('app.js')) .pipe(buffer()) .pipe(uglify()) .pipe(gulp.dest('./dist/js')); }); gulp.task('watch', ['build'], function () { gulp.watch('./src/js/*.js', ['build']); }); gulp.task('default', ['build', 'watch']); |
我們新增了 gulp-uglify 和 vinyl-buffer。gulp-uglify 是 gulp 的一個外掛,它能壓縮 JavaScript 程式碼。但 vinyl-buffer 有何用呢?由於 gulp-uglify 現在不支援 stream,而支援 buffer。vinyl-buffer 能將 stream 轉為 buffer,讓 gulp-uglify 能正常執行。
最後階段了,讓我們為 gulpfile 新增 source maps 和 livereload。同樣地,先安裝包(packages):
1 |
npm install --save-dev gulp-sourcemaps gulp-livereload |
OK,下面是我們最後一次修改 gulpfile 檔案了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var gulp = require('gulp'); var browserify = require('browserify'); var babelify = require('babelify'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var uglify = require('gulp-uglify'); var sourcemaps = require('gulp-sourcemaps'); var livereload = require('gulp-livereload'); gulp.task('build', function () { return browserify({entries: './src/js/app.js', debug: true}) .transform("babelify", { presets: ["es2015"] }) .bundle() .pipe(source('app.js')) .pipe(buffer()) .pipe(sourcemaps.init()) .pipe(uglify()) .pipe(sourcemaps.write('./maps')) .pipe(gulp.dest('./dist/js')); .pipe(livereload()); }); gulp.task('watch', ['build'], function () { livereload.listen(); gulp.watch('./src/js/*.js', ['build']); }); gulp.task('default', ['build', 'watch']); |
在壓縮 JavaScript 程式碼前,要先初始化 source map。這就能讓壓縮後的程式碼對映到原來的程式碼上。然後,將 source map 作為單獨一個檔案儲存在 maps/ 目錄下。現在,每當你想檢視壓縮後的 JS 程式碼所對應的行數時,source map 就能告訴你其相應程式碼在未壓縮檔案的所在行數,這無疑讓 debug 更輕鬆。
最後,讓 livereoload 監聽每個檔案的變化,讓每次構建迭代後都會重新整理瀏覽器。為了以最簡單的方式充分利用 livereload,可安裝相應瀏覽器外掛——我最喜歡的 Chrome 的外掛。
就這樣吧!我們能正式用這 gulpfile 檔案了。
結語
如果你遵循上述步驟(或只是複製了文章開頭的 gulpfile 檔案),那麼你就擁有一個執行良好的、能將 ES6 程式碼轉譯成現代瀏覽器所識別的 ES5 程式碼的自動化構建系統(有一些不必要但很好的外掛,如最小化和 source map)。我知道這裡面有些知識點可能比較複雜,而如果你只是想為瀏覽器編寫 ES5 程式碼,你大可不必深究它。但掌握它,能讓你成為一個更好的開發人員。畢竟,你是在熟悉優雅的前端構建工具。
另外,我強烈建議你看看 ES6 文件,瞭解某些新特效能有效提高你的工作效率。就我個人而言,我對新的 class API 充滿怨言(因為 JavaScript 是基於原型鏈繼承的,而不是基於類。你可以看看我的相關 文章),但對模組模式系統(module pattern system)則喜愛有加,而且我打算經常使用它。如果你打算緊追語言的最新迭代版本,babel 和 gulp 能助你一臂之力。
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式