为了利用可能快速访问, 须要对构建代码进行"减肥", 将无用代码剔除掉.以后得支流构建框架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我的项目时, 组件注册有全局注册和部分注册之分. 全局注册后的组件在每一个其余组件中都能够应用, 无需再次导入注册, 具备良好的开发体验. 但也会导致首屏包过大, 升高用户体验.

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