一、问题引出
我们知道 React
中一切皆组件,公共组件一般都会在 src
目录下新建 components
目录来存放。那么问题来了:如果组件拆分得细而多,那么文件数量自然而然就多了起来,我们在使用时可能会看见如下场景:
一大堆的文件是不是看着头就大!试想我们可不可以像 antd
组件一样:
import {
ReimburseDetail,
ContactUs,
HoverTips,
CustomModal,
ReimburseStatus
} from 'src-components'
这么使用呢?哎,还真有办法!
二、解决问题
方案一
- 在
components
目录下新建一个index.js
,引入目录下所有文件,然后导出。/** * './module' 要读取的目录 * true 是否读取子目录 * /\.js$/ 匹配后缀为'.js'的文件 */ const files = require.context('./module', true, /\.js$/) const modules = files.keys().reduce((modules, path) => { // './app.js' => 'app' const name = path.replace(/^\.\/|.js$/g, '') modules[name] = files(path).default return modules }, {}) export default modules
- 试试效果:可以满足要求,但打包体积会变大,未使用的组件也参与了打包。
方案二
-
我们试试手动实现一个
babel-plugin
。 -
思路:我们知道引入单一文件时不存在所谓的按需加载,当一个文件暴露的出口文件多时,那么这个文件就不是单一文件了,一旦文件被引用,该文件暴露的文件就全部会引入并参与打包,这显然不是我们需要的。那么我们是不是可以做点什么呢?假如我引入一个不存在的包,然后导出想要的组件:
- 显然不可能会导入所有的包
- 显然不可能会生效,编译会报错
基于这个思路,我们可以手动修改它默认的编译规则,以达到我们想要的目的。我们需要拦截所有使用了我们自定义的包名文件,对传入的组件名提取出来,修改编译规则,让其单独引入传入的包名的组件参与单独引入打包,就可以达到我们想要的目的。
-
来吧,上手干。参照 链接这篇文章 查看
babel-plugin
api
就开搞:// .babelrc { "plugins": [ [ "./src/utils/my-plugin-import", { "libraryName": "src-components", "alias": "@/components" } ], ... ] }
// src/utils/my-plugin-import.js const toLine = name => { const str = name.replace(/([A-Z])/g, "-$1").toLowerCase() return str.split('-')[0] ? str : str.slice(1) } module.exports = function ({ types: t }) { return { visitor: { ImportDeclaration(path, source) { const { opts: { libraryName, alias } } = source if (!t.isStringLiteral(path.node.source, { value: libraryName })) { return } const newImports = path.node.specifiers.map(item => { const str = toLine(item.local.name) return t.importDeclaration( [t.importDefaultSpecifier(item.local)], t.stringLiteral(`${alias}/${str}`) ) }) path.replaceWithMultiple(newImports) } } } }
.babelrc
使用上我们的插件,babel-loader
会在编译的时候执行我们的自定义js
- 配置我们的自定义包名
src-components
(引用的时候使用) - 配置我们需要加载的组件的路径
alias
- 映射规则转换:大写驼峰 - 中划线命名方式
- 最终路径就是:
alias + name
- 恭喜你,到这里就完成了
三、上手体验
修改组件引入方式,保存 ,编译正常,无任何毛病;爽歪歪,再 build
构建试试,无完全没问题。
四、回顾与思考
这种方式确实给我们带来了极大便利,优化了大量代码,简洁易读。这样看来,我们的代码是不是都可以和 antd
写法相媲美了,美滋滋。
等等 antd
是怎么实现按需引入的呢?噢噢,原来使用了 babel-plugin-import
,让我们看看 .babelrc
:
// .babelrc
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}
]
哇咔咔,这是不是惊奇的相似?只不过别人还做了额外的功能就是导包的时候,再引入对应的css罢了。
至此,就到文末了。我们从发现问题到解决问题,从写插件再到发觉按需引入原理,收获还真不止一点点哦!