Vue3 + Typescript + pnpm + rollup/gulp 工程化搭建组件库(二)

4,032 阅读4分钟

组件搭建

接着第一章节开始组件部分的内容

首先在packages下创建三个目录文件夹,目录结构如下:

    + packages
        - components  # 组件代码
        - theme-chalk # 样式
        - utils  # 公共方法

然后分别cd到每个目录下初始化pnpm init -y生成三个包,修改package.json,三个内容类似

# 以components为例子,其它同,修改name即可
{
  "name": "@w-plus/components",  
  "version": "1.0.0",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "WQ",
  "license": "ISC"
}

这样每个目录下现在有一个package.json文件,现在我们希望每个包可以互相引用使用,所以要在根目录下安装,也可以独立发布

在根目录下安装三个子包pnpm install @w-plus/components -w,其它两个包同样的操作,-w--workspace代表允许安装到根目录下,不加会报错,然后查看根目录下的package.json已经有了这三个包

image.png

components目录下创建icon目录,来编写一个icon组件,目录如下:

    + components
        + icon
            + src # 组件源代码
                - icon.ts  # 放组件的props及公共方法
                - icon.vue # 组件代码
            - index.ts # 组件入口

icon.ts下来定义props

// 这里放组件的props及公共方法
import type { ExtractPropTypes } from "vue" 
//  as const,会让对象的每个属性变成只读(readonly)
export const iconProps = {
    size:{
        type:Number
    },
    color:{
        type:String
    }
} as const

export type IconProps = ExtractPropTypes<typeof iconProps>

icon.vue中写组件代码

<template>
  <i class="w-icon" :style="style">
      <slot></slot>
  </i>
</template>

<script lang="ts">
import { computed, defineComponent } from "vue";
import { iconProps } from "./icon";

export default defineComponent({
  name: "WIcon",
  props: iconProps,
  setup(props) {
      const style = computed(() => {
          if(!props.size && !props.color){
              return {}
          }
          return {
              ...(props.size ? { 'font-size':props.size + 'px'} : {}),
              ...(props.color ? { 'color':props.color} : {}),
          }
      })

      return { style }
  },
});
</script>

在组件入口处导出组件,index.ts

import { withInstall } from "@w-plus/utils/with-install"
import Icon from "./src/icon.vue";

const WIcon = withInstall(Icon);

export{
    WIcon
}
export default WIcon;

最后在play下的main.ts中引入组件测试,增加以下代码:

import { WIcon } from "@w-plus/components/icon“
app.use(WIcon)

这时候会发现use(WIcon)报错传入的类型不对,传入的类型要为Plugin_2

image.png

所以我们需要处理一下这个问题,在Icon组件下的index.ts中,修改代码为:

import { withInstall } from "@w-plus/utils/with-install"
import Icon from "./src/icon.vue";

const WIcon = withInstall(Icon);
export default WIcon;

因为在每个组件中都要处理这个问题,所以我们封装一个withInstall方法在utils下新建文件with-install.ts文件,代码如下:

import type { App, Plugin } from "vue"; // 只是导入类型不是导入App的值

/**
 * 组件外部使用use时执行install,然后将组件注册为全局
 */

// 类型必须导出否则生成不了.d.ts文件
export type SFCWithInstall<T> = T & Plugin;

 /**
  * 定义一个withInstall方法处理以下组件类型问题
  * @param comp 
  */
export const withInstall = <T>(comp: T) => {
   /**
    * 直接写comp.install = function(){} 的话会报错,因为comp下没有install方法
    * 所以从vue中引入Plugin类型,断言comp的类型为T&Plugin
    */
   (comp as SFCWithInstall<T>).install = function (app: App) {
     app.component((comp as any).name, comp);
   };
   return comp as SFCWithInstall<T>;
 };
 

在这里补充一个ts的小知识,Type 类型别名

可以使用类型别名接收抽离出来的内联类型实现复用,可以通过如下所示“type别名名字 = 类型定义”的格式来定义类型别名

/** 类型别名 */
{
  type LanguageType = {
    /** 以下是接口属性 */
    /** 语言名称 */
    name: string;
    /** 使用年限 */
    age: () => number;
  }
}
// 针对接口类型无法覆盖的场景,比如组合类型、交叉类型,我们只能使用类型别名来接收
{
  /** 联合 */
  type MixedType = string | number;
  /** 交叉 */
  type IntersectionType = { id: number; name: string; } 
    & { age: number; name: string };
  /** 提取接口属性类型 */
  type AgeType = ProgramLanguage['age'];  
}

类型别名,即我们仅仅是给类型取了一个新的名字,并不是创建了一个新的类型。

我们在play下的app.vue文件中使用icon组件,然后npm run dev可以看到组件已经生效了

<template>
    测试
    <w-icon color="blue" :size="20">Hello Icon</w-icon>
</template>

image.png

Icon组件需要有图标,这里使用iconfont,在iconfont中新建项目,名称为w-plus,然后再图标库添加自己想添加的图标到项目里,打开项目设置,更换图标前缀,配置如下:

image.png 设置好后把项目下载到本地

image.png 接着需要编写样式文件,在我们的样式目录theme-chalk下,目录结构如下:

+ theme-chalk
    + src
        + fonts # 字体文件存放,将刚才下载的字体文件放入
            - iconfont.ttf
            - iconfont.woff
            - iconfont.woff2
        + mixins
            - config.scss # 全局配置
            - mixins.scss # 混入mixin
        - icon.scss # 图标文件
        - index.scss # 入口

config.scss代码如下(sass语法):

$namespace:'w'

/**
* 命名规范 BEM规范

<div class="z-cell">
    <div class="z-cell__label"></div>
    <div class="z-cell__value is_check"></div>
</div>

*/

mixins.scss代码如下:

// 声明公共的sass方法

/*
* 引入config
*/
@use 'config' as *;

/*
* 将config暴露给全局使用
*/
@forward 'config';

icon.scss将刚才下载的css文件内容copy到里面,修改如下:

image.png index.scss中导入icon.scss,代码如下:

@use 'icon.scss';

最后验证iconfont配置是否生效,修改play/app.vue文件(class是刚才icon.scss中的class):

<template>
    测试
    <w-icon color="blue" :size="30" class="w-icon-delete">Hello Icon</w-icon>
</template>

npm run dev启动服务,测试,页面中已经显示图标:

image.png

到此,Icon组件已经基本完成了,并且可以正常使用


后续见下一章节:工具库打包