乐趣区

关于babel:按需导入之babel插件转换

为了利用可能快速访问, 须要对构建代码进行 ” 减肥 ”, 将无用代码剔除掉. 以后得支流构建框架 webpackrollup等都提供了 tree shaking 机制, 利用 es6 得申明式模块零碎语法和语句依赖剖析, 进行高精度得代码剔除. 但 tree shaking 也存在一些限度, 个别的第三方库都采纳 es5 语法, 不应用 es6 的模块语法, 导致 tree shaking 生效. 对于这些第三方库, 个别采纳一些转换导入语句的 babel 插件, 如 babel-plugin-import), babel-plugin-component), babel-plugin-transform-imports)等.

本文中的案例中很多采纳了 antd 的例子, 但其实 antd 是反对 tree shaking 的, 不须要应用这些插件也能按需导入.

导入语句转换插件

不采纳按需导入的插件, 导入一个库如loadash, 就会这样写:


import {trim, isEqual} from 'loadash';

trim(str);

isEqual(1, 2);

这会将整个 lodash 代码都给导入, 如果不像导入不须要的代码, 且以后库反对按需导入, 手动按需导入的代码应该为:


import isEqual from 'lodash/isequal';

import trim from 'lodash/trim';

trim(str);

isEqual(1, 2);

但以后模块中大量应用了这个库的模块 (函数) 时, 手动按需导入就会十分繁琐, 代码整洁度大大降低了.

如果可能将全量导入代码:


import {trim, isEqual} from 'loadash';

利用工具转换成按需导入代码:


import isEqual from 'lodash/isequal';

import trim from 'lodash/trim';

这样既能享受全量导入的简洁, 又能够不必放心导入过多的无用代码.

babel-plugin-import, babel-plugin-component, babel-plugin-transform-imports 等就是这种提供转换的工具.

babel-plugin-import是阿里为了 antd 组件库量身定做的一套转换工作, 不过在后续的更新中, 实用的范畴越来越广. 它除了会导入指标组件外, 还反对导入组件从属款式文件. 转化示意:


import {Button} from 'antd';

ReactDOM.render(<Button>xxxx</Button>);

 ↓ ↓ ↓ ↓ ↓ ↓

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

require('antd/lib/button/style/css');

ReactDOM.render(<_button>xxxx</_button>);

babel-plugin-componentelement-ui 团队对 babel-plugin-import 一个低版本的 fork, 不倡议应用, 因为可配置化基地, 根本只能对 element-ui 这个 UI 库按需导入应用.

babel-plugin-transform-imports是一个十分轻量级的转换插件, 可定制化水平十分高, 能够定制转换后的导入语句, 适应不同的目录构造. 但它只能为全量导入的每一项转换为一个独自的导入, 这不实用的对 UI 组件库按需导入. 在 babel-plugin-component 还很轻便时,babel-plugin-transform-imports是十分好用的. 它的导入示意:


import {MdCheck, FaCheck} from 'react-icons'

->

import MdCheck from 'react-icons/lib/md/check'

import FaCheck from 'react-icons/lib/fa/check'

从原理来说, 这些插件都是剖析具名导入的 import 语句, 依据导入项, 转换为按需导入语句. babel-plugin-importbabel-plugin-transform-imports 在源码实现的细节上有所不同.

babel-plugin-import 如对于代码:


import {Button} from 'element-ui';

console.log(Button);

插件解析进去所须要按需导入的模块后, 会在新的一行中增加按需导入语句 (应用babel@/helper-module-imports 库中的工具), 此时的代码会变为:


import _Button from 'element-ui/lib/button';

import 'components/lib/button/style.css';

import {Button} from 'element-ui';

console.log(Button);

此时还须要须要将所有应用变量 Button 的中央改为_Button:


import _Button from 'element-ui/lib/button';

import 'components/lib/button/style.css';

import {Button} from 'element-ui';

console.log(_Button);

而后删除原有导入:


import _Button from 'element-ui/lib/button';

import 'components/lib/button/style.css';

console.log(_Button);

为了将 Button 变量转为 _Button, 须要对可能应用变量语句转换, 在babel-plugin-import 以后最新代码中, 检测的语句 (表达式) 类型有:CallExpression, MemberExpression, Property, VariableDeclarator, ArrayExpression, LogicalExpression, ConditionalExpression, IfStatement, ExpressionStatement, ReturnStatement, ExportDefaultDeclaration, BinaryExpression, NewExpression, ClassDeclaration等 (具体能够参照源码). 这种采纳枚举可能存在的语句可能会有脱漏. 从库的变更来看, babel-plugin-component 对应的 babel-plugin-import 的版本到最新的版本, 多了几个表达式. 其实这里能够像 babel-plugin-lodash 的实现外面一样, 通过作用域查问到所有应用变量的语句, 更加精确和简洁.

babel-plugin-import 的最新的代码来看, 应用了通过作用域来解决变量重名导致的问题)

babel-plugin-transform-imports 非常简单, 通过剖析指标 import 语句, 将具名导入语句替换为多条按需导入语句, 且变量名维持跟原样.

就算最新版本的 babel-plugin-import 适用范围曾经十分广, 然而还是倡议应用 babel-plugin-transform-imports, 它的实现轻量, 简洁. 对于babel-plugin-transform-imports 不反对按需导入额定的资源, 能够 fork 源码, 进行扩大.

依据调用进行按需导入

还有一种 babel 转换导入语句的按需导入的机制, 原理来自 babel-plugin-loadash 插件, 它能够将上面的代码:


import _ from 'loadash';

_.trim(str);

_.isEqual(1, 2);

转换为: 


import isEqual from 'lodash/isequal';

import trim from 'lodash/trim';

trim(str);

isEqual(1, 2);

原理是依据全量导入的变量的调用链, 剖析所须要的模块, 而后按需导入. 这样的不便之处在于编码时应用模块的全量导入或者默认导入, 防止具名导入那样须要保护每一项. 比方利用 antd 组件库在开发表单时, 须要导入大量的表单组件, 随着业务的变更, 组件也须要变更, 每一次保护都须要从新在导入语句中导入增加须要的模块.

一开始控件中只应用了 inputbutton:


import {Button, Input, Form, FormItem} from 'antd';

后续扩大了业务时, 须要应用下拉框, 须要改写:


import {Button, Input, Form, FormItem, Select} from 'antd';

这在以后文件代码量非常大是, 每一次应用新的组件, 都须要滚动文件顶部保护好新的导入, 而后在回到开发点持续开发, 打断晦涩的开发快感. 且如果没有配置相应的 eslint 规定的话, 还会导致一些没有应用的组件仍旧被导入, 导致无用代码被加载.

而一大早应用全量导入就没有这种懊恼:


import * as Ad from 'antd';



return (<Ad.Form>

    <Ad.FormItem>

        <Ad.Input />
// ...

这里只提供这样一个的思路, 如果感觉这样能够进步开发效率, 能够参考 babel-plugin-lodash 实现相应的插件. 笔者所在的团队是这样应用的, 但带来一个苦恼是可能组件前缀过多 (Ad. 为前缀), 不简洁.

写在最初

在开发 vue 我的项目时, 组件注册有全局注册和部分注册之分. 全局注册后的组件在每一个其余组件中都能够应用, 无需再次导入注册, 具备良好的开发体验. 但也会导致首屏包过大, 升高用户体验.

能够不能够有一种形式, 让开发人员开发时像全局注册一样, 理论打包又跟部分注册一样反对按需导入呢?

退出移动版