likes
comments
collection
share

手把手教你写一个Vue3按需加载组件库但是乞丐版

作者站长头像
站长
· 阅读数 20

好久没写文章了,最近在研究一些组件库的实现方法,分享一下。在这我这篇文章之前其实已经有一篇文章讲了Vue如何打包组件库了(最底部),但是这篇文章一是没有源码二是Vue3和Vue2的组件库写法有点不一样,关于这个我研究了ElementPlus源码一下午,终于找到了正确的使用方法,所以就有了这篇文章。在翻看Element-plus源码的时候发现Element-plus的组件库基本架构与我们本质是一样的,所以本文可以放心食用。

关于使用组件库方式

随便打开一个Vue3的UI组件库,我们可以看到通常有两种引入方法。

  • 第一种:通过script的方式引入
  • 第二种:install后通过import的方法引入

无论第一种还是第二种,引入之后我们通常会这样

const app = createApp(App)
app.use(MyComponents)
app.mount('#app')

对于第一种script标签引入方式,Vue2和Vue3有着不同的引入方法,下面会详细讲到。 之后我们便可以使用MyComponents里面的所有组件了。在这个app.use里面到底发生了什么,为什么会这样?让我们深入兔子洞一探究竟。(源码在文章最下方)

随便写两个组件

先搭建一个Vue项目,搭建方式可以用cli也可以用vite,我自己简单的通过webpack搭建了一个,这里不再赘述搭建过程。

下面给出两个Vue组件,之后我们将会做出一个包含这两个组件的丐版组件库

手把手教你写一个Vue3按需加载组件库但是乞丐版

封装、构建

文章开头有讲到,组件的使用方式为app.use(我们的组件)所以我们打开官方文档看一下app.use的定义。cn.vuejs.org/api/applica…

手把手教你写一个Vue3按需加载组件库但是乞丐版 它需要一个install方法或者带有install方法的对象,之后会调用这个install方法,再思考一下当我们全局注册组件的时候是怎么做的。没错!就是Vue.component(WeComponent)。所以我们只需要将组件库所有组件都通过这样的形式注册一遍就可以了。

下面给出代码实现

// src/index.js
import Hello from './component/Hello/Hello.vue';
import Hi from './component/Hi/Hi.vue';

const components = [
    Hello,
    Hi,
];
const componentName = [
  'Hello',
  'Hi',
];
const install = function (Vue) {
  components.forEach((component, index) => {
    Vue.component(componentName[index], component);
  });
};


export default {
  install,
};

if (typeof window !== 'undefined' && window.Vue) {
    install(window.Vue);
  }

代码层面的功夫我们就做好了,现在我们需要修改一下webpack配置

  output: {
    path: path.join(__dirname, '/lib'),
    filename: () => '[name].js',
    libraryTarget: 'umd', // 用到的模块定义规范
  },
  externals: {
    vue: {
      root: 'Vue', // 通过 script 标签引入,此时全局变量中可以访问的是 Vue
      commonjs: 'vue', // 可以将vue作为一个 CommonJS 模块访问
      commonjs2: 'vue', // 和上面的类似,但导出的是 module.exports.default
      amd: 'vue', // 类似于 commonjs,但使用 AMD 模块系统
    },
  },

文章最底下的链接中文章有说明libraryTargetexternals的作用,这里不再赘述。 之后将构建工具的入口设置为index.js构建。构建完成后我们将package.json的入口设置为我们的构建结果的入口文件,例如我的构建结果为lib/main.js。这样,一个超低配的组件库就封装好了,接下来我们测试一下。

本地测试

首先我们通过script脚本插入这一最简单的测试方法来进行测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue3组件库</title>
</head>
<body>
    <div id="app">
        script引入!
        <Hello></Hello>
        <Hi></Hi>
    </div>
</body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="./lib/index.js"></script>
<script>
    const App = {
        data() {},
      };
    const app = Vue.createApp(App);
    app.use(VueComponents);
    app.mount("#app");
</script>
</html>

渲染结果:

手把手教你写一个Vue3按需加载组件库但是乞丐版

下面通过npm link和import的方式来测试组件库

npm link

首先在我们的组件库根目录运行

npm link

之后,我们随便通过任意脚手架搭建一个vue项目,在它的根目录运行

npm link test_library

这样我们就可以像使用普通组件库一样使用它了。

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import Hello from 'test_library'
const app = createApp(App)
app.use(Hello)
app.mount('#app')
// app.vue
<template>
  <div>
    这是我们的测试代码哦
    <Hello></Hello>
    <Hi></Hi>
  </div>
</template>

手把手教你写一个Vue3按需加载组件库但是乞丐版

细心的同学会发现,我们明明没有引入Hi组件,但却能使用这是为什么呢。其实import Hello from 'test_library'这个语句就相当于将lib/index.jsexport出来的对象进行了引入拿到的就是带有install的对象,这个install会在app.use()被调用,也就将两个组件都注册了。那么当我们不想要引入全部组件的时候该怎么做呢?答案是按需引入。

按需引入

所谓按需引入,就是要多少引入多少,例如这样import { Hi } from 'test_library'但是在没处理之前这样引用明显是不行的,毕竟我们的test_library本质上引入的是带有install的对象,所以根本没法拆分,对吧?

那该怎么办呢,答案是将入口变为多个,然后每个组件打包出自己专属的入口文件和css文件,这就开始做!

首先我们还是需要通过App.use()的方法来使用组件,这一点永远不会变,所以在每一个组件的组件文件夹下面写出他们自己的入口文件。

// src/component/Hello/index.js
import Hello from './Hello.vue';
Hello.install = function (Vue) {
  Vue.component('Hello', Hello);
};
export default Hello;

// src/component/Hi/index.js
import Hi from './Hi.vue';
Hi.install = function (Vue) {
  Vue.component('Hi', Hi);
};
export default Hi;

最后通过webpack的多入口打包功能,就可以做到分开打包了。

entry: {
    hello: './src/component/Hello/index.js',
    hi: './src/component/Hi/index.js',
    index: './src/index.js',
  },
  output: {
    path: path.join(__dirname, '/lib'),
    filename: () => '[name].js',
    libraryTarget: 'umd', // 用到的模块定义规范
  },

webpack的配置到这里还没算完,既然我们的vue组件进行了拆分,那每一个组件的css代码也要进行拆分才行,所以我们需要用到mini-css-extract-plugin这个插件。

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  plugins: [
    new VueLoaderPlugin(),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'lib-style/[name].css',
    }),
  ],

最后的产物目录如下

手把手教你写一个Vue3按需加载组件库但是乞丐版

接下来我们测试一下

import { createApp } from 'vue'
import App from './App.vue'
import Hi from 'test_library/lib/hi'
import 'test_library/lib/lib-style/hi.css'
const app = createApp(App)
app.use(Hi)
app.mount('#app')

效果: 手把手教你写一个Vue3按需加载组件库但是乞丐版

通过babel插件优化按需引入

上面的代码我们都实现的很好了,但是似乎有些不对劲,我们平时使用组件库的时候好像是这样的import { 组件a } from '组件库'。 但是也许有同学忘了,在你按需引入组件库的时候你通常还需要装一个babel插件,例如Element-plus里面会让你这样(element-plus.gitee.io/en-US/guide… ),这个插件做的事情就是将 import { 组件a } from '组件库转变成我们上面的引入方式。

现在让我们来使用一下,在我们用来测试组件库组件的项目中的babel.config.js文件加入这些代码

module.exports = {
···
  "plugins": [
    [
      "component",
      {
        "libraryName": "test_library",
        "styleLibrary": {
          "name": "lib-style", // same with styleLibraryName
          "base": false  // if theme package has a base.css
        }
      }
    ]
  ]
···
}

更改引用方式

import { createApp } from 'vue'
import App from './App.vue'
import { Hi } from 'test_library'
const app = createApp(App)
app.use(Hi)
app.mount('#app')

查看最终效果 手把手教你写一个Vue3按需加载组件库但是乞丐版 完美收官。

Vue2与Vue3到底有什么不同

我们都知道,在Vue2中我们创建vue实例的方法是new Vue(),而注册全局组件的方法是Vue.component(),所以由于这个特性我们只需要去简单的注册组件,那无论new多少个vue实例也是会有我们的注册组件的。所以在vue2组件库中如果我们想要在script中注册组件我们只需要,Vue.use(install)(这个install方法就是上面的install方法)。

但是在Vue3中,上面的办法行不通了,因为在Vue3中Vue.component()这个方法被移除了,取而代之的是createApp().component()这就导致了我们无法真正的全局注册并且更坏的情况是我们似乎没办法拿到这个create出来的vue实例。所以我们只能另辟蹊径通过文章中的方式来进行注册。具体Element-plus到底有没有写的和我一样,这个我无从得知代码阅读能力有限。但是可以肯定的是,当通过script引用他们的组件库时,window上却确实有ElementPlus这个对象。

手把手教你写一个Vue3按需加载组件库但是乞丐版 下面给出这两个注册方式的对比

// Vue2
import Hello from './component/Hello/Hello.vue';
import Hi from './component/Hi/Hi.vue';

const components = [
    Hello,
    Hi,
];
const componentName = [
  'Hello',
  'Hi',
];
const install = function (Vue) {
  components.forEach((component, index) => {
    Vue.component(componentName[index], component);
  });
};

const VueComponents = { install }
if (typeof window !== 'undefined' && window.Vue) {
  Vue.use(install)
}
export default VueComponents
// Vue2在script中的使用,可以不用use,直接使用(因为已经注册)

// Vue3
import Hello from './component/Hello/Hello.vue';
import Hi from './component/Hi/Hi.vue';

const components = [
    Hello,
    Hi,
];
const componentName = [
  'Hello',
  'Hi',
];
const install = function (Vue) {
  components.forEach((component, index) => {
    Vue.component(componentName[index], component);
  });
};

const VueComponents = { install }

if (typeof window !== 'undefined') {
    window.VueComponents = VueComponents
}
export default VueComponents
// Vue3在script中的使用,需要use
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="./lib/index.js"></script>
<script>
    const App = {
        data() {},
      };
    const app = Vue.createApp(App);
    app.use(VueComponents);
    app.mount("#app");
</script>

源码

github.com/spicylemonh…

参考资料

吐槽一下,我们学校国庆不放假。。。。我无语。。。。

转载自:https://juejin.cn/post/7144735261161685023
评论
请登录