babel 7 簡單升級指南
babel 7
babel 7 釋出兩天了,試著對當前專案更新了下,僅此記錄分享
主要改動參考 官方部落格
主要升級內容
- 不再支援放棄維護的 node 版本 0.10、0.12、4、5
- 使用 @babel 名稱空間,如 @babel/core
- @babel/preset-env 代替 preset-es2015 等
- TC39 提議的外掛改名為 -proposal,代替 -transform
- 針對面向使用者的包(如 babel-loader、@babel/cli)在 @babel/core 中引入 peerDependency
新增 babel.config.js
- 可按執行環境配置 babel 配置
- 解析方式不同於 .babelrc,不會向上查詢
- 可用 overrides 進行選擇性配置
主要新增語法
- BigInt
- 動態匯入
- 可選連結 ( a?.b )
- 邏輯賦值 ( a &&= b ; a ||= b )
let a = {b: 11}
a?.b // a ? a.b : undefined
a &&= b // a = a && (a=b) 或 a = a && b
複製程式碼
- ( a ?? b )
- 管道操作符 ( a |> b )
let c = a ?? b // 只有 a 是 undefined 或 null 時,c 等於 b
let res = foo(boo(aaa('nice')))
===
let res = 'nice' |> aaa |> boo |> foo 複製程式碼
- 使用 @babel/preset-typescript 支援 TypeScript
polyfill 修改
- import “@babel/polyfill” 匯入整個 polyfill
- 可以使用配置選項 useBuiltIns: “entry” 按需匯入
- 可以使用 useBuiltIns: “usage” 自動按使用到的 polyfill匯入
其他修改
- babel-upgrade 自動升級工具
- 速度提升、程式碼優化
- 添加註解,方便 Uglify 等外掛移除無用程式碼
- 新增一些 TC39 提案語法
- @babel/runtime 變化
- Babel 巨集配置
- 模組模式配置支援
- 支援擴充套件原生內建元素 (Array、Error 等)
升級
babel 升級工具修改配置
npx babel-upgrade --write
# 或是安裝 babel-upgrade 在 global 並執行
npm install babel-upgrade -g
babel-upgrade --write
複製程式碼
可以看到 package.json 中移除了舊版本的依賴,自動新增了新版名稱,.babelrc 檔案的配置也會自動修改 但是不會刪除已有的外掛,如原來的 transform-decorators-legacy
// 移除就版本依賴後重新安裝依賴
rm -rf node_modules
yarn install
複製程式碼
修改所有 bable-polyfill 為 @babel/polyfill
因為 babel 7 新增的 @babel 名稱空間,所以原來的 babel-polyfill 需要修改名稱
// index.js 頭部匯入修改
import '@babel/polyfill'
// webpack config 入口修改
entry: ['@babel/polyfill', './src/index.js'],
複製程式碼
裝飾器外掛
yarn add @babel/plugin-proposal-decorators -D
複製程式碼
注意
@babel/plugin-proposal-decorators
必須在@babel/plugin-proposal-class-properties
之前配置- 裝飾器外掛新增的 legacy 配置
升級後的 .babelrc
"dev": {
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/transform-async-to-generator",
[
"@babel/plugin-proposal-decorators", { "legacy": true } ], ["@babel/plugin-proposal-class-properties", { "loose": true }], "@babel/plugin-proposal-optional-chaining", "react-hot-loader/babel", [ "react-css-modules", { "webpackHotModuleReloading": true, "generateScopedName": "[path][name]__[local]--[hash:base64:5]", "filetypes": { ".less": { "syntax": "postcss-less" } } } ], [ "import", { "libraryName": "antd" } ] ] }, 複製程式碼
其他外掛
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators", { "legacy": true } ], "@babel/plugin-proposal-function-sent", "@babel/plugin-proposal-export-namespace-from", "@babel/plugin-proposal-numeric-separator", "@babel/plugin-proposal-throw-expressions", "@babel/plugin-proposal-export-default-from", "@babel/plugin-proposal-logical-assignment-operators", "@babel/plugin-proposal-optional-chaining", [ "@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" } ], "@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-do-expressions", "@babel/plugin-proposal-function-bind"
preset
比如 es2015 是一套規範,包含大概十幾二十個轉譯外掛。如果每次要開發者一個個新增並安裝,配置檔案很長不說,npm install
的時間也會很長,更不談我們可能還要同時使用其他規範呢。
為了解決這個問題,babel 還提供了一組外掛的集合。因為常用,所以不必重複定義 & 安裝。(單點和套餐的差別,套餐省下了巨多的時間和配置的精力)
preset 分為以下幾種:
- 官方內容,目前包括 env, react, flow, minify 等。這裡最重要的是 env,後面會詳細介紹。
- stage-x,這裡麵包含的都是當年最新規範的草案,每年更新。
這裡面還細分為 - Stage 0 - 稻草人: 只是一個想法,經過 TC39 成員提出即可。
- Stage 1 - 提案: 初步嘗試。
- Stage 2 - 初稿: 完成初步規範。
- Stage 3 - 候選: 完成規範和瀏覽器初步實現。
- Stage 4 - 完成: 將被新增到下一年度釋出。
例如 syntax-dynamic-import
就是 stage-2 的內容,transform-object-rest-spread
就是 stage-3 的內容。
此外,低一級的 stage 會包含所有高階 stage 的內容,例如 stage-1 會包含 stage-2, stage-3 的所有內容。
stage-4 在下一年更新會直接放到 env 中,所以沒有單獨的 stage-4 可供使用。
- es201x, latest
這些是已經納入到標準規範的語法。例如 es2015 包含arrow-functions
,es2017 包含syntax-trailing-function-commas
。但因為 env 的出現,使得 es2016 和 es2017 都已經廢棄。所以我們經常可以看到 es2015 被單獨列出來,但極少看到其他兩個。
latest 是 env 的雛形,它是一個每年更新的 preset,目的是包含所有 es201x。但也是因為更加靈活的 env 的出現,已經廢棄。
執行順序
很簡單的幾條原則:
- Plugin 會執行在 Preset 之前。
- Plugin 會從前到後順序執行。
- Preset 的順序則 剛好相反(從後向前)。
preset 的逆向順序主要是為了保證向後相容,因為大多數使用者的編寫順序是 ['es2015', 'stage-0']
。這樣必須先執行 stage-0
才能確保 babel 不報錯。因此我們編排 preset 的時候,也要注意順序,其實只要按照規範的時間順序列出即可。
外掛和 preset 的配置項
簡略情況下,外掛和 preset 只要列出字串格式的名字即可。但如果某個 preset 或者外掛需要一些配置項(或者說引數),就需要把自己先變成陣列。第一個元素依然是字串,表示自己的名字;第二個元素是一個物件,即配置物件。
最需要配置的當屬 env,如下:
"presets": [
// 帶了配置項,自己變成陣列
[ // 第一個元素依然是名字 "env", // 第二個元素是物件,列出配置項 { "module": false } ], // 不帶配置項,直接列出名字 "stage-2" ]
env (重點)
因為 env 最為常用也最重要,所以我們有必要重點關注。
env 的核心目的是通過配置得知目標環境的特點,然後只做必要的轉換。例如目標瀏覽器支援 es2015,那麼 es2015 這個 preset 其實是不需要的,於是程式碼就可以小一點(一般轉化後的程式碼總是更長),構建時間也可以縮短一些。
如果不寫任何配置項,env 等價於 latest,也等價於 es2015 + es2016 + es2017 三個相加(不包含 stage-x 中的外掛)。env 包含的外掛列表維護在這裡
下面列出幾種比較常用的配置方法:
{
"presets": [
["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] } }] ] }
如上配置將考慮所有瀏覽器的最新2個版本(safari大於等於7.0的版本)的特性,將必要的程式碼進行轉換。而這些版本已有的功能就不進行轉化了。這裡的語法可以參考 browserslist
{
"presets": [
["env", { "targets": { "node": "6.10" } }] ] }
如上配置將目標設定為 nodejs,並且支援 6.10 及以上的版本。也可以使用 node: 'current'
來支援最新穩定版本。例如箭頭函式在 nodejs 6 及以上將不被轉化,但如果是 nodejs 0.12 就會被轉化了。
另外一個有用的配置項是 modules
。它的取值可以是 amd
, umd
, systemjs
, commonjs
和 false
。這可以讓 babel 以特定的模組化格式來輸出程式碼。如果選擇 false
就不進行模組化處理。
npm package 名稱的變化 (重點)
這是 babel 7 的一個重大變化,把所有 babel-*
重新命名為 @babel/*
,例如:
babel-cli
變成了@babel/cli
。babel-preset-env
變成了@babel/preset-env
。進一步,還可以省略preset
而簡寫為@babel/env
。babel-plugin-transform-arrow-functions
變成了@babel/plugin-transform-arrow-functions
。和preset
一樣,plugin
也可以省略,於是簡寫為@babel/transform-arrow-functions
。
這個變化不單單應用於 package.json 的依賴中,包括 .babelrc 的配置 (plugins
, presets
) 也要這麼寫,為了保持一致。例如
{
"presets": [
- "env"
+ "@babel/preset-env"
]
}
順帶提一句,上面提過的 babel 解析語法的核心 babylon
現在重新命名為 @babel/parser
,看起來是被收編了。
上文提過的 stage-x 被刪除了,它包含的外掛雖然保留,但也被重新命名了。babel 團隊希望更明顯地區分已經位於規範中的外掛 (如 es2015 的 babel-plugin-transform-arrow-functions
) 和僅僅位於草案中的外掛 (如 stage-0 的 @babel/plugin-proposal-function-bind
)。方式就是在名字中增加 proposal
,所有包含在 stage-x 的轉譯外掛都使用了這個字首,語法外掛不在其列。
最後,如果外掛名稱中包含了規範名稱 (-es2015-
, -es3-
之類的),一律刪除。例如 babel-plugin-transform-es2015-classes
變成了 @babel/plugin-transform-classes
。(這個外掛我自己沒有單獨用過,慚愧)
不再支援低版本 node
babel 7.0 開始不再支援 nodejs 0.10, 0.12, 4, 5 這四個版本,相當於要求 nodejs >= 6 (當前 nodejs LTS 是 8,要求也不算太過分吧)。
這裡的不再支援,指的是在這些低版本 node 環境中不能使用 babel 轉譯程式碼,但 babel 轉譯後的程式碼依然能在這些環境上執行,這點不要混淆。
only 和 ignore 匹配規則的變化
在 babel 6 時,ignore
選項如果包含 *.foo.js
,實際上的含義 (轉化為 glob) 是 ./**/*.foo.js
,也就是當前目錄 包括子目錄 的所有 foo.js
結尾的檔案。這可能和開發者常規的認識有悖。
於是在 babel 7,相同的表示式 *.foo.js
只作用於當前目錄,不作用於子目錄。如果依然想作用於子目錄的,就要按照 glob 的完整規範書寫為 ./**/*.foo.js
才可以。only
也是相同。
這個規則變化只作用於萬用字元,不作用於路徑。所以 node_modules
依然包含所有它的子目錄,而不單單隻有一層。(否則全世界開發者都要爆炸)
@babel/node 從 @babel/cli 中獨立了
和 babel 6 不同,如果要使用 @babel/node
,就必須單獨安裝,並新增到依賴中。
babel-upgrade
在提到刪除 stage-x 時候提過這個工具,它的目的是幫助使用者自動化地從 babel 6 升級到 7。
這款升級工具的功能包括:(這裡並不列出完整列表,只列出比較重要和常用的內容)
- package.json
- 把依賴(和開發依賴)中所有的
babel-*
替換為@babel/*
- 把這些
@babel/*
依賴的版本更新為最新版 (例如^7.0.0
) - 如果
scripts
中有使用babel-node
,自動新增@babel/node
為開發依賴 - 如果有
babel
配置項,檢查其中的plugins
和presets
,把短名 (env
) 替換為完整的名字 (@babel/preset-env
) - .babelrc
- 檢查其中的
plugins
和presets
,把短名 (env
) 替換為完整的名字 (@babel/preset-env
) - 檢查是否包含
preset-stage-x
,如有替換為對應的外掛並新增到plugins
使用方式如下:
# 不安裝到本地而是直接執行命令,npm 的新功能
npx babel-upgrade --write
# 或者常規方式
npm i babel-upgrade -g
babel-upgrade --write
babel-upgrade
工具本身也還在開發中,還列出了許多 TODO 沒有完成,因此之後的功能可能會更加豐富,例如上面提過的 ignore
的萬用字元轉化等等。