摘要:是一個(gè)對(duì)象,它表示兩個(gè)節(jié)點(diǎn)之間的連接。接著返回一個(gè)對(duì)象,其屬性是這個(gè)插件的主要節(jié)點(diǎn)訪問(wèn)者。所以上面的執(zhí)行方式是運(yùn)行引入了自定義插件的打包文件現(xiàn)在為明顯減小,自定義插件成功插件文件目錄覺(jué)得好玩就關(guān)注一下歡迎大家收藏寫(xiě)評(píng)論
目錄
Babel簡(jiǎn)介
Babel運(yùn)行原理
AST解析
AST轉(zhuǎn)換
寫(xiě)一個(gè)Babel插件
Babel簡(jiǎn)介Babel 是一個(gè) JavaScript 編譯器,它能將es2015,react等低端瀏覽器無(wú)法識(shí)別的語(yǔ)言,進(jìn)行編譯。
上圖的左邊代碼中有箭頭函數(shù),Babel將進(jìn)行了源碼轉(zhuǎn)換,下面我們來(lái)看Babel的運(yùn)行原理。
Babel運(yùn)行原理Babel 的三個(gè)主要處理步驟分別是:
解析(parse),轉(zhuǎn)換(transform),生成(generate)。.
其過(guò)程分解用語(yǔ)言描述的話,就是下面這樣:
解析
使用 babylon 解析器對(duì)輸入的源代碼字符串進(jìn)行解析并生成初始 AST(File.prototype.parse)
利用 babel-traverse 這個(gè)獨(dú)立的包對(duì) AST 進(jìn)行遍歷,并解析出整個(gè)樹(shù)的 path,通過(guò)掛載的 metadataVisitor 讀取對(duì)應(yīng)的元信息,這一步叫 set AST 過(guò)程
轉(zhuǎn)換
transform 過(guò)程:遍歷 AST 樹(shù)并應(yīng)用各 transformers(plugin) 生成變換后的 AST 樹(shù)
babel 中最核心的是 babel-core,它向外暴露出 babel.transform 接口。
let result = babel.transform(code, { plugins: [ arrayPlugin ] })
生成
利用 babel-generator 將 AST 樹(shù)輸出為轉(zhuǎn)碼后的代碼字符串
AST解析AST解析會(huì)把拿到的語(yǔ)法,進(jìn)行樹(shù)形遍歷,對(duì)語(yǔ)法的每個(gè)節(jié)點(diǎn)進(jìn)行響應(yīng)的變化和改造再生產(chǎn)新的代碼字符串
節(jié)點(diǎn)(node)AST將開(kāi)頭提到的箭頭函數(shù)轉(zhuǎn)根據(jù)節(jié)點(diǎn)換為節(jié)點(diǎn)樹(shù)
ES2015箭頭函數(shù)
codes.map(code=>{ return code.toUpperCase() })
AST樹(shù)形遍歷轉(zhuǎn)換后的結(jié)構(gòu)
{ type:"ExpressionStatement", expression:{ type:"CallExpression" callee:{ type:"MemberExpression", computed:false object:{ type:"Identifier", name:"codes" } property:{ type:"Identifier", name:"map" } range:[] } arguments:{ { type:"ArrowFunctionExpression", id:null, params:{ type:"Identifier", name:"code", range:[] } body:{ type:"BlockStatement" body:{ type:"ReturnStatement", argument:{ type:"CallExpression", callee:{ type:"MemberExpression" computed:false object:{ type:"Identifier" name:"code" range:[] } property:{ type:"Identifier" name:"toUpperCase" } range:[] } range:[] } } range:[] } generator:false expression:false async:false range:[] } } } }
我們從 ExpressionStatement開(kāi)始往樹(shù)形結(jié)構(gòu)里面走,看到它的內(nèi)部屬性有callee,type,arguments,所以我們?cè)僖来卧L問(wèn)每一個(gè)屬性及它們的子節(jié)點(diǎn)。
于是就有了如下的順序
進(jìn)入 ExpressionStatement 進(jìn)入 CallExpression 進(jìn)入 MemberExpression 進(jìn)入 Identifier 離開(kāi) Identifier 進(jìn)入 Identifier 離開(kāi) Identifier 離開(kāi) MemberExpression 進(jìn)入 ArrowFunctionExpression 進(jìn)入 Identifier 離開(kāi) Identifier 進(jìn)入 BlockStatement 進(jìn)入 ReturnStatement 進(jìn)入 CallExpression 進(jìn)入 MemberExpression 進(jìn)入 Identifier 離開(kāi) Identifier 進(jìn)入 Identifier 離開(kāi) Identifier 離開(kāi) MemberExpression 離開(kāi) CallExpression 離開(kāi) ReturnStatement 離開(kāi) BlockStatement 離開(kāi) ArrowFunctionExpression 離開(kāi) CallExpression 離開(kāi) ExpressionStatement 離開(kāi) Program
Babel 的轉(zhuǎn)換步驟全都是這樣的遍歷過(guò)程。(有點(diǎn)像koa的洋蔥模型??)
AST轉(zhuǎn)換解析好樹(shù)結(jié)構(gòu)后,我們手動(dòng)對(duì)箭頭函數(shù)進(jìn)行轉(zhuǎn)換。
對(duì)比兩張圖,發(fā)現(xiàn)不一樣的地方就是兩個(gè)函數(shù)的arguments.type
let babel = require("babel-core");//babel核心庫(kù) let types = require("babel-types"); let code = `codes.map(code=>{return code.toUpperCase()})`;//轉(zhuǎn)換語(yǔ)句 let visitor = { ArrowFunctionExpression(path) {//定義需要轉(zhuǎn)換的節(jié)點(diǎn) let params = path.node.params let blockStatement = path.node.body let func = types.functionExpression(null, params, blockStatement, false, false) path.replaceWith(func) // } } let arrayPlugin = { visitor } let result = babel.transform(code, { plugins: [ arrayPlugin ] }) console.log(result.code)
注意: ArrowFunctionExpression() { ... } 是 ArrowFunctionExpression: { enter() { ... } } 的簡(jiǎn)寫(xiě)形式。
Path 是一個(gè)對(duì)象,它表示兩個(gè)節(jié)點(diǎn)之間的連接。
定義需要轉(zhuǎn)換的節(jié)點(diǎn)
ArrowFunctionExpression(path) { ...... }
創(chuàng)建用來(lái)替換的節(jié)點(diǎn)
types.functionExpression(null, params, blockStatement, false, false)
babel-types文檔鏈接
在node節(jié)點(diǎn)上找到需要的參數(shù)
replaceWith(替換)
寫(xiě)一個(gè)Babel插件從一個(gè)接收了 babel 對(duì)象作為參數(shù)的 function 開(kāi)始。
export default function(babel) { // plugin contents }
接著返回一個(gè)對(duì)象,其 visitor 屬性是這個(gè)插件的主要節(jié)點(diǎn)訪問(wèn)者。
export default function({ types: t }) { return { visitor: { // visitor contents } }; };
我們?nèi)粘R胍蕾嚨臅r(shí)候,會(huì)將整個(gè)包引入,導(dǎo)致打包后的代碼太冗余,加入了許多不需要的模塊,比如index.js三行代碼,打包后的文件大小就達(dá)到了483 KiB,
index.js
import { flatten, join } from "lodash"; let arr = [1, [2, 3], [4, [5]]]; let result = _.flatten(arr);
所以我們這次的目的是將
import { flatten, join } from "lodash";
轉(zhuǎn)換為從而只引入兩個(gè)lodash模塊,減少打包體積
import flatten from "lodash/flatten"; import join from "lodash/join";
實(shí)現(xiàn)步驟如下:
在項(xiàng)目下的node_module中新建文件夾 babel-plugin-extraxt
注意:babel插件文件夾的定義方式是 babel-plugin-插件名
我們可以在.babelrc的plugin中引入自定義插件 或者在webpack.config.js的loader options中加入自定義插件
在babel-plugin-extraxt新建index.js
module.exports = function ({types:t}) { return { // 對(duì)import轉(zhuǎn)碼 visitor:{ ImportDeclaration(path, _ref = { opts: {} }) { const specifiers = path.node.specifiers; const source = path.node.source; // 只有l(wèi)ibraryName滿足才會(huì)轉(zhuǎn)碼 if (_ref.opts.library == source.value && (!t.isImportDefaultSpecifier(specifiers[0]))) { //_ref.opts是傳進(jìn)來(lái)的參數(shù) var declarations = specifiers.map((specifier) => { //遍歷 uniq extend flatten cloneDeep return t.ImportDeclaration( //創(chuàng)建importImportDeclaration節(jié)點(diǎn) [t.importDefaultSpecifier(specifier.local)], t.StringLiteral(`${source.value}/${specifier.local.name}`) ) }) path.replaceWithMultiple(declarations) } } } }; }
修改webpack.prod.config.js中babel-loader的配置項(xiàng),在plugins中添加自定義的插件名
rules: [{ test: /.js$/, loader: "babel-loader", options: { presets: ["env","stage-0"], plugins: [ ["extract", { "library":"lodash"}], ["transform-runtime", {}] ] } }]
注意:plugins 的插件使用順序是順序的,而 preset 則是逆序的。所以上面的執(zhí)行方式是extract>transform-runtime>env>stage-0
運(yùn)行引入了自定義插件的webpack.config.js
打包文件現(xiàn)在為21.4KiB,明顯減小,自定義插件成功!~
插件文件目錄
YUAN-PLUGINS | | - node_modules | | | | - babel-plugins-extract | | | index.js | | - src | | - index.js | | - webpack.config.js覺(jué)得好玩就關(guān)注一下~歡迎大家收藏寫(xiě)評(píng)論~~~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93831.html
摘要:的工作過(guò)程的處理主要過(guò)程解析轉(zhuǎn)換生成。代碼轉(zhuǎn)換處理,處理工具插件等就是在這個(gè)階段進(jìn)行代碼轉(zhuǎn)換,返回新的。若感興趣了解更多內(nèi)容,插件中文開(kāi)發(fā)文檔提供了很多詳細(xì)資料。 Babel簡(jiǎn)介 Babel是Javascript編譯器,是種代碼到代碼的編譯器,通常也叫做『轉(zhuǎn)換編譯器』。 Babel的工作過(guò)程 Babel的處理主要過(guò)程:解析(parse)、轉(zhuǎn)換(transform)、生成(generat...
摘要:最近的技術(shù)項(xiàng)目里大量用到了需要修改源文件代碼的需求,也就理所當(dāng)然的用到了及其插件開(kāi)發(fā)。在這里我要推薦一款實(shí)現(xiàn)了這些標(biāo)簽的插件,建議在你的項(xiàng)目中加入這個(gè)插件并用起來(lái),不用再艱難的書(shū)寫(xiě)三元運(yùn)算符,會(huì)大大提升你的開(kāi)發(fā)效率。具體可以參見(jiàn)插件手冊(cè)。 最近的技術(shù)項(xiàng)目里大量用到了需要修改源文件代碼的需求,也就理所當(dāng)然的用到了Babel及其插件開(kāi)發(fā)。這一系列專題我們介紹下Babel相關(guān)的知識(shí)及使用。 ...
摘要:繼個(gè)實(shí)例入門(mén)并掌握二后續(xù)配置配置配置使用加快打包速度多頁(yè)面打包配置編寫(xiě)編寫(xiě)編寫(xiě)十七配置源碼地址本節(jié)使用的代碼為基礎(chǔ)我們來(lái)模擬平時(shí)開(kāi)發(fā)中,將打包完的代碼防止到服務(wù)器上的操作,首先打包代碼然后安裝一個(gè)插件在中配置一個(gè)命令運(yùn) 繼 24 個(gè)實(shí)例入門(mén)并掌握「Webpack4」(二) 后續(xù): PWA 配置 TypeScript 配置 Eslint 配置 使用 DLLPlugin 加快打包速度 多...
摘要:表示的是在嚴(yán)格模式下解析并且允許模塊定義即能識(shí)別和語(yǔ)法識(shí)別不了。 前段時(shí)間開(kāi)始研究ast,然后慢慢的順便把babel都研究了,至于ast稍后的時(shí)間會(huì)寫(xiě)一篇介紹性博客專門(mén)介紹ast,本博客先介紹一下babel的基本知識(shí)點(diǎn)。 背景: 由于現(xiàn)在前端出現(xiàn)了很多非es5的語(yǔ)法,如jsx,.vue,ts等等的格式和寫(xiě)法,如果要在瀏覽器的設(shè)備上識(shí)別并執(zhí)行,需要額外將這些非傳統(tǒng)格式的語(yǔ)法轉(zhuǎn)成傳統(tǒng)的es...
摘要:而掃描各個(gè)模塊并合并路由表的腳本非常簡(jiǎn)單,讀寫(xiě)文件就了。編寫(xiě)插件之前先要理解抽象語(yǔ)法樹(shù)這個(gè)概念。的解析器,的配置。編寫(xiě)腳本識(shí)別字段思路首先獲取到源代碼是類單文件的語(yǔ)法。獲取內(nèi)的字段,并替換成已生成的路由表。 話不多說(shuō)先上圖,簡(jiǎn)要說(shuō)明一下干了些什么事。圖可能太模糊,可以點(diǎn)svg看看showImg(https://segmentfault.com/img/bV3fs4?w=771&h=63...
閱讀 3631·2021-11-24 10:22
閱讀 3701·2021-11-22 09:34
閱讀 2501·2021-11-15 11:39
閱讀 1537·2021-10-14 09:42
閱讀 3672·2021-10-08 10:04
閱讀 1565·2019-08-30 15:52
閱讀 858·2019-08-30 13:49
閱讀 3028·2019-08-30 11:21