我们平时在做项目优化的时候,第一反应是什么呢?
减小dist包的体积
尽量删除非必要引用的node_modules
Ui库按需引入
等等
我们今天就主要聊一聊按需引入问题。
我们平时是如何引入UI库的呢?是这样暴力引入?
Import h_ui from 'h_ui'
Vue.use(h_ui)
还是这样按需引入?
Import { Button } from 'h_ui'
Vue.use(Button)
我相信大部分人都是使用的第二种,那如果只是按照上面这样配置,能做到真正的按需引入吗?我们今天就来实现 从编译组件到按需引入的整个流程。
全量包
-
组件打包
我们准备三个组件ComponentA、ComponentB、ComponentC。下面我们来编写webpack
// 入口文件 index.js
import ComponentA from "./ComponentA.vue"
import ComponentB from "./ComponentB.vue"
import ComponentC from "./ComponentC.vue"
const components = {
ComponentA,
ComponentB,
ComponentC
}
const install = function(Vue, opts = {}) {
if (install.installed) return;
Object.keys(components).forEach(key => {
Vue.component(key, components[key]);
});
};
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
const API = {
install
};
export default API
// webpack.config.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
entry: {
'dist/index.js': path.resolve(__dirname + "/index.js")
}, //入口文件
output: {
path: `${__dirname}/../lib`, //当前目录
libraryTarget: "umd",
filename: "[name].js"
},
module: {
rules: [
{
test: /.vue$/,
use: "vue-loader"
},
{
test: /.js$/,
use: "babel-loader"
},
{
test: /.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
}
]
},
resolve: {
extensions: [".vue", ".js", ".less"]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: "dist/style.css",
})
]
};
相信大家对webpack已经非常熟了,就不多讲了,通过如上配置,我们就可以得到组件包:lib/dist/index.js
因为我们的组件包并没有放到node_modules中,所以我们在开发环境配置一下alias:
chainWebpack: config => {
config.resolve.alias
.set("hundsun-ui", path.resolve("src/lib/dist/index.js"))
}
然后我们在main.js,再进行如下配置:
import hundsunUI from 'hundsun-ui'
Vue.use(hundsunUI)
好了,到这里一个全量的组件包已经搞定了,我们在页面直接使用组件就可以了。如下:
-
webpack-bundle-analyzer
我们如何查看webpack编译之后的资源加载情况呢? 这里我们用到了webpack的插件:webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
configureWebpack: {
plugins: [
new BundleAnalyzerPlugin({
analyzerPort: 9999
})
]
}
我们在vue.config.js中进行如上配置,就可以清楚的看到资源加载情况。
-
分包加载
如果我们不想全量包加载,想单独引用,该如何配置呢?
// 修改 入口文件 index.js(只需要分别导出组件即可)
// 省略代码
const API = {
install
};
export {
ComponentA,
ComponentB,
ComponentC
}
export default API
这里只需要分别导出组件就可以了,我们来修改一下main.js
import { ComponentA, ComponentB, ComponentC } from 'hundsun-ui'
Vue.component(ComponentA.name, ComponentA)
Vue.component(ComponentB.name, ComponentB)
Vue.component(ComponentC.name, ComponentC)
但是这样是真正的按需引入吗?我们来看下analyzer
我们可以看到,引用的还是dist/index.js,并不会因为使用组件的数量而发生变化。
按需引入
-
按组件打包
既然要按需引入,那加载的时候就不能引用index.js, 应该使用哪个组件就加载哪个组件,我们来修改一下webpack
// 分别添加 ComponentA.js、Component B.js、ComponentC.js 三个文件,内容如下:
import ComponentA from "./ComponentA.vue"
export default ComponentA
// webpack.config.js
entry: {
'ComponentA': path.resolve(__dirname + "/ComponentA.js"),
'ComponentB': path.resolve(__dirname + "/ComponentB.js"),
'ComponentC': path.resolve(__dirname + "/ComponentC.js")
},
output: {
path: `${__dirname}/../lib/components`,
libraryTarget: "umd",
filename: "[name]/index.js"
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: "[name]/style.css",
})
]
};
这样我们就可以得到这样的组件包,那如何使用呢?
两种方案:
1. 直接引用组件
// vue.config.js
config.resolve.alias
.set("hundsun-ui", path.resolve("src/lib/components"))
// main.js
import ComponentA from 'hundsun-ui/ComponentA'
Vue.component(ComponentA.name, ComponentA)
2. 解构引用组件
// vue.config.js
config.resolve.alias
.set("hundsun-ui", path.resolve("src/lib/components"))
// lib/components 目录下创建index.js
import CompnentA from './ComponentA'
export {
CompnentA
}
// main.js
import { ComponentA } from 'hundsun-ui'
Vue.component(ComponentA.name, ComponentA)
以上两种方式均可以做到组件级别引用,但是我们应该更倾向于第二种。
但是第二种真的是按需引入吗? 我们来看一下analyzer
我们可以看到并没有如我们所想,它把A、B、C三个组件都加载了。😭😭
那么问题出在哪里呢?就是这个 components/index.js 这个文件,这个js文件把所有的组件都引用进来了。那么我们该怎么做呢?
-
babel-plugin-import
这里我们就不得不用到babel插件了。目前antd、iview也都用到该插件来实现按需加载功能。他是怎么做到的呢???
import { ComponentA } from 'hundsun-ui'
// babel-plugin-import 解析为:
require('hundsun-ui/components/ComponentA')
require('hundsun-ui/components/ComponentA/style.css')
哇,这不就是我们想要的结果吗??我们看一下如何配置:
// 创建babel.config.js
module.exports = {
"plugins": [
[
"import",
{
"libraryName": "hundsun-ui",
"libraryDirectory": "/components",
"camel2DashComponentName": false,
"style": (name) => {
return `${name}/style.css`;
}
},
"hundsun-ui"
]
]
}
// libraryName 组件库的名称
// libraryDirectory 组件库的目录
// camel2DashComponentName 禁止驼峰转译破折号
// style 返回访问路径
我们再来看一下analyzer:
大功告成!!!
-
组件间相互引用
到上一步真的大功告成了吗?现在我们来稍微修改一下组件,我们在A、B组件内引用组件C,再编译出来看一下:
我们发现A、B 两个组件的体积从2KB 变成了 3KB,好奇打开ComponentA.js,竟然发现ComponentC组件竟然在js里面,what?? 不是应该引用过来吗? 为什么给打到包里面去了??
我们发现如果组件间的相互引用使用相对路径的话,会当成该组件的一部分,并打入到该js中。那我们还需要修改两个地方
// webpack.config.js
module.export = {
resolve: {
alias: {
ComponentC: path.resolve(__dirname, "src/components/ComponentC.vue")
}
},
externals: {
ComponentC: "hundsun-ui/components/ComponentC"
}
}
// ComponentA 组件
import ComponentC from "ComponentC"
externals:防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖。
我们在 externals 中配置 ComponentC 的外部引用地址,这样编译后我们发现ComponentA.js 中就有了require("hundsun-ui/components/ComponentC")。
我们再看下analyzer:
我们可以看到,只加载了A组件与C组件,B组件并没有加载。
我们再来看一下npm run build 之后,B组件是不是在dist包中:
发现组件B并没有在dist中,喜极而泣😊😊
文章到这里也就结束了,希望对开发组件的同学有所帮助,加油打工人💪💪