搭建Vue3组件库:第二章 开发一个Vue组件

简介: 使用Vite+Vue开 发一个简单的组件:基础组件===>单文件组件===>JSX组件

1、基础组件

  • 首先安装 Vue3.0 包依赖。
pnpm i vue@"3.2.37"
  • 接着尝试编写一个简单的 Button 组件

创建src/button/index.ts目录文件

import { defineComponent, h } from "vue";

export default defineComponent({

  name: "SButton",

  // template:'<button>MyButton</button>'

  render() {

    return h("button", null, "MyButton");

  },

});
写到这里大家可能有点疑问: 为什么是使用 render 函数,而不是熟悉的 template 语法编写呢?

这是因为 Vue3.0 默认的包是不支持模板编译功能的。也就是说, template 语法现在还不能用。在 Vue3.0 中编译功能推荐在构建阶段完成,而不是放到浏览器中运行。如果希望在浏览器中的话,可以选择 ./node_modules/vue/dist/vue.global.js 这个包。

  • index.html 中添加一个容器 , 用来展示组件
<div id="app"></div>

给容器添加id属性,用于后面Vue实例挂载。

  • src/index.ts 中启动 Vue 实例
import { createApp } from "vue";

import SButton from "./button";

createApp(SButton).mount("#app");
  • 启动项目
pnpm dev
  • 浏览器访问项目地址
  VITE v3.0.7  ready in 165 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  • 页面并没有显示button组件,且控制台报错
Uncaught SyntaxError: Cannot use import statement outside a module (at index.ts:1:1)

这里报错的原因是用了es6的语法, 浏览器默认将它作为js解析会出现问题,需要将它作为模块导入,script标签默认type="text/javascript",需要改为type="module",更改后的index.html

  • 修改index.html文件,再次查看
<script src="./src/index.ts" type="module"></script>
  • 再次查看浏览器,可以看见button组件,但是控制台有以下警告
runtime-core.esm-bundler.js:4952 Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined. You are running the esm-bundler build of Vue, which expects these compile-time feature flags to be globally injected via the bundler config in order to get better tree-shaking in the production bundle.

For more details, see https://link.vuejs.org/feature-flags.

查阅资料——解决方案

  • 安装插件@vitejs/plugin-vue
pnpm i @vitejs/plugin-vue -D
  • 创建vite.config.ts 配置插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
plugins: [
  vue(), // VUE插件
],
})
  • 再次启动项目
pnpm dev
  • 再次查看浏览器控制台

组件正常显示,且控制台此时无警告报错。


2、单文件组件

由于Vue3.0 默认的包是不支持模板编译功能, 这里需要安装viteVue插件,也就是上面的解决方案步骤。

Vite默认只能支持TS代码,而 Vue的模板需要在编译阶段转换为 TypeScript代码(渲染函数)才可以运行。 Vue 插件不但提供了模板的编译,同时还支持 Vue 单文件 ( SFC) 组件的编译。
  • 安装插件
pnpm i @vitejs/plugin-vue -D
  • 创建vite.config.ts 配置插件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue(), // VUE插件
  ],
})
  • 引入到index.ts中测试
import { createApp } from "vue";
import SFCButton from "./SFCButton.vue";

createApp(SFCButton)
.mount("#app");

此时报错

找不到模块“./SFCButton.vue”或其相应的类型声明。ts(2307)

这是因为Typescript 默认是不支持 .vue 类型的模块的。可以通过添加一个模块的类型定义来解决这个问题。

  • 创建src/shims-vue.d.ts类型声明文件
declare module "*.vue" {
  import { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
  • 运行项目
pnpm dev
  • 浏览器访问地址
  VITE v3.0.7  ready in 297 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

此时可以看到按钮组件。


3、JSX 组件

JSX 是一种 Javascript 的语法扩展,最早运用于 React 架构中。JSX 也可以当作一种模板语言使用。虽然有人会质疑利用JSX语法编写 Vue3 代码是否合理, 比如怀疑 JSX 语法是否兼容 Vue3 的静态提升特性。但是现在很多基于 Vue 的组件库都大量使用 JSX 语法,对于工程化搭建,还是以开发者的使用习惯优先,我们支持了再说。

  • 在这里我们通过插件实现扩展
pnpm i @vitejs/plugin-vue-jsx@"2.0.0" -D
  • 修改 vite.config.ts 文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vue(), // VUE插件
    vueJsx({}), // JSX 插件
  ],
})
  • 创建src/JSXButton.tsx 文件
import { defineComponent, h } from "vue";

export default defineComponent({

  name: "JSXButton",
  render() {
    return <button>JSX Button</button>;
  },

});

这个时候TS会报错

(property) button: ElementAttrs<ButtonHTMLAttributes>
找不到名称“React”。ts(2304)

这个提示的意思是不支持 JSX 语法造成的。而不是需要安装 React。只需要在 tsconfig 中配置一下 jsx 语法支持就行了。

  • 在根目录下创建tsconfig.json文件
{
  "compilerOptions": {
      "declaration": true, /* 生成相关的 '.d.ts' 文件。 */
      "declarationDir": "./dist/types", /* '.d.ts' 文件输出目录 */
      "jsx": "preserve",
  },
  "include": [
      "./**/*.*",
      "./src/shims-vue.d.ts"
  ],
  "exclude": [
      "node_modules"
  ],
  "esModuleInterop": true,
  "allowSyntheticDefaultImports": "true"
}
  • 将组件引入index.ts中测试
import { createApp } from "vue";
import JSXButton from "./JSXButton";

createApp(JSXButton)
.mount("#app");
  • 运行项目
pnpm dev
  • 浏览器查看地址
 VITE v3.0.7  ready in 466 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

查看浏览器显示的按钮组件是否正常。


4、库文件封装

参考一下 Element 的使用指南。可以看到组件库有两种引入形态:

  • 完整引入 :一次性引入全部组件,使用 Vue.use Vue 插件的形式引入
  • 按需引入 :按需引入,导出单个组件,使用 Vue.component 注册
import Vue from 'vue'
import Element from 'element-ui'

// 完整引入
Vue.use(Element)

// or
import {
  Select,
  Button
  // ...

} from 'element-ui'

// 按需引入
Vue.component(Select.name, Select)
Vue.component(Button.name, Button)

综上所述,组件库的形态应该是这样的结构:

可以满足以下的要求:

  • 默认导出为Vue插件
  • 每个组件可以单独导出

首先设计一个入口,包含两个功能:

  • 导出全部组件
  • 实现一个 Vue 插件,插件中编写 install 方法,将所有组件安装到 vue 实例中.

新建目录文件:src/entry.ts

import { App } from "vue";
import MyButton from "./button";
import SFCButton from "./SFCButton.vue";
import JSXButton from "./JSXButton";

// 导出单独组件
export { MyButton, SFCButton, JSXButton };

// 编写一个插件,实现一个install方法

export default {
  install(app: App): void {
    app.component(MyButton.name, MyButton);
    app.component(SFCButton.name, SFCButton);
    app.component(JSXButton.name, JSXButton);
  },

};

默认 Vite 就是可以支持构建,使用 Vite 的 build 命令就可以打包输出。如果导出的是一个库文件的话,还需要配置【导出模块类型】并确定导出的文件名。配置如下:

修改文件 vite.config.ts

const rollupOptions = {

  external: ["vue", "vue-router"],
  output: {
    globals: {
      vue: "Vue",
    },
  },
};

export default defineConfig({

  .....  

  // 添加库模式配置

  build: {
    rollupOptions,
    minify:false,
    lib: {
      entry: "./src/entry.ts",
      name: "SmartyUI",
      fileName: "smarty-ui",
      // 导出模块格式
      formats: ["es", "umd","iife"],
    },
  },
});

修改包配置(package.json)添加打包命令

 "scripts": {
    "build": "vite build"
  },

执行命令

pnpm build

控制台打印

vite v3.0.7 building for production...
✓ 6 modules transformed.
dist/smarty-ui.mjs   1.02 KiB / gzip: 0.45 KiB
Entry module "src/entry.ts" is using named and default exports together. Consumers of your bundle will have to use `SmartyUI["default"]` to access the default export, which may not be what you want. Use `output.exports: "named"` to disable this warning
dist/smarty-ui.umd.js   1.56 KiB / gzip: 0.67 KiB
Entry module "src/entry.ts" is using named and default exports together. Consumers of your bundle will have to use `SmartyUI["default"]` to access the default export, which may not be what you want. Use `output.exports: "named"` to disable this warning
dist/smarty-ui.iife.js   1.26 KiB / gzip: 0.54 KiB

生成新的文件目录

dist
|----smart-ui.iife.js
|----smart-ui.mjs
|----smart-ui.umd.js

看到提示说明正常导出了。最后编写一个验证页面,测试一下打包结果是否正确。

验证的过程还是基于Vite。首先测试加载全部组件,引用构建完的 smarty-ui.mjs 文件。

创建目录文件:demo/esm/index.html 测试全量导入

<h1>全量加载组件</h1>
<div id="app"></div>
<script type="module">
  import { createApp } from "vue/dist/vue.esm-bundler.js";
  import SmartyUI from "../../dist/smarty-ui.mjs";

  createApp({
    template: `
      <SButton/>
      <JSXButton/>
      <SFCButton/>
    `}).use(SmartyUI).mount('#app')

</script>

创建目录文件:demo/esm/button.html 测试按需导入

<h1>按需加载组件</h1>
<div id="app"></div>
<script type="module">
  import { createApp } from "vue/dist/vue.esm-bundler.js";
  import {
    SFCButton,
    JSXButton,
    MyButton,
  } from "../../dist/smarty-ui.mjs";

  createApp({
    template: `
<SButton/>
<JSXButton/>
<SFCButton/>
`,
  })
    .component(SFCButton.name, SFCButton)
    .component(JSXButton.name, JSXButton)
    .component(MyButton.name, MyButton)
    .mount("#app");
</script>

执行命令启动项目

pnpm dev

访问全量加载URL: http://localhost:5173/demo/esm/index.html

访问按需加载URL: http://localhost:5173/demo/esm/button.html

查看3种按钮组件是否都加载出来了,最后验证结果。

相关文章
|
1天前
|
JavaScript
Vue实战-组件通信
Vue实战-组件通信
4 0
|
1天前
|
JavaScript
Vue实战-将通用组件注册为全局组件
Vue实战-将通用组件注册为全局组件
5 0
|
1天前
|
JavaScript 前端开发
vue的论坛管理模块-文章评论02
vue的论坛管理模块-文章评论02
|
1天前
|
JavaScript Java
vue的论坛管理模块-文章查看-01
vue的论坛管理模块-文章查看-01
|
2天前
|
存储 JavaScript
Vue当前时间与接口返回时间的判断
Vue当前时间与接口返回时间的判断
8 0
|
2天前
|
JavaScript 前端开发
Vue生成Canvas二维码
Vue生成Canvas二维码
7 0
|
2天前
|
JavaScript 前端开发 开发者
new Vue() 发生了什么
new Vue() 发生了什么
9 1
|
2天前
|
JavaScript
Vue 中如何模块化使用 Vuex
Vue 中如何模块化使用 Vuex
6 0
|
1天前
|
JavaScript
vue页面加载时同时请求两个接口
vue页面加载时同时请求两个接口
|
1天前
|
JavaScript
vue里样式不起作用的方法,可以通过deep穿透的方式
vue里样式不起作用的方法,可以通过deep穿透的方式