新的一年babel了解一下
參考文檔 Babel 插件手冊
Babel的作用
Babel是一個JavaScript編譯器
很多瀏覽器目前還不支持ES6的代碼,Babel的作用就是把瀏覽器不資辭的代碼編譯成資辭的代碼。
注意很重要的一點就是,Babel只是轉譯新標準引入的語法,比如ES6的箭頭函數轉譯成ES5的函數, 但是對于新標準引入的新的原生對象,部分原生對象新增的原型方法,新增的API等(如Set、Promise),這些Babel是不會轉譯的,需要引入polyfill來解決。
API
Babel實際上是一組模塊的集合。
@babel/core
Babel 的編譯器,核心 API 都在這里面,比如常見的transform、parse。
npm i @babel/core -D 復制代碼- 使用
- transform
babel.transform(code: string, options?: Object)
babel.transform(code, options, function(err, result) {result; // => { code, map, ast } }); 復制代碼- parse
babel.parse(code: string, options?: Object, callback: Function)
@babel/cli
cli是命令行工具, 安裝了@babel/cli就能夠在命令行中使用babel 命令來編譯文件。
npm i @babel/core @babel/cli -D 復制代碼- 使用
Note: 因為沒有全局安裝@babel/cli, 建議用npx命令來運行,或者./node_modules/.bin/babel,關于npx命令,可以看下官方文檔
@babel/node
直接在node環境中,運行 ES6 的代碼
- 使用
babylon
Babel的解析器
首先,安裝一下這個插件。
npm i babylon -S 復制代碼先從解析一個代碼字符串開始:
// src/index.js import * as babylon from 'babylon';const code = `function add(m, n) {return m + n; }`;babylon.parse(code); 復制代碼npx babel-node src/index.js 復制代碼Node {type: "File",start: 0,end: 38,loc: SourceLocation {...},program: Node {...},comments: [],tokens: [...] } 復制代碼babel-traverse
用于對 AST 的遍歷,維護了整棵樹的狀態,并且負責替換、移除和添加節點。
運行以下命令安裝:
npm i babel-traverse -S 復制代碼import * as babylon from 'babylon'; import traverse from 'babel-traverse';const code = `function add(m, n) {return m + n; }`;const ast = babylon.parse(code);traverse(ast, {enter(path) {if (path.node.type === 'Identifier' &&path.node.name === 'm') {// do something}} }); 復制代碼babel-types
用于 AST 節點的 Lodash 式工具庫, 它包含了構造、驗證以及變換 AST 節點的方法,對編寫處理 AST 邏輯非常有用。
npm i babel-types -S 復制代碼import traverse from 'babel-traverse'; import * as t from 'babel-types';traverse(ast, {enter(path) {if (t.isIdentifier(path.node, { name: 'm' })) {// do something}} }); 復制代碼babel-generator
Babel 的代碼生成器,它讀取AST并將其轉換為代碼和源碼映射(sourcemaps)
npm i babel-generator -S 復制代碼import * as babylon from 'babylon'; import generate from 'babel-generator';const code = `function add(m, n) {return m + n; }`;const ast = babylon.parse(code);generate(ast, {}, code); // { // code: "...", // map: "...", // rawMappings: "..." // } 復制代碼Babel是怎么工作的
為了理解Babel,我們從ES6最受歡迎的特性箭頭函數入手。
假設要把下面這個箭頭函數的Javascript代碼
(foo, bar) => foo + bar; 復制代碼編譯成瀏覽器支持的代碼:
; (function (foo, bar) {return foo + bar; }); 復制代碼Babel的編譯過程和大多數其他語言的編譯器相似,可以分為三個階段:
- 解析(Parsing):將代碼字符串解析成抽象語法樹。
- 轉換(Transformation):對抽象語法樹進行轉換操作。
- 生成(Code Generation): 根據變換后的抽象語法樹再生成代碼字符串。
解析(Parsing)
Babel拿到源代碼會把代碼抽象出來,變成AST(抽象語法樹),洋文是Abstract Syntax Tree。
抽象語法樹是源代碼的抽象語法結構的樹狀表示,樹上的每個節點都表示源代碼中的一種結構,這所以說是抽象的,是因為抽象語法樹并不會表示出真實語法出現的每一個細節,比如說,嵌套括號被隱含在樹的結構中,并沒有以節點的形式呈現。它們主要用于源代碼的簡單轉換。
箭頭函數(foo, bar) => foo + bar;的AST長這樣:
{"type": "Program","start": 0,"end": 202,"body": [{"type": "ExpressionStatement","start": 179,"end": 202,"expression": {"type": "ArrowFunctionExpression","start": 179,"end": 202,"id": null,"expression": true,"generator": false,"params": [{"type": "Identifier","start": 180,"end": 183,"name": "foo"},{"type": "Identifier","start": 185,"end": 188,"name": "bar"}],"body": {"type": "BinaryExpression","start": 193,"end": 202,"left": {"type": "Identifier","start": 193,"end": 196,"name": "foo"},"operator": "+","right": {"type": "Identifier","start": 199,"end": 202,"name": "bar"}}}}],"sourceType": "module" } 復制代碼上面的AST描述了源代碼的每個部分以及它們之間的關系,可以自己在這里試一下astexplorer。
AST是怎么來的?解析過程分為兩個步驟:
- 分詞:將整個代碼字符串分割成語法單元數組
Javascript代碼中的語法單元主要指如標識符(if/else、return、function)、運算符、括號、數字、字符串、空格等等能被解析的最小單元
[{"type": "Punctuator","value": "("},{"type": "Identifier","value": "foo"},{"type": "Punctuator","value": ","},{"type": "Identifier","value": "bar"},{"type": "Punctuator","value": ")"},{"type": "Punctuator","value": "=>"},{"type": "Identifier","value": "foo"},{"type": "Punctuator","value": "+"},{"type": "Identifier","value": "bar"} ] 復制代碼- 語法分析:建立分析語法單元之間的關系
語義分析則是將得到的詞匯進行一個立體的組合,確定詞語之間的關系。考慮到編程語言的各種從屬關系的復雜性,語義分析的過程又是在遍歷得到的語法單元組,相對而言就會變得更復雜。
簡單來說語義分析既是對語句和表達式識別,這是個遞歸過程,在解析中,Babel 會在解析每個語句和表達式的過程中設置一個暫存器,用來暫存當前讀取到的語法單元,如果解析失敗,就會返回之前的暫存點,再按照另一種方式進行解析,如果解析成功,則將暫存點銷毀,不斷重復以上操作,直到最后生成對應的語法樹。
轉換(Transformation)
Plugins
插件應用于babel的轉譯過程,尤其是第二個階段Transformation,如果這個階段不使用任何插件,那么babel會原樣輸出代碼。
Presets
babel官方幫我們做了一些預設的插件集,稱之為preset,這樣我們只需要使用對應的preset就可以了。每年每個preset只編譯當年批準的內容。 而babel-preset-env 相當于 es2015 ,es2016 ,es2017 及最新版本。
Plugin/Preset 路徑
如果 plugin 是通過 npm 安裝,可以傳入 plugin 名字給 babel,babel 將檢查它是否安裝在node_modules中
"plugins": ["babel-plugin-myPlugin"] 復制代碼也可以指定你的 plugin/preset 的相對或絕對路徑。
"plugins": ["./node_modules/asdf/plugin"] 復制代碼Plugin/Preset 排序
如果兩次轉譯都訪問相同的節點,則轉譯將按照 plugin 或 preset 的規則進行排序然后執行。
- Plugin 會運行在 Preset 之前。
- Plugin 會從第一個開始順序執行。
- Preset 的順序則剛好相反(從最后一個逆序執行)。
例如:
{"plugins": ["transform-decorators-legacy","transform-class-properties"] } 復制代碼將先執行transform-decorators-legacy再執行transform-class-properties
但 preset 是反向的
{"presets": ["es2015","react","stage-2"] } 復制代碼會按以下順序運行: stage-2, react, 最后es2015。
生成(Code Generation)
用babel-generator通過 AST 樹生成 ES5 代碼
編寫一個Babel插件
基礎的東西講了些,下面說下具體如何寫插件。
插件格式
先從一個接收了當前babel對象作為參數的function開始。
export default function(babel) {// plugin contents } 復制代碼我們經常會這樣寫
export default function({ types: t }) {// } 復制代碼接著返回一個對象,其visitor屬性是這個插件的主要訪問者。
export default function({ types: t }) {return {visitor: {// visitor contents}}; }; 復制代碼visitor中的每個函數接收2個參數:path和state
export default function({ types: t }) {return {visitor: {CallExpression(path, state) {}}}; }; 復制代碼寫一個簡單的插件
我們寫一個簡單的插件,把所有定義變量名為a的換成b, 先從astexplorer看下var a = 1的 AST
{"type": "Program","start": 0,"end": 10,"body": [{"type": "VariableDeclaration","start": 0,"end": 9,"declarations": [{"type": "VariableDeclarator","start": 4,"end": 9,"id": {"type": "Identifier","start": 4,"end": 5,"name": "a"},"init": {"type": "Literal","start": 8,"end": 9,"value": 1,"raw": "1"}}],"kind": "var"}],"sourceType": "module" } 復制代碼從這里看,要找的節點類型就是VariableDeclarator,下面開搞
export default function({ types: t }) {return {visitor: {VariableDeclarator(path, state) {if (path.node.id.name == 'a') {path.node.id = t.identifier('b')}}}} } 復制代碼我們要把id屬性是 a 的替換成 b 就好了。但是這里不能直接path.node.id.name = 'b'。如果操作的是object,就沒問題,但是這里是 AST 語法樹,所以想改變某個值,就是用對應的 AST 來替換,現在我們用新的標識符來替換這個屬性。
測試一下
import * as babel from '@babel/core'; const c = `var a = 1`;const { code } = babel.transform(c, {plugins: [function({ types: t }) {return {visitor: {VariableDeclarator(path, state) {if (path.node.id.name == 'a') {path.node.id = t.identifier('b')}}}}}] })console.log(code); // var b = 1 復制代碼實現一個簡單的按需打包功能
例如我們要實現把import { Button } from 'antd'轉成import Button from 'antd/lib/button'
通過對比 AST 發現,specifiers里的type和source不同。
// import { Button } from 'antd' "specifiers": [{"type": "ImportSpecifier",...} ] 復制代碼// import Button from 'antd/lib/button' "specifiers": [{"type": "ImportDefaultSpecifier",...} ] 復制代碼import * as babel from '@babel/core'; const c = `import { Button } from 'antd'`;const { code } = babel.transform(c, {plugins: [function({ types: t }) {return {visitor: {ImportDeclaration(path) {const { node: { specifiers, source } } = path;if (!t.isImportDefaultSpecifier(specifiers[0])) { // 對 specifiers 進行判斷const newImport = specifiers.map(specifier => (t.importDeclaration([t.ImportDefaultSpecifier(specifier.local)],t.stringLiteral(`${source.value}/lib/${specifier.local.name}`))))path.replaceWithMultiple(newImport)}}}}}] })console.log(code); // import Button from "antd/lib/Button"; 復制代碼總結
主要介紹了一下幾個babel的 API,和babel編譯代碼的過程以及簡單編寫了一個babel插件
總結
以上是生活随笔為你收集整理的新的一年babel了解一下的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: jquery事件都有哪些(jQuery)
- 下一篇: CSS 实现 0.5px 边框线
