vite2 + vue3 开发组件库

使用vite2 + vue3开发一个组件库

完成后的效果图展示:

 

 在线体验

在线体验2

GitHub 地址

 

一、搭建一个 Vite 项目

参照vite官网

通过附加的命令行选项直接指定项目名称和你想要使用的模板。例如,要构建一个 Vite + Vue 项目

# yarn
yarn create vite my-baseui --template vue

  

接下来执行

cd my-baseui
yarn 
yarn dev

 

运行后打开页面长这样子:

 在根目录下创建packages文件夹,目录结构如下:

 描述:

  • packages/components: 存放组件。
  • packages/styles: 存放全局样式和组件样式。
  • packages/index.js

二、组件编写

一、components中,编写一个测试的button组件

二、components中的 button组件index.vue代码

<template>
  <button
    class="m-button"
    :class="styleClass"
    :disabled="disabled"
    :round="round">
    <slot></slot>
  </button>
</template>
 
<script>
import { computed } from 'vue';
export default {
  name: 'm-button',
  props: {
    type: {
      type: String,
      default: 'default',
      validator(value) {
        return ['default','primary', 'success', 'info', 'warning', 'danger','text'].indexOf(value) > -1;
      }
    },
    disabled: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    }
  },
  setup(props) {
 
    const styleClass = computed(() => {
      return {
        [`m-button--${props.type}`]: props.type,
        'is-disabled': props.disabled,
        'is-round': props.round
      }
    })
 
    return {
      styleClass
    }
  }
}
</script>
 
<style lang="scss" scoped>
@import '../../styles/components/button.scss';
</style>

  

三、styles中的button样式代码

.m-button {
  display: inline-block;
  padding: 12px 20px;
  margin-right: 10px;
  border: 1px solid #d9d9d9;
  background: #fff;
  color: #333;
  cursor: pointer;
}
.is-disabled{
  color: #c0c4cc;
  cursor: not-allowed;
  background-color: #fff;
  border-color: #ebeef5;
}
.m-button--primary{
  color: #fff;
  background-color: #409eff;
  border-color: #409eff;
}
.m-button--success {
  color: #fff;
  background-color: #67c23a;
  border-color: #67c23a;
}
.m-button--info {
  color: #fff;
  background-color: #909399;
  border-color: #909399;
}
.m-button--warning {
  color: #fff;
  background-color: #e6a23c;
  border-color: #e6a23c;
}
.m-button--danger {
  color: #fff;
  background-color: #f56c6c;
  border-color: #f56c6c;
}
.m-button--text {
  border-color: transparent;
  color: #409eff;
  background: transparent;
  padding-left: 0;
  padding-right: 0;
}
// 圆角
.is-round{
  border-radius: 20px;
}

  

四、button组件中 index.js文件,用来把写的组件暴露出去

import mButton from './index.vue'
mButton.install = app => {
  app.component(mButton.name, mButton)
} 
export default mButton;

  

五、packages文件夹下新建index.js文件,用来管理所有的组件

import mButton from "./components/button/index";
import mInput from "./components/input/index";
 
// 组件列表
const components = [
  mButton,
  mInput
]
 
// 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,那么所有的组件都会被注册
const install = (Vue) => {
  // 判断是否安装
  if (install.installed) return
  // 遍历注册全局组件
  components.map(component => Vue.component(component.name, component))
}
 
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}
 
export default {
  install,
  mButton,
  mInput
}

  

三、组件引入

一、在src 的main.js中引入

import { createApp } from 'vue'
import App from './App.vue'

import myBaseui from "../packages/index";

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

  

二、在src/App.vue中使用组件

改写 src/App.vue,引入button

<template>
  <div>
    <m-button>默认按钮</m-button>
    <m-button type="primary">主要按钮</m-button>
    <m-button type="success">成功按钮</m-button>
  </div>
</template>

 浏览器上查看效果,可以看到已经成功了

 

一个组件库有很多个组件,每个组件都应该有它独立的文档。这个文档不仅有对组件各项功能的描述,更应该具有组件预览、组件代码查看等功能。同时为了良好的组件开发体验,我们希望这个文档是实时的,这边修改代码,那边就可以在文档里实时地看到最新的效果。接下来我们就来实现这么一个功能。

组件的文档一般是用 Markdown 来写。这里我们使用一个插件 vite-plugin-md

vite pulgin 将 md 文件转换成 vue 组件渲染的主要流程是:

  1. 配置 vue router 路由,指向 .md 文件
  2. 编写 vite 插件将 .md 文件解析成 vue 文件字符串
  3. 最后由 vite 的插件@vitejs/plugin-vue 将 vue 文件字符串编译成函数组件返回给前端

 

在src下,新建router/index.js文件

四、配置 vue router 路由,指向 .md 文件

import { createRouter, createWebHashHistory } from "vue-router";

const routes = [
  {
    path: '/',
    name: '组件页面',
    component: () => import('@/views/home.vue'),
    children: [
      {
        path: '/button',
        name: 'Button 按钮',
        component: () => import('/packages/components/button/doc/doc.md')
      },
      {
        path: '/input',
        name: 'Input 输入框',
        component: () => import('/packages/components/input/doc/doc.md')
      }
    ]
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;

 

main.js中引入路由

import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";

import myBaseui from "../packages/index";

const app = createApp(App);
app.use(router)
app.use(myBaseui)
app.mount('#app')

 

这里路由引入的是Markdown 文件,这个在默认的 Vite 配置里是无效的,我们需要引入 vite-plugin-md 插件来解析 Markdown 文件并把它变成 Vue 文件。

五、编写 vite 插件将 .md 文件解析成 vue 文件

vite.config.js 如下:

import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue';
import Markdown from 'vite-plugin-md';
import { resolve } from "path";

// https://vitejs.dev/config/
export default defineConfig({
  base: '/my-baseui/',
  plugins: [
    Vue({
      include: [/\.vue$/, /\.md$/],
    }),
    Markdown(),
  ],
  resolve: {
    alias: {
      "@": resolve(__dirname, "src"),
      '~': resolve(__dirname, "packages")
    }
  },
})

这样配置以后,任意的 Markdown 文件都能像一个 Vue 文件一样被使用了

 

接下来,编写home.vue页面,增加侧边栏和主区域

<template>
  <div class="main-container">
    <div class="sidebar">
      <ul v-for="item in routes" :key="item">
        <li 
          v-for="(ele,index) in item.children" 
          :key="ele"
          :class="{'active': mIndex == index}"
          @click="goPath(ele,index)">
          {{ele.name}}
        </li>
      </ul>
    </div>
    <main class="app-main">
      <router-view></router-view>
    </main>
  </div>
</template>

<script setup>
import { computed, ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';

const router = useRouter()
const mIndex = ref(sessionStorage.getItem("mIndex") || '0');

const routes = computed(() => router.options.routes)

const goPath = (ele,index) => {
  mIndex.value = index
  router.push({
    path:ele.path
  })
  sessionStorage.setItem("mIndex", index);
}

</script>

<style lang="scss" scoped>
.main-container{
  display: flex;
  justify-content: space-between;
  overflow: hidden;
  .sidebar{
    width: 200px;
    height: 100%;
    border-right: 1px solid #eee;
    text-align: center;
    ul{
      li{
        height: 50px;
        line-height: 50px;
        cursor: pointer;
      }
      .active{
        color: #409eff;
        background-color: #ECF5FF;
        border-right: 1px solid #409eff;
      }
    }
  }
  .app-main{
    flex: 1;
    padding: 20px 50px;
    overflow-y: auto;
  }
}
</style>

 

把App.vue改下

<template>
  <router-view></router-view>
</template>

<script setup>

</script>

<style>

</style>

  

往 /packages/components/button/doc/doc.md 里面随便写点东西,

完成后,浏览器查看有效果了。

vite-plugin-md 是支持在 Markdown 里面写 setup 函数的!因此我们可以把需要执行 JS 逻辑的代码封装成一个组件,然后在 Markdown 里通过 setup 来引入。

在packages/components/button/doc 目录下新建一个 demo.vue

<template>
  <div>
    <m-button>默认按钮</m-button>
    <m-button type="primary">主要按钮</m-button>
    <m-button type="success">成功按钮</m-button>
    <m-button type="danger">危险按钮</m-button>
  </div>
</template>

  

然后在md文件中引入

<script setup>
import demo from './demo.vue';
</script>

# Button 按钮
<demo/>

  

完成后,查看页面显示:

六、代码预览功能

接下来我们来实现代码预览功能,虽然说代码预览也很简单,可以直接在 Markdown 中贴代码,但是代码又写一遍就显得很繁琐了

有没有方法可以直接把demo.vue中的代码展示出来呢?

答案是可以的。

在 Vite 的开发文档里有记载到,它支持在资源的末尾加上一个后缀来控制所引入资源的类型。比如可以通过 import xx from 'xx?raw' 以字符串形式引入 xx 文件。基于这个能力,我们可以编写一个 <Preview /> 组件来获取所需要展示的文件源码。

一、新建一个 Preview.vue 文件

<template>
  <div class="pre-code-box">
    <transition name="slide-fade">
      <pre
        class="language-html"
        v-if="showCode"
      >
        <code class="language-html">{{ sourceCode }}</code>
      </pre>
    </transition>
    <div class="showCode" @click="showOrhideCode">
      <i :class="iconClass"></i>
      <span>{{ showCode ? "隐藏代码" : "显示代码" }}</span>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue";

const props = defineProps({
  compName: {
    type: String,
    default: "",
    require: true,
  },
  demoName: {
    type: String,
    default: "",
    require: true,
  },
});

const showCode = ref(false);
const sourceCode = ref("");

const iconClass = computed(() => {
  return [
    'iconfont',
    showCode.value ? 'icon-arrow-up-filling' : 'icon-arrow-down-filling'
  ]
})

const showOrhideCode = () => {
  showCode.value = !showCode.value;
}

const getSourceCode = async () => {
   let msg = await import(/* @vite-ignore */ `/packages/components/${props.compName}/doc/${props.demoName}.vue?raw`)
   // console.log(msg.default);
   sourceCode.value = msg.default
}

onMounted(() => {
  getSourceCode()
})

</script>

 

这里需要加 @vite-ignore 的注释是因为 Vite 基于 Rollup,在 Rollup 当中动态 import 是被要求传入确定的路径,不能是这种动态拼接的路径。此处加上该注释则会忽略 Rollup 的要求而直接支持该写法。

但是这样的写法,在开发模式下可行,build打包到线上后报错,拿不到资源,我们可以通过判断环境变量,在 build 模式下通过 fetch 请求文件的源码来绕过,我们后面再讲。

二、在md文件中引入preview

<script setup>
import demo from './demo.vue';
import preview from '@/components/preview.vue';
</script>

# Button 按钮
<demo/>

<preview compName="button" demoName="demo"/>

  

查看页面效果:

 

我们发现,样式没有高亮效果,接下来下载插件 highlight.js 来实现代码高亮

三、main.js中引入 highlight.js ,样式有很多种,我们随便找个看看效果

import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";

import myBaseui from "../packages/index";
import hljs from "highlight.js";
import "highlight.js/styles/color-brewer.css";

const app = createApp(App);
app.use(router)
app.use(myBaseui)
app.directive("highlight", function (el) {
  const blocks = el.querySelectorAll("pre code");
  blocks.forEach((block) => {
    hljs.highlightBlock(block);
  });
});
app.mount('#app')

  

preview.vue中加入 v-highlight 自定义指令

<pre
  class="language-html"
  v-if="showCode"
  v-highlight
 >
    <code class="language-html">{{ sourceCode }}</code>
 </pre>

 

调整后,页面高亮效果出来了

到这里,我们的组件库基本结构算是完成了,接下来,我们上传到github,部署静态站点。

 

七、部署站点

查看vite文档,在vite.config.js 中加入 base: '/my-baseui/',

 

执行npm run build 时,默认会打包成dist文件,我们把输出目录改成docs

vite.config.js加如下配置:

build: {
    outDir: 'docs'
},

 

 接下来,在你的项目中,创建一个 deploy.sh 脚本,包含以下内容(注意高亮的行,按需使用),运行脚本来部署站点

#!/usr/bin/env sh

# 发生错误时终止
set -e

# 构建
npm run build:docs

# 进入构建文件夹
cd docs

# 如果你要部署到自定义域名
# echo 'www.example.com' > CNAME

git init
git add -A
git commit -m 'deploy'

# 如果你要部署在 https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git main

# 如果你要部署在 https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:wangibook/my-baseui.git master:gh-pages

cd -

 

packages.json中的 scripts 修改下:

"scripts": {
    "dev": "vite",
    "build:docs": "vite build",
    "preview": "vite preview",
    "deploy": "bash deploy.sh"
  },

 

之后执行 npm run deploy 命令时,会把docs文件夹打包上传到github的 gh-pages 分支上

切换到gh-pages分支,可以看到看到内容就是我们打包的docs文件夹下的内容,

点击Settings--->Pages---->Source选择gh-pages分支然后点击链接https://wangibook.github.io/my-baseui/

 

 

 此时预览代码会报错,拿不到资源,其原因是由于 Rollup 无法进行静态分析,因此无法在构建阶段处理需要动态 import 的文件,导致会出现找不到对应资源。

我们可以通过判断环境变量,在 build 模式下通过 fetch 请求文件的源码来绕过。

把preview.vue做下修改:

const isDev = import.meta.env.MODE === 'development';

const getSourceCode = async () => {
  if(isDev) {
    let msg = await import(/* @vite-ignore */ `/packages/components/${props.compName}/doc/${props.demoName}.vue?raw`)
    // console.log(msg.default);
    sourceCode.value = msg.default
  } else {
    sourceCode.value = await fetch(`/my-baseui/components/${props.compName}/doc/${props.demoName}.vue`).then(res => res.text());
  }
}

  

打包后的文件夹里并没有components文件,我们得把packages下的components文件加入进去

每次复制粘贴这个components就会显得很麻烦,在这里可以用一个插件自动帮我们实现,下载插件 copy-dir

新建config/copyDocs.js

let copydir = require('copy-dir')

copydir.sync(
  process.cwd() + '/packages/components',
  process.cwd() + '/docs/components',
  {
    utimes: true,
    mode: true,
    cover: true
  },
  function (err) {
    if (err) throw err
    console.log('done')
  }
)

 

再次执行构建命令 npm run deploy, 等一会儿查看部署的站点,代码预览可以拿到资源了。

 

八、分开文档和库的构建逻辑

我们目前打包出来的文件是交互文档docs,为了构建一个 my-baseui 组件库并发布到 npm,可供别人下载使用,我们需要将构建的逻辑分开

 

在根目录下添加一个 /build 目录,依次写入 base.config.js,dist.config.js,docs.config.js,分别为基础配置、库配置和文档配置

 

base.config.js:

基础配置,需要确定路径别名、配置 Vue 插件和 Markdown 插件用于对应文件的解析。

import { defineConfig } from 'vite';
import Vue from '@vitejs/plugin-vue';
import Markdown from 'vite-plugin-md';
import { resolve } from "path";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    Vue({
      include: [/\.vue$/, /\.md$/],
    }),
    Markdown(),
  ],
  resolve: {
    alias: {
      "@": resolve(__dirname, "../src")
    }
  },
})

  

dist.config.js:

import baseConfig from './base.config';
import { defineConfig } from 'vite';

export default defineConfig({
  ...baseConfig,
  build: {
    outDir: 'dist',
    rollupOptions: {
      // 请确保外部化那些你的库中不需要的依赖
      external: ["vue"],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: "Vue",
        }
      },
    },
    lib: {
      entry: "./packages/index.js",
      name: "my-baseui",
    },
  }
});

  

docs.config.js:

import baseConfig from './base.config';
import { defineConfig } from 'vite';

export default defineConfig({
  ...baseConfig,
  base: '/my-baseui/',
  build: {
    outDir: 'docs',
  },
});

  

完成了上面这些构建配置以后,修改一下packages.json

"scripts": {
    "dev": "vite --host",
    "build:docs": "vite build --config ./build/docs.config.js && node ./config/copyDocs.js",
    "build:dist": "vite build --config ./build/dist.config.js",
    "preview": "vite preview",
    "deploy": "bash deploy.sh"
  },

  

 执行npm run build:docs 结果

 执行npm run build:dist 结果

 

九、上传npm

一、在package.json中添加发布的一些信息

描述:

description:描述信息

main:入口

keywords:关键词

npm发布时,要将package.json中的private属性值改为false

 

README.md中写使用说明

# my-baseui

## 安装
使用npm 或 yarn 安装

```
npm install my-baseui
yarn add my-baseui
```

## 引入 my-baseui
### 完整引入
#### 需要注意的是 css 样式文件需要单独引入。
在 main.js 中写入以下内容:
```js
import { createApp } from 'vue'
import App from './App.vue'
// 导入组件库
import myBaseui from 'my-baseui'
import 'my-baseui/dist/style.css';

const app = createApp(App)
app.use(myBaseui)
app.mount('#app')
```
<br/>

## 愉快开始

#### 至此 my-baseui 就引入完成并且可以使用了。

```html
<!-- html -->
<m-button>默认按钮</m-button>
<m-button type="primary">主要按钮</m-button>
```

<br/>

  

发布npm

执行npm login 命令,输入用户名和密码,输入密码时是看不到的

之后提示输入email,成功后你的邮箱会收到一个one-time password,填入这个一次性密码

登录之后,执行npm publish进行发布(每次进行发布的时候记得改下版本号)

发布成功后,到npm上,查看头像---->packages,就可以看到发布的包了。

 

至此,大体就算完成了,后面就是完善每个组件。

如果你感兴趣的话,请前往 GitHub 查看源码和完整文档

 github地址: https://github.com/wangibook/my-baseui

posted @ 2022-06-15 17:54  王大师  阅读(2197)  评论(1编辑  收藏  举报