<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          簡(jiǎn)單實(shí)現(xiàn) babel-plugin-import 插件

          共 15015字,需瀏覽 31分鐘

           ·

          2021-02-02 00:51

          前言

          平時(shí)在使用?antd、element?等組件庫(kù)的時(shí)候,都會(huì)使用到一個(gè)?Babel?插件:babel-plugin-import,這篇文章通過(guò)例子和分析源碼簡(jiǎn)單說(shuō)一下這個(gè)插件做了一些什么事情,并且實(shí)現(xiàn)一個(gè)最小可用版本。

          插件地址:https://github.com/ant-design/babel-plugin-import[1]

          babel-plugin-import 介紹

          Why:為什么需要這個(gè)插件

          antd?和?element?這兩個(gè)組件庫(kù),看它的源碼,?index.js?分別是這樣的:

          //?antd
          export?{?default?as?Button?}?from?'./button';
          export?{?default?as?Table?}?from?'./table';
          //?element
          import?Button?from?'../packages/button/index.js';
          import?Table?from?'../packages/table/index.js';
          export?default?{
          ??Button,
          ??Table,
          };

          antd?和?element?都是通過(guò)?ES6 Module?的?export?來(lái)導(dǎo)出帶有命名的各個(gè)組件。

          所以,我們可以通過(guò)?ES6?的?import { } from?的語(yǔ)法來(lái)導(dǎo)入單組件的?JS?文件。但是,我們還需要手動(dòng)引入組件的樣式:

          //?antd
          import?'antd/dist/antd.css';
          //?element
          import?'element-ui/lib/theme-chalk/index.css';

          如果僅僅是只需要一個(gè)?Button?組件,卻把所有的樣式都引入了,這明顯是不合理的。

          當(dāng)然,你說(shuō)也可以只使用單個(gè)組件啊,還可以減少代碼體積:

          import?Button?from?'antd/lib/button';
          import?'antd/lib/button/style';

          PS:類似?antd?的組件庫(kù)提供了?ES Module?的構(gòu)建產(chǎn)物,直接通過(guò)?import {} from?的形式也可以?tree-shaking,這個(gè)不在今天的話題之內(nèi),就不展開(kāi)說(shuō)了~

          對(duì),這沒(méi)毛病。但是,看一下如果我們需要多個(gè)組件的時(shí)候:

          import?{?Affix,?Avatar,?Button,?Rate?}?from?'antd';

          import?'antd/lib/affix/style';
          import?'antd/lib/avatar/style';
          import?'antd/lib/button/style';
          import?'antd/lib/rate/style';

          會(huì)不會(huì)覺(jué)得這樣的代碼不夠優(yōu)雅?如果是我,甚至想打人。

          這時(shí)候就應(yīng)該思考一下,如何在引入?Button?的時(shí)候自動(dòng)引入它的樣式文件。

          What:這個(gè)插件做了什么

          簡(jiǎn)單來(lái)說(shuō),babel-plugin-import?就是解決了上面的問(wèn)題,為組件庫(kù)實(shí)現(xiàn)單組件按需加載并且自動(dòng)引入其樣式,如:

          import?{?Button?}?from?'antd';

          ??????↓?↓?↓?↓?↓?↓

          var?_button?=?require('antd/lib/button');
          require('antd/lib/button/style');

          只需關(guān)心需要引入哪些組件即可,內(nèi)部樣式我并不需要關(guān)心,你幫我自動(dòng)引入就 ok。

          How:這個(gè)插件怎么用

          簡(jiǎn)單來(lái)說(shuō)就需要關(guān)心三個(gè)參數(shù)即可:

          {
          ??"libraryName":?"antd",?????//?包名
          ??"libraryDirectory":?"lib",?//?目錄,默認(rèn)?lib
          ??"style":?true,?????????????//?是否引入?style
          }

          其它的看文檔:https://github.com/ant-design/babel-plugin-import#usage[2]

          babel-plugin-import 源碼分析

          主要來(lái)看一下?babel-plugin-import?如何加載?JavaScript?代碼和樣式的。

          以下面這段代碼為例:

          import?{?Button,?Rate?}?from?'antd';
          ReactDOM.render(<Button>xxxxButton>);

          第一步 依賴收集

          babel-plubin-import?會(huì)在?ImportDeclaration?里將所有的?specifier?收集起來(lái)。

          先看一下?ast?吧:

          可以從這個(gè)?ImportDeclaration?語(yǔ)句中提取幾個(gè)關(guān)鍵點(diǎn):

          • source.value: antd
          • specifier.local.name: Button
          • specifier.local.name: Rate

          需要做的事情也很簡(jiǎn)單:

          1. import?的包是不是?antd,也就是?libraryName
          2. 把?Button?和?Rate?收集起來(lái)

          來(lái)看代碼:

          ImportDeclaration(path,?state)?{
          ??const?{?node?}?=?path;
          ??if?(!node)?return;
          ??//?代碼里?import?的包名
          ??const?{?value?}?=?node.source;
          ??//?配在插件?options?的包名
          ??const?{?libraryName?}?=?this;
          ??//?babel-type?工具函數(shù)
          ??const?{?types?}?=?this;
          ??//?內(nèi)部狀態(tài)
          ??const?pluginState?=?this.getPluginState(state);
          ??//?判斷是不是需要使用該插件的包
          ??if?(value?===?libraryName)?{
          ????//?node.specifiers?表示?import?了什么
          ????node.specifiers.forEach(spec?=>?{
          ??????//?判斷是不是?ImportSpecifier?類型的節(jié)點(diǎn),也就是是否是大括號(hào)的
          ??????if?(types.isImportSpecifier(spec))?{
          ????????//?收集依賴
          ????????//?也就是?pluginState.specified.Button?=?Button
          ????????//?local.name?是導(dǎo)入進(jìn)來(lái)的別名,比如?import?{?Button?as?MyButton?}?from?'antd'?的?MyButton
          ????????//?imported.name?是真實(shí)導(dǎo)出的變量名
          ????????pluginState.specified[spec.local.name]?=?spec.imported.name;
          ??????}?else?{
          ????????//?ImportDefaultSpecifier?和?ImportNamespaceSpecifier
          ????????pluginState.libraryObjs[spec.local.name]?=?true;
          ??????}
          ????});
          ????pluginState.pathsToRemove.push(path);
          ??}
          }

          待?babel?遍歷了所有的?ImportDeclaration?類型的節(jié)點(diǎn)之后,就收集好了依賴關(guān)系,下一步就是如何加載它們了。

          第二步 判斷是否使用

          收集了依賴關(guān)系之后,得要判斷一下這些?import?的變量是否被使用到了,我們這里說(shuō)一種情況。

          我們知道,JSX?最終是變成?React.createElement()?執(zhí)行的:

          ReactDOM.render(<Button>HelloButton>);

          ??????↓?↓?↓?↓?↓?↓

          React.createElement(Button,?null,?"Hello");

          沒(méi)錯(cuò),createElement?的第一個(gè)參數(shù)就是我們要找的東西,我們需要判斷收集的依賴中是否有被?createElement?使用。

          分析一下這行代碼的?ast,很容易就找到這個(gè)節(jié)點(diǎn):

          來(lái)看代碼:

          CallExpression(path,?state)?{
          ??const?{?node?}?=?path;
          ??const?file?=?(path?&&?path.hub?&&?path.hub.file)?||?(state?&&?state.file);
          ??//?方法調(diào)用者的?name
          ??const?{?name?}?=?node.callee;
          ??//?babel-type?工具函數(shù)
          ??const?{?types?}?=?this;
          ??//?內(nèi)部狀態(tài)
          ??const?pluginState?=?this.getPluginState(state);

          ??//?如果方法調(diào)用者是?Identifier?類型
          ??if?(types.isIdentifier(node.callee))?{
          ????if?(pluginState.specified[name])?{
          ??????node.callee?=?this.importMethod(pluginState.specified[name],?file,?pluginState);
          ????}
          ??}

          ??//?遍歷?arguments?找我們要的?specifier
          ??node.arguments?=?node.arguments.map(arg?=>?{
          ????const?{?name:?argName?}?=?arg;
          ????if?(
          ??????pluginState.specified[argName]?&&
          ??????path.scope.hasBinding(argName)?&&
          ??????path.scope.getBinding(argName).path.type?===?'ImportSpecifier'
          ????)?{
          ??????//?找到?specifier,調(diào)用?importMethod?方法
          ??????return?this.importMethod(pluginState.specified[argName],?file,?pluginState);
          ????}
          ????return?arg;
          ??});
          }

          除了?React.createElement(Button)?之外,還有?const btn = Button?/?[Button]?... 等多種情況會(huì)使用?Button,源碼中都有對(duì)應(yīng)的處理方法,感興趣的可以自己看一下:?https://github.com/ant-design/babel-plugin-import/blob/master/src/Plugin.js#L163-L272[3]?,這里就不多說(shuō)了。

          第三步 生成引入代碼(核心)

          第一步和第二步主要的工作是找到需要被插件處理的依賴關(guān)系,比如:

          import?{?Button,?Rate?}?from?'antd';
          ReactDOM.render(<Button>HelloButton>);

          Button?組件使用到了,Rate?在代碼里未使用。所以插件要做的也只是自動(dòng)引入?Button?的代碼和樣式即可。

          我們先回顧一下,當(dāng)我們?import?一個(gè)組件的時(shí)候,希望它能夠:

          import?{?Button?}?from?'antd';

          ??????↓?↓?↓?↓?↓?↓

          var?_button?=?require('antd/lib/button');
          require('antd/lib/button/style');

          并且再回想一下插件的配置?options[4],只需要將?libraryDirectory?以及?style?等配置用上就完事了。

          小朋友,你是否有幾個(gè)問(wèn)號(hào)?這里該如何讓?babel?去修改代碼并且生成一個(gè)新的?import?以及一個(gè)樣式的?import?呢,不慌,看看代碼就知道了:

          import?{?addSideEffect,?addDefault,?addNamed?}?from?'@babel/helper-module-imports';

          importMethod(methodName,?file,?pluginState)?{
          ??if?(!pluginState.selectedMethods[methodName])?{
          ????// libraryDirectory:目錄,默認(rèn) lib
          ????// style:是否引入樣式
          ????const?{?style,?libraryDirectory?}?=?this;

          ????//?組件名轉(zhuǎn)換規(guī)則
          ????//?優(yōu)先級(jí)最高的是配了 camel2UnderlineComponentName:是否使用下劃線作為連接符
          ????//?camel2DashComponentName?為?true,會(huì)轉(zhuǎn)換成小寫字母,并且使用?-?作為連接符
          ????const?transformedMethodName?=?this.camel2UnderlineComponentName
          ????????transCamel(methodName,?'_')
          ??????:?this.camel2DashComponentName
          ????????transCamel(methodName,?'-')
          ??????:?methodName;
          ????//?兼容?windows?路徑
          ????//?path.join('antd/lib/button')?==?'antd/lib/button'
          ????const?path?=?winPath(
          ??????this.customName
          ??????????this.customName(transformedMethodName,?file)
          ????????:?join(this.libraryName,?libraryDirectory,?transformedMethodName,?this.fileName),
          ????);
          ????//?根據(jù)是否有導(dǎo)出?default?來(lái)判斷使用哪種方法來(lái)生成?import?語(yǔ)句,默認(rèn)為?true
          ????//?addDefault(path,?'antd/lib/button',?{?nameHint:?'button'?})
          ????//?addNamed(path,?'button',?'antd/lib/button')
          ????pluginState.selectedMethods[methodName]?=?this.transformToDefaultImport
          ????????addDefault(file.path,?path,?{?nameHint:?methodName?})
          ??????:?addNamed(file.path,?methodName,?path);
          ????//?根據(jù)不同配置?import?樣式
          ????if?(this.customStyleName)?{
          ??????const?stylePath?=?winPath(this.customStyleName(transformedMethodName));
          ??????addSideEffect(file.path,?`${stylePath}`);
          ????}?else?if?(this.styleLibraryDirectory)?{
          ??????const?stylePath?=?winPath(
          ????????join(this.libraryName,?this.styleLibraryDirectory,?transformedMethodName,?this.fileName),
          ??????);
          ??????addSideEffect(file.path,?`${stylePath}`);
          ????}?else?if?(style?===?true)?{
          ??????addSideEffect(file.path,?`${path}/style`);
          ????}?else?if?(style?===?'css')?{
          ??????addSideEffect(file.path,?`${path}/style/css`);
          ????}?else?if?(typeof?style?===?'function')?{
          ??????const?stylePath?=?style(path,?file);
          ??????if?(stylePath)?{
          ????????addSideEffect(file.path,?stylePath);
          ??????}
          ????}
          ??}
          ??return?{?...pluginState.selectedMethods[methodName]?};
          }

          addSideEffect,?addDefault?和?addNamed?是?@babel/helper-module-imports?的三個(gè)方法,作用都是創(chuàng)建一個(gè)?import?方法,具體表現(xiàn)是:

          addSideEffect

          addSideEffect(path,?'source');

          ??????↓?↓?↓?↓?↓?↓

          import?"source"

          addDefault

          addDefault(path,?'source',?{?nameHint:?"hintedName"?})

          ??????↓?↓?↓?↓?↓?↓

          import?hintedName?from?"source"

          addNamed

          addNamed(path,?'named',?'source',?{?nameHint:?"hintedName"?});

          ??????↓?↓?↓?↓?↓?↓

          import?{?named?as?_hintedName?}?from?"source"

          更多關(guān)于?@babel/helper-module-imports?見(jiàn):@babel/helper-module-imports[5]

          總結(jié)

          一起數(shù)個(gè) 1 2 3,babel-plugin-import?要做的事情也就做完了。

          我們來(lái)總結(jié)一下,babel-plugin-import?和普遍的?babel?插件一樣,會(huì)遍歷代碼的?ast,然后在?ast上做了一些事情:

          1. 收集依賴:找到?importDeclaration,分析出包?a?和依賴?b,c,d....,假如?a?和?libraryName一致,就將?b,c,d...?在內(nèi)部收集起來(lái)
          2. 判斷是否使用:在多種情況下(比如文中提到的?CallExpression)判斷 收集到的?b,c,d...?是否在代碼中被使用,如果有使用的,就調(diào)用?importMethod?生成新的?impport?語(yǔ)句
          3. 生成引入代碼:根據(jù)配置項(xiàng)生成代碼和樣式的?import?語(yǔ)句

          不過(guò)有一些細(xì)節(jié)這里就沒(méi)提到,比如如何刪除舊的?import?等... 感興趣的可以自行閱讀源碼哦。

          看完一遍源碼,是不是有發(fā)現(xiàn),其實(shí)除了?antd?和?element?等大型組件庫(kù)之外,任意的組件庫(kù)都可以使用?babel-plugin-import?來(lái)實(shí)現(xiàn)按需加載和自動(dòng)加載樣式。

          沒(méi)錯(cuò),比如我們常用的?lodash,也可以使用?babel-plugin-import?來(lái)加載它的各種方法,可以動(dòng)手試一下。

          動(dòng)手實(shí)現(xiàn) babel-plugin-import

          看了這么多,自己動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的?babel-plugin-import?吧。

          如果還不了解如何實(shí)現(xiàn)一個(gè)?Babel?插件,可以閱讀?【Babel 插件入門】如何用 Babel 為代碼自動(dòng)引入依賴

          最簡(jiǎn)功能實(shí)現(xiàn)

          按照上文說(shuō)的,最重要的配置項(xiàng)就是三個(gè):

          {
          ??"libraryName":?"antd",
          ??"libraryDirectory":?"lib",
          ??"style":?true,
          }

          所以我們也就只實(shí)現(xiàn)這三個(gè)配置項(xiàng)。

          并且,上文提到,真實(shí)情況中會(huì)有多種方式來(lái)調(diào)用一個(gè)組件,這里我們也不處理這些復(fù)雜情況,只實(shí)現(xiàn)最常見(jiàn)的?


          瀏覽 58
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99成人看的视频 | 无需播放器的AV | 99久久久无码国产精精品品不卡 | 亚洲操逼网站豆花 | 中文字幕日产av 中文字幕在线资源 |