美文网首页
模块按需引入优化

模块按需引入优化

作者: joyer_li | 来源:发表于2020-03-07 14:09 被阅读0次

本文为作者团队对按需引入的迭代史, 阅读本文可以对组件库按需引入的原理有所了解, 提供一种对项目中所有模块按需导入新思路.

团队中vue项目使用element-ui和react项目使用antd组件库, 为了避免项目打包后首屏过大, 对这些组件库进行了按需导入的配置(没有采用tree shaking).一开始使用的是两个库自带的babel插件, babel-plugin-importbabel-plugin-component.

如果你采用 ES module 的 tree shaking, 不需要阅读本文.

但随着的业务的增长, 发现这两个插件越来越不好用. 且babel-plugin-component是fork自babel-plugin-import, 相当于babel-plugin-import的低版本, 更加难用. babel-plugin-import相当于是对antd等类似组件库定制化的按需导入的插件. 如果想要对项目中的基础的模块(组件也是js模块)进行按需导入时, 就会显得有些笨重. 因为项目中基础组件的css样式文件都是直接导入到js代码中, 且不想维护一个类似antd组件库那样的目录结构(存放模块的目录下需要有lib这样的子级目录). babel-plugin-component还有一个缺点, 就是导入时不能使用别名, 在项目中大量使用babel-plugin-component进行按需导入模块时, 如混合, 工具函数等, 由于不能设置别名导致可能会出现重名的情况, 当时的临时解决方案是不同类型的模块采用不同的前缀.在使用babel-plugin-componen时, 它会时刻提醒你, 必须这样维护目录结构, 必须这样写, 不然就会报错.

结论: babel-plugin-import和babel-plugin-component适合对element-ui和antd的按需导入, 但不适合项目中所有的模块.

后面在npm搜索更好的按需导入组件, 发现babel-plugin-transform-imports, 这个插件扩展性更高, 实现的也更加优雅.这个插件支持定制化转换后的导入源, 也就是由element-uielement-ui/lib/button的规则支持自己定制, 这极大的解脱了开始笨重的目录结构, 立马用这个插件对项目中所有的基础模块(混合, 工具, 基础组件等一切可以视为js模块)进行按需导入.且这个插件支持别名, 使用起来直接无感, 非常酸爽.

此时项目采用babel-plugin-transform-importsbabel-plugin-importbabel-plugin-component并驾齐驱.

项目继续迭代, 对基础组件库element-uiantd的主题进行定制, 然后定制后的样式文件放在了一个单独npm库中, 使用babel-plugin-component和babel-plugin-import指向新的样式文件地址, babel-plugin-import可以比较优雅的解决, 大概是阿里也碰到过这种场景, 但是使用babel-plugin-component实现的代码旧比较丑陋.

接而打算放弃使用babel-plugin-import和babel-plugin-component, 只使用babel-plugin-transform-imports. 但在实际使用中发现, babel-plugin-transform-imports限制了一个导入项只能转为一个新的导入, 它只能将:


import { Button } from 'element-ui';

转为:


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

对于button.css将导入不进来.

而对于element-uiantd这样样式跟逻辑分开放的组件库, 只能全局导入样式.故打算改造babel-plugin-transform-imports, 让它能覆盖babel-plugin-import的功能.

其实这类按需导入插件的原理特别简单, 利用babel对代码进行语法分析后, 对import语句进行转换.如对:


import { Button } from 'components';

会被转为:


import Button from 'components/lib/button';

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

故只需要让babel-plugin-transform-imports支持额外的资源导入即可.

这里另外提一点, 在阅读babel-plugin-transform-imports和babel-plugin-import的源码之后, 发现它们的实现方式不一样.

babel-plugin-transform-imports实现原理是, 对目标import语句进行分析后, 根据分析结果生成新的一个import语句列表, 然后替换掉原有的import语句.

而babel-plugin-import实现原理是, 对目标import语句进行分析, 根据分析结果加入新的import语句, 但新的import语句中的变量名会跟原import语句(目标import)中的输入变量名不一样, 需要在整个模块中可能使用原import导入变量的地方都换成新的导入变量名. 最后删除目标导入语句.

比较拗口,用代码说明. 比如待转换的代码为:


import { Button } from 'element-ui';

console.log(Button);

babel-plugin-import会根据配置分析import { Button } from 'element-ui';,得到需要加载Button组件和Button的样式文件, 使用@babel/helper-module-imports的addDefaultaddSideEffect, 此时代码会变为:


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

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

import { Button } from 'element-ui';

console.log(Button);

此时出现了_ButtonButton, 而Button是需要丢弃的旧的导入变量, 为了支持import { Button } from 'element-ui';能够删除,需要插件对当前模块所有使用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';变为:


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

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

import { Button } from 'element-ui';  

console.log(_Button);

为了保证Button都转为_Button, 需要尽可能的覆盖模块代码, babel-plugin-component检测的语句(表达式)类型有:CallExpression, MemberExpression, AssignmentExpression, ArrayExpression, Property, VariableDeclarator, LogicalExpression, ConditionalExpression, IfStatement,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-import比babel-plugin-component多了几个需要检测的表达式.其实更好的方法是可以通过babel提供的file.scope.getBinding作用域来获取所有使用变量的地方, 然后修改对应使用变量的地方.

babel-plugin-import采用的方式很像babel-plugin-lodash使用的方式, file.scope.getBinding也是babel-plugin-lodash中解决修改旧变量的办法, 但是这里只是对简单的一个导入语句的转换, 个人觉得babel-plugin-transform-imports处理的更加优雅.

模块的按需导入转换到这里就已经完全完毕, 但实际使用时, 虽然按需导入转换插件能够让按需导入模块像全局导入那样写, 避免过多的import语句, 所有需要导入的模块都可以集中在一条import语句中.但是并不是最简方式, 它有如下缺点:

*. 每一次导入一个新的模块, 总需要滑到文件最顶端, 添加一个新的模块, 这在一个复杂的表单页面时, 是非常痛苦的.

*. 需要移除一个不需要的模块时, 在没有使用eslint的情况下可能忘记删除废弃模块导入, 这在多人协同的团队中改小bug时经常会出现.

为了解决这些问题, 团队参照babel-plugin-lodash, 开发了插件支持对于下面的代码:


import { Row, Col } from 'antd';

(<Row>

    <Col></Col>

</Row>)

可以这样写:


import Antd from 'antd';

(<Antd.Row>

    <Antd.Col></Antd.Col>

</Antd.Row>)

这样的好处是, 在也不需要频繁的添加新的模块和移除废弃的模块了, 一切皆可以点出来, 特别是配合typescipt的代码提示时, 再也不用担心记不住模块的名字了.

这部分内容后续会有单独文章.

对于vue项目, 由于使用jsx较少, 还是需要根据需在当前组件中局部注册组件, 不够酸爽. 团队开发了自动根据模板中的标签按需导入组件库的插件, 让vue组件的使用就像全局注册一样, 但是打包确是按需导入.

这部分内容后续会有单独文章.

对babel-plugin-transform-imports进行改造后的库, 暂时还没有开源, 等开源后会在文末贴出地址.

相关文章

  • 模块按需引入优化

    本文为作者团队对按需引入的迭代史, 阅读本文可以对组件库按需引入的原理有所了解, 提供一种对项目中所有模块按需导入...

  • 网页性能优化

    优化api 优化api 时在api / index.js 中用这种引入方法导入所有分文件api的按需导入, 在按需...

  • elementUI按需引入以后 Message组件错误

    项目完成后,进行性能优化,其中一项措施对Element-Ui采用按需引入的方式 但是发现Message模块出现错误...

  • webpack打包优化

    此篇分享为 vue-cli2.0 中 webpack 的优化配置。 1、按需引入组件 例如引入 element-u...

  • vue项目引入 Element ui 的两种方式

    手动引入 按需引入(推荐):自动(vue ui)方式 按需引入生成代码 按需引入:手动方式 1.安装 Elemen...

  • Webpack优化

    Webpack优化打包速度:按需引入压缩代码每个路由页面单独打包使用时再去下载 性能优化:v-if代替v-show...

  • vue-cli脚手架项目按需引入elementUI

    先说作者自己按需引入elementui的好处,全局引入是超过1M的,按需引入后不到400K,作者按需引入用到...

  • vue优化

    1.vue项目首屏加载优化减少组件全局引入.手动引入 ECharts 各模块使用更轻量级的工具库 2。CDN优化V...

  • vue引入element-ui

    安装 npm i element-ui -S 完整引入 在mian.js中复制如下代码 按需引入 按需引入需要借助...

  • element-ui按需引入及自定义主题

    按需引入 element-ui安装后提供两种引入方式,全局引入以及按需引入,整个element-ui大概有1M左右...

网友评论

      本文标题:模块按需引入优化

      本文链接:https://www.haomeiwen.com/subject/iplcdhtx.html