如何在NodeJS項目中優雅的使用ES6
NodeJs最近的版本都開始支持ES6(ES2015)的新特性了,設置已經支持了async/await這樣的更高級的特性。只是在使用的時候需要在node后面加上參數:--harmony
。但是,即使如此node也還是沒有支持全部的ES6特性。所以這個時候就需要用到Babel了。
現在開始Babel
在開始使用Babel之前,假設
- 你已經安裝了nodejs,並且已經熟悉了Js。
- 你也可以使用npm安裝各種依賴包。
- 而且你也對ES6(后來改為ES2015)有一定程度的熟悉。
同時假設你已經安裝了yarn,並且也熟悉了yarn。Yarn最大的優點就是它比npm要快很多,因為yarn只把需要的庫下載一次,之后用到的時候直接使用本地緩存的版本。npm每次都會下載這些庫。這簡直就是浪費生命。如果你還沒有安裝yarn,也沒有關系,下面也會有npm的使用方法。
接下來開始安裝配置Babel。安裝babel-cli
yarn add babel-cli --dev // npm install --save-dev babel-cli
安裝babel的presets。
yarn add babel-preset-es2015 --dev // npm install --save-dev babel-preset-es2015
這個時候你就可以使用ES2015的特性了。但是,這還不夠,比如我不想用Promise
我想用更加方便的async/await
語法。只有es2015這個preset是不夠的。
Babel的plugin和preset
Babel本身不處理語言特性的轉碼。這些功能都是由plugin和preset實現的(preset也是一個plugin的集合)。如上文所述,要使用es2015的內容就需要安裝babel-preset-es2015這個preset。要使用async/await那么就需要安裝對應的preset或者插件。為了簡單我們安裝preset:babel-preset-stage-0
。preset stage-0包含了async/await相關的插件: babel-plugin-syntax-async-functions、babel-plugin-transform-regenerator。
yarn add babel-preset-stage-0 --dev // npm install --save-dev babel-preset-stage-0
這樣還是不能在項目中使用es7的async/await了。還需要更多的配置,有兩種方法可以達到目的:
- 使用
babel-polyfill
。有一個不好地地方,babel-polyfill
會污染global對象,所以不適合於library之類的使用。僅適合於web app使用。 - 使用babel運行時轉碼工具,
transform-runtime
插件。使用這個方法正好彌補了上面的方法的不足之處。它是尤其適合於library一類的項目使用。
分別介紹這兩種方法。
安裝babel-polyfill
:
yarn add babel-polyfill --dev // npm install --save-dev babel-polyfill
之后,在你的項目的入口文件的最上方引入babel-polyfill
。比如我現在有一個Express的Web App,那么的入口文件就是開啟這個app的index.js文件。在這個文件的最上方引入polyfill,require('babel-polyfill')
。或者你的入口文件已經是ES2015的寫法了,那么就直接import,import 'babel-polyfill'
。
使用transform-runtime
也非常簡單。安裝:
yarn add babel-plugin-transform-runtime --dev // npm install --save-dev babel-plugin-transform-runtime
另外還需要安裝babel-runtime
:
yarn add babel-runtime // npm install --save babel-runtime
之后在.babelrc文件中添加如下的配置,兩個二選其一即可:
// without options
{
"plugins": ["transform-runtime"]
}
// with options
{
"plugins": [
["transform-runtime", {
"helpers": false, // defaults to true
"polyfill": false, // defaults to true
"regenerator": true, // defaults to true
"moduleName": "babel-runtime" // defaults to "babel-runtime"
}]
]
}
剩下的就是歡暢的使用async/await了。
另外如果要使用Object.assing
這樣的方法的話,也可以使用插件:babel-plugin-transform-object-assign
,如果要使用解構賦值可以使用插件:babel-plugin-transform-object-rest-spread
。當然這些都包含在了stage-0這個preset中。
現在就開始寫ES2015的代碼吧。在項目中安裝ExpressJs,創建一個index.js文件。我們來試着創建一個小小的web app作為練習:
import Express from 'express'
let app = Express()
app.get('/', (req, res) => {
res.send('hello world')
})
app.listen(8080, () => console.log('server is running at http://localhost:8080'))
運行命令:
./node_modules/.bin/babel-node index.js --preset es2015, stage-0
使用命令*babel-node**就可以讓代碼運行起來,后面的參數指定了在轉義js代碼的時候使用的preset和plugin。
Babel官方推薦的方法是時候用.babelrc文件,這一方式可以更加靈活。在項目的更目錄上創建.babelrc文件,在里面添加你安裝的preset和plugin的描述:
{
"presets": ["es2015", "stage-0"]
}
這樣可以直接使用babel-node來執行代碼,或者使用命令babel來轉義代碼。如:
babel -w code/ -d build/
babel命令會從配置文件中讀取配置,來變異code/目錄下的文件,並把轉義之后的JavaScript文件導出到build/目錄下。還有命令行里的參數-w,這個命令參數指定的是watch,每次code目錄的文件修改后都會觸發babel命令的再次執行。
在文件中使用Source Maps
上面看起來很不錯了。但是還有一個問題,在你調試代碼的時候,你調試的實際是babel命令轉碼之后的js,不是原來你編寫的源代碼所在的文件。調試的不是源文件,多少會有些不便。比如下面的文件會拋出一個異常:
async function errorAsyncFunc() {
try{
throw new Error('Async function error')
} catch(e) {
throw e
}
}
errorAsyncFunc()
在轉碼命令中加一個--source-maps
可以解決這個問題:
babel code/ -d build/ --source-maps
最后在package.json里添加scripts節點:
"scripts": {
"build": "babel src -d build --source-maps",
"start": "node build/index.js"
},
接下來:
npm run build
Gulp出場
上文講述了如何使用Babel實現ES201x的開發。但是在正式的開發中,上面的這些配置還略顯不足,尤其是你的項目包括web端、server端,尤其web端不僅處理ES201x的代碼還需要處理。所以需要Gulp出場。
這玩意兒看起來很復雜,你定義了編譯的過程。其實掌握了以后很好用,尤其是可以自動處理很多東西,節約大把時間。要使用Gulp,必須先安裝NodeJS。這個基本是標配。然后你會用到它的命令行工具。
安裝Gulp
在最新發布的Gulp里有一點調整。gulp-cli從gulp分離出來作為單獨的一部分使用。所以,如果你已經安裝過gulp之前的版本需要先刪除:
npm rm --global gulp
安裝gulp-cli
yarn global add gulp-cli // npm install --global gulp-cli
在--dev模式下安裝gulp
yarn add gulp --dev // npm install --save-dev gulp
創建gulp配置文件
就像Babel要用.babelrc作為配置文件一樣,gulp也需要一個配置文件。這個配置文件就是gulpfile.js, 但是和babel同用的情況下把gulpfile.js重命名為gulp.babel.js:
mv "gulpfile.js" "gulpfile.babel.js"
gulp
的使用還是很簡單的,主要就是在gulpfile.babel.js文件中添加各種task。在這些task中一定要添加一個叫做default的task,gulp命令的執行起點就是從這里開始。
假設有這么一個場景:
- 使用eslint檢查代碼,發現代碼風格和潛在的錯誤。
- 自動實現ES201x -> ES5的代碼轉碼,並把轉碼后的代碼放在指定目錄下。
- 在轉碼的時候添加sourcemaps。
以上這些“任務”都是用gulp自動實現。該如何配置呢?
gulp和eslint
要在gulp中使用各種請他的類似於eslint這樣的功能的時候需要使用在gulp上的對應的插件。沒錯,gulp的設計思路和gulp基本一樣:插件機制。
那么我們就需要首先下載eslint的插件:
yarn add --dev gulp-eslint // npm install --save-dev gulp-eslint
在開始編寫我們的第一個task之前, 做最后的准備工作。首先需要配置.eslintrc文件。eslint會根據這個文件定義的規則檢查代碼的風格。我們不准備大批的配置規則,這樣非常耗時間而且也照顧不到很多我們項目已經保留下來的編碼風格。所以,airbnb的一套規則拿來使用時最好的辦法。
安裝eslint
yarn add --dev eslint // npm install --save-dev eslint
然后你可以運行命令來初始化配置:./node_modules/.bin/eslint --init
。你也可以忽略這個命令,直接創建一個.eslintrc的文件。
安裝eslint的airbnb擴展
要使用airbnb的一套規則就需要安裝他們的eslint擴展:
yarn add eslint-config-airbnb --dev // npm install --save-dev eslint-config-airbnb
命令執行之后會提示有些依賴項沒有安裝,分別是eslint-plugin-import@^2.2.0
、eslint-plugin-import@^2.2.0
、eslint-plugin-jsx-a11y@^3.0.2
。依次安裝這些依賴項就好。
.eslintrc
{
"env": {
"es6": true
},
"rules": {
"semi": "off",
"import/no-extraneous-dependencies": ["error", {
"devDependencies": true,
"optionalDependencies": false,
"peerDependencies": false
}]
,"quotes": ["error", "single", {"allowTemplateLiterals": true}]
},
"extends": "airbnb"
}
env
指定環境是支持es6的,rules指定的是一些補充內容,比如字符串使用單引號還是雙引號等。這個是根據個人喜好配置的,你可以選擇添加你需要的規則。最后是extends
,這里配置airbnb就用上了airbnb的一套eslint編碼檢查規則。
gulp-eslint插件用起來
import gulp from 'gulp'
import eslint from 'gulp-eslint
// 配置需要處理的文件目錄和轉碼之后文件的存放目錄
const paramConfig = {
source: 'src/**/*.js',
dest: 'build',
}
引入相關模塊之后開始寫任務:
gulp.task('lint', () => {
// eslint配置,使用配置的文件目錄。排除node_modules下的全部文件。
return gulp.src([paramConfig.source, '!node_modules/**'])
.pipe(eslint())
.pipe(eslint.result(result => {
console.log(`ESLint result: ${result.filePath}`);
console.log(`# Messages: ${result.messages.length}`);
console.log(`# Warnings: ${result.warningCount}`);
console.log(`# Errors: ${result.errorCount}`);
}))
.pipe(eslint.format())
.pipe(eslint.failOnError())
})
如前文所述,default任務是必須:
gulp.task('default', ['lint'], function () {
// lint任務成功執行之后執行這個方法
});
跳轉到項目的目錄下,運行gulp命令。會得到如下的輸出:
$ gulp
[21:43:01] Requiring external module babel-register
[21:43:01] Using gulpfile ~/Documents/test-polyfill/gulpfile.babel.js
[21:43:01] Starting 'lint'...
[21:43:02] Starting 'babel-sourcemaps'...
ESLint result: ~/Documents/test-polyfill/src/index.js
# Messages: 0
# Warnings: 0
# Errors: 0
ESLint result: ~/Documents/test-polyfill/src/test.js
# Messages: 0
# Warnings: 0
# Errors: 0
[21:43:02] Finished 'lint' after 605 ms
[21:43:02] Finished 'babel-sourcemaps' after 653 ms
[21:43:02] Starting 'default'...
gulp default task!
[21:43:02] Finished 'default' after 98 μs
gulp和babel
這次同時處理babel和sourcemaps的問題。
首先安裝插件:
yarn add --dev gulp-babel // npm install --save-dev gulp-babel
import
gulp-babel插件:
import babel from 'gulp-babel'
import sourcemaps from 'gulp-sourcemaps'
添加任務:
gulp.task('babel-sourcemaps', () => {
return gulp.src(paramConfig.source)
.pipe(sourcemaps.init())
.pipe(babel())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paramConfig.dest))
})
修改default任務:
gulp.task('default', ['lint', 'babel-sourcemaps'], () => {
console.log('gulp default task!')
})
如果你不想用sourcemaps的話,可以這么寫:
gulp.task('babel', () => {
return gulp.src(paramConfig.source)
.pipe(babel())
.pipe(gulp.dest(paramConfig.dest))
})
把gulp放在npm命令體系下
babel
老早就配置好了,現在和配置好了gulp。gulp每次輸入命令基本上就是手動執行。現在應該讓這個命令半自動執行了。
修改package.json文件,在其中添加scripts
節點:
"scripts": {
"build": "gulp",
"start": "node build/index.js"
},
如此一來,很多的命令都可以像gulp一樣放在npm的scripts里執行。比如,現在可以在命令行李輸入如下命令來實現lint
和babel
轉碼:
npm run build
開始執行:
npm start
總結
使用bebel可以提前使用最新的JavaScript語言特性,這樣編寫很多代碼的時候會變得簡潔高效。並且babel轉碼之后生成的代碼也是非常規范的ES5寫法,同時是在嚴格模式下的。所以,我們在寫ES201x代碼的時候不需要再添加'use strict';
標識。
使用gulp又可以使很多不大不小但是很費時間的事自動處理。
把這兩者結合在一起會讓你的項目開發效率提升很多。所以,看到這里你不覺得你應該趕快在項目里使用這些技術,讓開發進入快車道嗎!!!???