定義
Babel是一個Javascript的編譯器,通過它你可以將一些新版本的ECMAScript語法轉換成低版本的語法。以便能夠在低版本的瀏覽器或者其它環境平穩執行。
截至目前筆者寫這篇文章的時候,babel的版本是7.10.0
實踐
第一步:建立專案
mkdir babel-study && cd babel-study
第二步:初始化專案,並安裝相關依賴包
npm init -y
npm i @babel/cli @babel/core @babel/preset-env --save-dev
相關依賴包說明:
@babel/cli: hello,你好,我是腳手架工具,我可以通過一些命令將原始檔編譯成目的碼。
@babel/core: hello,你好,我是一個程式碼分析選手,我負責將程式碼分析稱ast,方便其他外掛進行相關處理。
@babel/preset-env: hello,你好,我是一個語法轉義器,我負責的內容是將JS的相關語法進行編譯,關於轉義新增的API和全域性物件這個我不負責的。
第三步:配置.babelrc
樓下這幾位就是常用的babel配置引數了,這裡做簡要介紹
presets(預設)
早期的版本其實是引入類似babel-preset-x
,這種形式的包,現在官方推薦統一用@babel/preset-env
,這個包來做語法轉義器這部分的工作。
plugins(外掛)
彌補babel本身上的功能不足,比如轉義新增的API和全域性物件可能就需要用到一些新的外掛來做這部分工作,我們稱之為補丁轉義器。
ignore(忽略)
把不需要babel編譯的檔案寫配置到這個引數裡面,是一個陣列的形式。
minified(壓縮)
Boolean型別的,將其設定為true後,編譯後的檔案會被壓縮。
comments(註釋)
Boolean型別的,將其設定為true後,編譯後的檔案會有註釋(你專案開發中寫的註釋)。
env(環境變數)
babel執行的環境變數,如果設定了BABEL_ENV
則使用它,如果沒有設定,它會去找有沒有NODE_ENV
,如果還是沒有,那就是走預設development
。
這裡附上一份我調研後的配置檔案
{
"presets": [[
"@babel/preset-env", {
// "modules": false,
"corejs": "3",
"useBuiltIns": "usage",
"targets": {
"node": "4"
// "browsers": ["last 2 versions"]
}
}
]],
"plugins": ["@babel/plugin-transform-runtime"],
"env": {
"test": {
"presets": [[
"@babel/preset-env", {
"modules": false,
"targets": {
"node": "current",
// "chrome": "83",
// "edge": "17",
// "firefox": "68",
// "ie": "11",
// "ios": "11.3",
// "safari": "5.1",
// "samsung": "9.2",
"browsers": ["last 2 versions"]
}
}
]],
"minified": true,
"comments": true,
"ignore": ["./src/test.js"]
},
"development": {
"presets": [[
"@babel/preset-env", {
"targets": {
"browsers": ["last 2 versions", "safari 7"]
}
}
]]
},
"production": {
"presets": [[
"@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
]],
"plugins": ["@babel/runtime"],
"minified": true,
"comments": true
}
}
}
這裡簡要說明下,modules預設為true的,在node的環境下(支援COMMONJS),如果使用ES Module的語法(import、export),然後將其設定為false,你會發現入口檔案沒有被編譯,所有這裡把它去掉了。然後targets下面你可以單獨設定相關環境的支援版本,browsers的優先順序高於其他的。babel7.4.0以後,廢棄了polyfill,需要單獨安裝core-js
第四步:編寫相關測試程式碼
這裡我們測試下ES Module寫法,然後一些新的API的轉義情況,比如陣列的include,箭頭函式、模板字串、Promise等,這裡我們不考慮相關的寫法是不是冗餘,單純地就是為了測試下編譯效果。
animal.js
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating!`);
}
}
export default Animal;
person.js
import Animal from './animal';
class Person extends Animal {
constructor(name, sex) {
super(name);
this.name = name;
this.sex = sex;
this.sexMap = new Map([[1, '男'], [0, '女']]);
}
sing() {
console.log(`${this.name} is singing!`);
}
getSex() {
if (![0, 1].includes(this.sex)) {
return false;
} else {
return this.sexMap.get(this.sex);
}
}
testArr(arr) {
return arr.map(item => item * 2);
}
testPromise() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2020);
}, 2000);
});
}
}
export default Person;
index.js
import Person from './person';
// 我就是試試
let ataola = new Person('ataola', 1);
ataola.eat();
ataola.sing();
const sex = ataola.getSex();
console.log(sex);
let testArr = ataola.testArr([0, 1, 2]);
console.log(testArr);
ataola.testPromise().then(res => {
console.log(res);
});
第五步:babel-cli的使用
編譯專案檔案
# 單純執行, 它會在控制檯打出編譯後的資訊
babel index.js
# 完整寫法
babel index.js --out-file bundle.js
# 簡寫形式
babel index.js -o bundle.js
編譯專案資料夾
# 完整寫法
babel src -out-dir dist
# 簡寫形式
babel src -d dist
# 生成sourc map檔案
babel src -d dist -s
babel-node
babel-cli天然自帶了一個babel-node的命令,拆分一下也就是babel + node,提供了一個支援ES6的REPL環境,你可以這麼玩。
# 直接進到這個環境
babel-node
# 直接執行這個檔案的程式碼
babel-node index.js
最後附上我的測試指令碼
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:dir": "babel src -d dist",
"build:dir:prod": "cross-env BABEL_ENV=production babel src -d dist",
"build:dir:dev": "cross-env BABEL_ENV=development babel src -d dist",
"build:dir:test": "cross-env BABEL_ENV=test babel src -d dist",
"build:dir:s": "babel src -d dist -s",
"build:file": "babel ./test/babel_core.test.js -o bundle.js"
},
...
專案地址: https://github.com/ataola/JavaScript-Tsukuki/tree/master/code/babel-study
問題思考
在專案中使用Babel,它的作用是什麼?或者這麼說它的意義何在?
將高版本的JS語法轉換成低版本的JS語法,可相容不同版本的瀏覽器或者執行環境,劃重點,解決了程式碼在不同版本的瀏覽器的相容性問題。人的腦容量都是有限的,相容的事情就愉快地交給它吧。
使用Babel後,把原始碼編譯成更復雜更難懂一坨坨的東西,我為什麼要去用它?
首先,這絕對不是為了裝逼,也不是為了混淆程式碼。我們先思考下使用高版本的語法它有什麼用?有一些其實是低版本的語法糖,使用了這些我們可以減少程式碼量,然後減輕維護成本。但是ECMAScript它是一個語法標準,不同的JS引擎以及瀏覽器對它的實現和支援又不大一樣,所有我們不能夠保證使用高版本的語法它能夠完美在各平臺執行,這也就是babel的作用體現。
ES的語法有那麼多版本,Babel的配置有那麼多個版本,我該怎麼去選擇呢?
早期地預設有babel-preset-es2015、babel-preset-stage-x之類的啥的,現在官方推薦統一@babel/preset-env,腳手架統一@babel/cli,對,不成文地規定就是@babel打頭基本是對的,polyfill除外。
@babel/xxx和 babel-xxx的,為什麼會有兩種,該用哪個?
先說結論,用前者@babel/xxx, @xxx就相當於註冊了一個名稱空間,特指這個是xxx下的某包,它是一個範圍,是一種組織的體現形式,例如@ataola/zjt,
.babelrc檔案我不寫行不行,能執行嗎?
如果只是建立一個.babelrc裡面什麼都不寫,會報錯,因為babel會讀取裡面的格式,加個{}
,這個是可以執行的,裡面什麼都不寫。這裡思考下babel的預設行為是什麼?只是轉換了Javascript的語法,而不對新的API進行轉換,新的還是要用外掛的。
什麼是語法轉義器,什麼是補丁轉義器?
在presets裡的形如@babel/preset-env就是語法轉義器,在plugins下的外掛包就是補丁轉義器,它們的分工不同,前者是將相關語法進行編譯,後者彌補了前者的一些不足,故稱之為補丁。
targets裡設定browsers的優先順序高,還是直接設定瀏覽器的優先順序高?
設定browsers的優先順序高於直接設定瀏覽器的,會覆蓋後者。
為什麼將modules設定成false,是否還有其他設定方案?
說明其預設為true,預設都是支援commonjs規範的。還可以設定成amd、umd之類的。
transform-runtime解決了一個什麼問題?
解決了es6語法中全域性物件或者全域性物件方法編譯不足的情況。
既然transform-runtime只是解決es6,那我要是用es7、es8、es9甚至更高怎麼辦呢?
babel-polyfill , core-js、regenerator-runtime
為什麼不推薦全域性安裝腳手架?
版本更新迭代太快了,安裝在專案本地易升級。
BABEL_ENV或者NODE_ENV的設定方式
# osx|linux
export NODE_ENV=production
#window
SET NODE_ENV=production
Babel的預設行為是什麼?
轉換了形如let、箭頭函式之類的語法, 如果要完全的ES6語法支援需要安裝plugin-transform-runtime外掛,如果需要更高版本的話,那就需要安裝polyfill外掛。
參考文獻
babel官網:https://babeljs.io/
@babel/preset-env文件:https://babeljs.io/docs/en/babel-preset-env/
@babel/plugin-transform-runtime:https://babeljs.io/docs/en/next/babel-plugin-transform-runtime.html
@babel/cli文件:https://babeljs.io/docs/en/babel-cli
babel配置檔案:https://babeljs.io/docs/en/config-files#file-relative-configuration
babel環境變數配置:https://babeljs.io/docs/en/options#envname
@xxx npm包的解釋 About Scope: https://docs.npmjs.com/about-scopes
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。