成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Babel從入門到插件開發(fā)

Jinkey / 869人閱讀

摘要:最近的技術(shù)項(xiàng)目里大量用到了需要修改源文件代碼的需求,也就理所當(dāng)然的用到了及其插件開發(fā)。在這里我要推薦一款實(shí)現(xiàn)了這些標(biāo)簽的插件,建議在你的項(xiàng)目中加入這個(gè)插件并用起來(lái),不用再艱難的書寫三元運(yùn)算符,會(huì)大大提升你的開發(fā)效率。具體可以參見插件手冊(cè)。

最近的技術(shù)項(xiàng)目里大量用到了需要修改源文件代碼的需求,也就理所當(dāng)然的用到了Babel及其插件開發(fā)。這一系列專題我們介紹下Babel相關(guān)的知識(shí)及使用。

對(duì)于剛開始接觸代碼編譯轉(zhuǎn)換的同學(xué),單純的介紹Babel相關(guān)的概念只是會(huì)當(dāng)時(shí)都能看懂,但是到了自己去實(shí)現(xiàn)一個(gè)需求的時(shí)候就又會(huì)變得不知所措,所以我們?cè)俳榻B中穿插一些例子。

大概分為以下幾塊:

0、Babel基礎(chǔ)介紹
1、使用npm上好用的Babel插件提升開發(fā)效率
2、使用Babel做代碼轉(zhuǎn)換使用到的模塊及執(zhí)行流程
3、示例:類中插入方法、類方法中插入代碼
4、Babel插件開發(fā)介紹
5、示例:通過(guò)Babel實(shí)現(xiàn)打包構(gòu)建優(yōu)化 -- 組件模塊按需打包

0.Babel基礎(chǔ)介紹

用到的名詞:

AST:Abstract Syntax Tree, 抽象語(yǔ)法樹

DI: Dependency Injection, 依賴注入

我們?cè)趯?shí)際的開發(fā)過(guò)程中,經(jīng)常有需要修改js源代碼的需求,比如一下幾種情形:

ES6/7轉(zhuǎn)化為瀏覽器可支持的ES5甚至ES3代碼;

JSX代碼轉(zhuǎn)化為js代碼(原來(lái)是Facebook團(tuán)隊(duì)支持在瀏覽器中執(zhí)行轉(zhuǎn)換,現(xiàn)在轉(zhuǎn)到在babel插件中維護(hù));

部分js新的特性動(dòng)態(tài)注入(用的比較多的就是babel-plugin-transform-runtime);

一些便利性特性支持,比如:React If/Else/For/Switch等標(biāo)簽支持;

于是,我們就需要一款支持動(dòng)態(tài)修改js源代碼的模塊,babel則是用的最多的一個(gè)。

Babel的解析引擎

Babel使用的引擎是babylon,babylon并非由babel團(tuán)隊(duì)自己開發(fā)的,而是fork的acorn項(xiàng)目,不過(guò)acorn引擎只提供基本的解析ast的能力,遍歷還需要配套的acorn-travesal, 替換節(jié)點(diǎn)需要使用acorn-,而這些開發(fā),在Babel的插件體系開發(fā)下,變得一體化了。

如何使用

使用方式有很多種:

webpack中作為js(x)文件的loader使用;

多帶帶在Node代碼中引入使用;

命令行中使用:
package.json中配置:

"scripts": {

"build": "rimraf lib && babel src --out-dir lib"

}

命令中執(zhí)行:npm run build。

通常,如果我們?cè)陧?xiàng)目根目錄下配置一個(gè).babelrc文件,其配置規(guī)則會(huì)被babel引入并使用。

1、使用npm上好用的Babel插件提升開發(fā)效率

在使用webpack做打包工具的時(shí)候,我們隊(duì)js(x)文件使用的loader通常就是babel-loader,babel只是提供了最基礎(chǔ)的代碼編譯能力,主要用到的一些代碼轉(zhuǎn)換則是通過(guò)插件的方式實(shí)現(xiàn)的。在loader中配置插件有兩種方式:presets及plugins,這里要注意presets配置的也是插件,只是優(yōu)先級(jí)比較高,而且他的執(zhí)行順序是從左到右的,而plugins的優(yōu)先級(jí)順序則是從右到左的。我們經(jīng)常用到的插件會(huì)包括:ES6/7轉(zhuǎn)ES5代碼的babel-plugin-es2015,React jsx代碼轉(zhuǎn)換的babel-plugin-react,對(duì)新的js標(biāo)準(zhǔn)特性有不同支持程度的babel-plugin-stage-0等(不同階段js標(biāo)準(zhǔn)特性的制定是不一樣的,babel插件支持程度也就不一樣,0表示完全支持),將瀏覽器里export語(yǔ)法轉(zhuǎn)換為common規(guī)范exports/module.exports的babel-plugin-add-module-exports,根據(jù)運(yùn)行時(shí)動(dòng)態(tài)插入polyfill的babel-plugin-transform-runtime(絕不建議使用babel-polyfill,一股腦將所有polyfill插入,打的包會(huì)很大),對(duì)Generator進(jìn)行編譯的babel-plugin-transform-regenerator等。想了解更多的配置可以參見這篇文章:如何寫好.babelrc?Babel的presets和plugins配置解析(https://excaliburhan.com/post...)

如果你是基于完全組件化(標(biāo)簽式)的開發(fā)模式的話,如果能提供常用的控制流標(biāo)簽如:If/ElseIf/Else/For/Switch/Case等給我們的話,那么我們的開發(fā)效率則會(huì)大大提升。在這里我要推薦一款實(shí)現(xiàn)了這些標(biāo)簽的babel插件:jsx-control-statement,建議在你的項(xiàng)目中加入這個(gè)插件并用起來(lái),不用再艱難的書寫三元運(yùn)算符,會(huì)大大提升你的開發(fā)效率。

2、使用Babel做代碼轉(zhuǎn)換使用到的模塊及執(zhí)行流程

Babel將源碼轉(zhuǎn)換AST之后,通過(guò)遍歷AST樹(其實(shí)就是一個(gè)js對(duì)象),對(duì)樹做一些修改,然后再將AST轉(zhuǎn)成code,即成源碼。

將js源碼轉(zhuǎn)換為AST用到的模塊叫:babylon,對(duì)樹進(jìn)行遍歷并做修改用到的模塊叫:babel-traverse,將修改后的AST再生成js代碼用到的模塊則是:babel-generator。而babel-core模塊則是將三者結(jié)合使得對(duì)外提供的API做了一個(gè)簡(jiǎn)化,使用babel-core只需要執(zhí)行以下的簡(jiǎn)單代碼即可:

import { transform } from "babel-core";
var result = babel.transform("code();", options);
result.code;
result.map;
result.ast;

我們?cè)贜ode中使用的時(shí)候一般都是使用的三步轉(zhuǎn)換的方式,方便做更多的配置及操作。所以整個(gè)的難點(diǎn)主要就在對(duì)AST的操作上,為了能對(duì)AST做一些操作后進(jìn)而能對(duì)js代碼做到修改,babel對(duì)js代碼語(yǔ)法提供了各種類型,比如:箭頭函數(shù)類型ArrowFunctionExpression,for循環(huán)里的continue語(yǔ)句類型:ContinueStatement等等,我們主要就是根據(jù)這些不同的語(yǔ)法類型來(lái)對(duì)AST做操作(生成/替換/增加/刪除節(jié)點(diǎn)),具體有哪些類型全部在:babel-types(https://www.npmjs.com/package...。

其實(shí)整個(gè)大的操作流程還是比較簡(jiǎn)單的,我們直接上例子好了。

3、示例 Babel使用案例0:往類中插入方法

比如我們有這樣的需求:我們有一個(gè)jsx代碼模板,該模板中有一個(gè)類似與下面的組件類:

class MyComponent extends React.Component {
    constructor(props, context) {
        super(props, context);
    }

    // 其他代碼
}

我們會(huì)需要根據(jù)當(dāng)前的DSL生成對(duì)應(yīng)的render方法并插入進(jìn)MyComponent組件類中,該如何實(shí)現(xiàn)呢?

上面已經(jīng)講到,我們對(duì)代碼的操作其實(shí)是通過(guò)對(duì)代碼生成的AST操作生成一個(gè)新的AST來(lái)完成的,而對(duì)AST的操作則是通過(guò)babel-traverse這個(gè)庫(kù)來(lái)實(shí)現(xiàn)的。

該庫(kù)通過(guò)簡(jiǎn)單的hooks函數(shù)的方式,給我們提供了在遍歷AST時(shí)可以操作當(dāng)前被遍歷到的節(jié)點(diǎn)的相關(guān)操作,要獲取并修改(增刪改查)當(dāng)前節(jié)點(diǎn),我們需要知道AST都有哪些節(jié)點(diǎn)類型,而所有的節(jié)點(diǎn)類型都存放于babel-types這個(gè)庫(kù)中。我們先看完整的實(shí)現(xiàn)代碼,然后再分析:

// 先引入相關(guān)的模塊
const babylon = require("babylon");
const Traverse = require("babel-traverse").default;
const generator = require("babel-generator").default;
const Types = require("babel-types");
const babel = require("babel-core");

// === helpers ===

// 將js代碼編譯成AST
 function parse2AST(code) {
    return babylon.parse(code, {
        sourceType: "module",
        plugins: [
            "asyncFunctions",
            "classConstructorCall",
            "jsx",
            "flow",
            "trailingFunctionCommas",
            "doExpressions",
            "objectRestSpread",
            "decorators",
            "classProperties",
            "exportExtensions",
            "exponentiationOperator",
            "asyncGenerators",
            "functionBind",
            "functionSent"
        ]
    });
}

// 直接將一小段js通過(guò)babel.template生成對(duì)應(yīng)的AST
function getTemplateAst(tpl, opts = {}) {
    let ast = babel.template(tpl, opts)({});

    if (Array.isArray(ast)) {
        return ast;
    } else {
        return [ast];
    }
}

/**
 *  檢測(cè)傳入?yún)?shù)是否已在插入代碼中定義
 */
checkParams = function(argv, newAst) {
    let params = [];
    const vals = getAstVals(newAst);
    if (argv && argv.length !== 0) {
        for (let i = 0; i < argv.length; i++) {
            if (vals.indexOf(argv[i]) === -1) {
                params.push(Types.identifier(argv[i]));
            } else {
                throw TypeError("參數(shù)名" + argv[i] + "已在插入代碼中定義,請(qǐng)更名");
            }
        }
    }
    return params;
}

const code = `
    class MyComponent extends React.Component {
        constructor(props, context) {
            super(props, context);
        }

        // 其他代碼
    }
`;

const insert = [
    {
        // name為方法名
        name: "render",
        // body為方法體
        body: `
            return (
                
我是render方法的返回內(nèi)容
); `, // 方法參數(shù) argv: null, // 如果原來(lái)的Class有同名方法則強(qiáng)制覆蓋 isCover: true } ]; const ast = parse2AST(code); Traverse(ast, { // ClassBody表示當(dāng)前類本身節(jié)點(diǎn) ClassBody(path) { if (!Array.isArray(insert)) { throw TypeError("插入字段類型必須為數(shù)組"); } for (let key in insert) { const methodObj = insert[key], name = methodObj.name, argv = methodObj.argv, body = methodObj.body, isCover = methodObj.isCover; if (typeof name !== "string") { throw TypeError("方法名必須為字符串"); } const newAst = getTemplateAst(body, { sourceType: "script" }); const params = checkParams(argv, newAst); // 通過(guò)Types.ClassMethodAPI,生成方法AST const property = Types.ClassMethod("method", Types.identifier(name), params, Types.BlockStatement(newAst)); // 插入進(jìn)AST path.node.body.push(property); } } }); console.log(generator(ast).code);

其中,最核心的地方就是下面的這一行代碼:

const property = Types.ClassMethod("method", Types.identifier(name), params, Types.BlockStatement(newAst));

確定好我們要進(jìn)行怎么樣的操作(比如要往一個(gè)類中插入一個(gè)方法),休閑要確定是怎樣的鉤子名(這里是ClassBody),然后通過(guò)要插入的代碼生成對(duì)應(yīng)的AST,生成AST可以通過(guò)Babel.Types的相關(guān)方法一點(diǎn)點(diǎn)生成,但是這里有個(gè)比較方便的API:babel.template,然后通過(guò)path的相關(guān)操作將新生成的AST插入即可。

穿插:AST樹的創(chuàng)建方法

一些AST樹的創(chuàng)建方法,有:
1、使用babel-types定義的創(chuàng)建方法創(chuàng)建
比如創(chuàng)建一個(gè)var a = 1;

types.VariableDeclaration(
     "var",
     [
        types.VariableDeclarator(
                types.Identifier("a"), 
                types.NumericLiteral(1)
        )
     ]
)

如果使用這樣創(chuàng)建一個(gè)ast節(jié)點(diǎn),肯定要累死了,可以:

使用replaceWithSourceString方法創(chuàng)建替換

使用template方法來(lái)創(chuàng)建AST結(jié)點(diǎn)

template方法其實(shí)也是babel體系中的一部分,它允許使用一些模板來(lái)創(chuàng)建ast節(jié)點(diǎn)

比如上面的var a = 1可以使用:

var gen = babel.template(`var NAME = VALUE;`);
 
var ast = gen({
    NAME: t.Identifier("a"), 
    VALUE: t.NumberLiteral(1)
});

也可以簡(jiǎn)單寫:

var gen = babel.template(`var a = 1;`);
 
var ast = gen({});
Babel使用案例1:往類的方法中插入代碼

這個(gè)案例會(huì)更復(fù)雜一點(diǎn),大家可以先試著去實(shí)現(xiàn)下,明天再講解具體實(shí)現(xiàn)。

往方法中要插入代碼,我們先找下類中方法的babel-types值是什么,查閱文檔:https://www.npmjs.com/package...可以發(fā)現(xiàn)是叫:ClassMethod。于是就可以像下面這樣實(shí)現(xiàn):

const injectCode = [{
    name: "constructor",
    code: insertCodeNext,
}];

const ast = parse2AST(originCode);
Traverse(ast, {
    ClassMethod(path) {
        if (!Array.isArray(injectCode)) {
            throw TypeError("插入字段類型必須為數(shù)組");
        }

        // 獲取當(dāng)前方法的名字
        const methodName = path.get("body").container.key.name;

        for (let key in injectCode) {
            const inject = injectCode[key],
                name = inject.name,
                code = inject.code,
                pos = inject.pos;

            if (methodName === name) {
                const newAst = getTemplateAst(code, {
                    sourceType: "script"
                });

                if (pos === "prev") {
                    Array.prototype.unshift.apply(path.node.body.body, newAst);
                } else {
                    Array.prototype.push.apply(path.node.body.body, newAst);
                }
            }
        }
    }
});

console.log(generator(ast).code);

其實(shí)跟往Class中插入method一樣的道理。

4、Babel插件開發(fā)介紹

Babel的插件就是一個(gè)帶有babel參數(shù)的函數(shù),該函數(shù)返回類似于babel-traverse的配置對(duì)象,即下面的格式:

module.exports = function(babel) {
    var t = babel.types;

    return {
        visitor: {
            ImportDeclaration(path, ref) {
                var opts = ref.opts; // 配置的參數(shù)
            }
        }
    };
};

在babel插件的時(shí)候,配置的參數(shù)就會(huì)存放在ref參數(shù)里,見上面的代碼所所示。具體可以參見babel插件手冊(cè):https://github.com/thejamesky...。

下面我們看一個(gè)具體的示例。

5、示例:通過(guò)Babel實(shí)現(xiàn)打包構(gòu)建優(yōu)化 -- 組件模塊按需打包 需求

比如,我們有一個(gè)UI組件庫(kù),在入口文件中會(huì)把所有的組件放在這里,并export出對(duì)外服務(wù),大概類似于如下的代碼:

export Button from "./lib/button/index.js";
export Input from "./lib/input/index.js";
// ......

那么我們?cè)谑褂玫臅r(shí)候就可以如下引用:

import {Button} from "ant"

這樣就有一個(gè)問(wèn)題,就是比如我們只是用了一個(gè)Button組件,這樣引用就會(huì)導(dǎo)致會(huì)把所有的組件打包進(jìn)來(lái),導(dǎo)致整個(gè)js文件會(huì)非常大。我們能不能把代碼動(dòng)態(tài)實(shí)時(shí)的編譯成如下的代碼來(lái)解決這個(gè)問(wèn)題?

import Button from "ant/lib/button";

我們可以寫個(gè)babel插件來(lái)實(shí)現(xiàn)這樣的需求。

// 入口文件
var extend = require("extend");
var astExec = require("./ast-transform");

// 一些個(gè)變量預(yù)設(shè)
var NEXT_MODULE_NAME = "ant";
var NEXT_LIB_NAME = "lib";
var MEXT_LIB_NAME = "lib";

module.exports = function(babel) {
    var t = babel.types;

    return {
        visitor: {
            ImportDeclaration: function ImportDeclaration(path, _ref) {
                var opts = _ref.opts;
                var next = opts.next || {};

                var nextJsName = next.nextJsName || NEXT_MODULE_NAME;
                var nextCssName = next.nextCssName || NEXT_MODULE_NAME;
                var nextDir = next.dir || NEXT_LIB_NAME;
                var nextHasStyle = next.hasStyle;

                var node = path.node;

                var baseOptions = {
                    node: node,
                    path: path,
                    t: t,
                    jsBase: "",
                    cssBase: "",
                    hasStyle: false
                };

                if (!node) {
                    return;
                }

                var jsBase;
                var cssBase;

                if (node.source.value === nextJsName) {
                    jsBase = nextJsName + "/" + nextDir + "/";
                    cssBase = nextCssName + "/" + nextDir + "/";

                    astExec(extend(baseOptions, {
                        jsBase: jsBase,
                        cssBase: cssBase,
                        hasStyle: nextHasStyle
                    }));
                }
            }
        }
    };
};

這里將部分的功能多帶帶放到了一個(gè)ast-transform文件中,代碼如下:

function transformName(name) {
    if (!name)
        return "";
    return name.replace(/[A-Z]/g, function(ch, index) {
        if (index === 0)
            return ch.toLowerCase();
        return "-" + ch.toLowerCase();
    });
}

module.exports = function astExec(options) {
    var node = options.node; // 當(dāng)前節(jié)點(diǎn)
    var path = options.path; // path輔助處理變量
    var t = options.t; // babel-types
    var jsBase = options.jsBase;
    var cssBase = options.cssBase;
    var hasStyle = options.hasStyle;

    node.specifiers.forEach(specifier => {
        if (t.isImportSpecifier(specifier)) {
            var comName = specifier.imported.name;
            var lcomName = transformName(comName);
            var libName = jsBase + lcomName;
            var libCssName = cssBase + lcomName + "/index.scss";

            // AST節(jié)點(diǎn)操作
            path.insertAfter(t.importDeclaration([t.ImportDefaultSpecifier(t.identifier(comName))], t.stringLiteral(libName)));

            if (hasStyle) {
                path.insertAfter(t.importDeclaration([], t.stringLiteral(libCssName)));
            }
        }
    });

    // 把原來(lái)的代碼刪除掉
    path.remove();
};

這樣我們?cè)谟玫臅r(shí)候就可以像下面這樣使用:
.babelrc文件中像下面這樣配置即可:

{
  "presets": [...], // babel-preset-react等
  "plugins" :[
    [
      "armor-fusion",
      {
          next: {
              jsName: "ant", //js庫(kù)名,默認(rèn)值:ant
              cssName: "ant", //css庫(kù)名,當(dāng)如果其他的主題包時(shí),可以換成別的主題包名,默認(rèn)值:ant
              dir: "lib", //目錄名,一般不需要設(shè)置,默認(rèn)值:lib
              hasStyle: true //會(huì)編譯出scss引用,不加則默認(rèn)不會(huì)編譯
          }
      }
    ]
  ]
}

大家可以把上面比較實(shí)用的插件功能整理下放到自己的github上,也許能給你的面試加分也說(shuō)不定哦。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83916.html

相關(guān)文章

  • Babel插件開發(fā)入門示例詳解

    摘要:的工作過(guò)程的處理主要過(guò)程解析轉(zhuǎn)換生成。代碼轉(zhuǎn)換處理,處理工具插件等就是在這個(gè)階段進(jìn)行代碼轉(zhuǎn)換,返回新的。若感興趣了解更多內(nèi)容,插件中文開發(fā)文檔提供了很多詳細(xì)資料。 Babel簡(jiǎn)介 Babel是Javascript編譯器,是種代碼到代碼的編譯器,通常也叫做『轉(zhuǎn)換編譯器』。 Babel的工作過(guò)程 Babel的處理主要過(guò)程:解析(parse)、轉(zhuǎn)換(transform)、生成(generat...

    Muninn 評(píng)論0 收藏0
  • react-redux插件入門

    摘要:描述這個(gè)插件可以讓我們的代碼更加的簡(jiǎn)潔和美觀。安裝使用提供了兩個(gè)重要的接口使用了這個(gè)插件,的和就可以忘記來(lái),它們就用不著了?,F(xiàn)在有美女個(gè)。 可先查看我的redux簡(jiǎn)單入門 react-redux簡(jiǎn)介 react-redux是使用redux開發(fā)react時(shí)使用的一個(gè)插件,另外插一句,redux不是react的產(chǎn)品,vue和angular中也可以使用redux;下面簡(jiǎn)單講解,如何使用rea...

    Baaaan 評(píng)論0 收藏0
  • webpack入門及結(jié)合react進(jìn)行開發(fā)

    摘要:最后還可以跟我們的進(jìn)行結(jié)合管理代碼什么是說(shuō)明白點(diǎn)就是模塊打包機(jī),可以很好的管理我們的模塊,可以對(duì)瀏覽器進(jìn)行更好的兼容。安裝首選我們要安裝,中已經(jīng)給我們下載了我們通過(guò)進(jìn)行安裝管理。 webpack入門及結(jié)合react進(jìn)行開發(fā) 重要提示(2017年7月26號(hào)更新) 本文介紹的是最新版的3.4.1,并且其中又跟React結(jié)合的例子!showImg(https://segmentfault.c...

    OldPanda 評(píng)論0 收藏0
  • babel插件入門-AST

    摘要:是一個(gè)對(duì)象,它表示兩個(gè)節(jié)點(diǎn)之間的連接。接著返回一個(gè)對(duì)象,其屬性是這個(gè)插件的主要節(jié)點(diǎn)訪問(wèn)者。所以上面的執(zhí)行方式是運(yùn)行引入了自定義插件的打包文件現(xiàn)在為明顯減小,自定義插件成功插件文件目錄覺得好玩就關(guān)注一下歡迎大家收藏寫評(píng)論 目錄 Babel簡(jiǎn)介 Babel運(yùn)行原理 AST解析 AST轉(zhuǎn)換 寫一個(gè)Babel插件 Babel簡(jiǎn)介 Babel 是一個(gè) JavaScript 編譯器,它能將es...

    sanyang 評(píng)論0 收藏0
  • ES2015入門系列9-Babel和Rollup

    摘要:雖然夠好用,奈何沒有瀏覽器對(duì)其可以完全支持,本文書寫時(shí)間,開發(fā)版號(hào)稱已經(jīng)支持的特性。開始安裝本系列假定讀者都有使用經(jīng)驗(yàn),如果還沒有,趕緊去這里了解并安裝吧。到此,我們的已經(jīng)準(zhǔn)備就緒。 通過(guò)前面章節(jié)的講解,大家對(duì)ES2015的一些新語(yǔ)法有了初步的理解,之前我們的測(cè)試代碼都可以直接放入 Chrome Console 中直接運(yùn)行,為了更好的學(xué)習(xí)后面的面向?qū)ο蠛湍K開發(fā),我先用一章介紹一下 B...

    eccozhou 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<