Skip to content

Latest commit

 

History

History
182 lines (135 loc) · 5.05 KB

File metadata and controls

182 lines (135 loc) · 5.05 KB

Component Library for UniApp

Table of Contents

Differences from Vue

Normally when we are developing a Vue 3 component library, we will use vite to generate the library code, and use vue-tsc to generate the .d.ts type definition files.

But it would be different to develop a component library for UniApp. UniApp would use their own build system, combined with @vue/compiler-sfc et cetera, to generate code for different platforms with their own components and VNode implementations.

So we'll have to distribute the .vue files as-is, and let the users use UniApp's build system to generate the code for their own projects.

Typical Project Configuration

Say we have a project named uniapp-component-library with the following structure:

uniapp-component-library
├── src/
│   ├── components/
│   │   ├── Button.vue
│   │   └── index.js
│   ├── index.js
│   └── types/
├── package.json
└── tsconfig.json

You might noticed that we are using *.js rather than *.ts files, but there is a tsconfig.json file in the project root. That is because we should distribute *.vue files as-is, only the *.js files can be imported without any extra configuration.

The package.json file

It is recommended to set the type in the package.json file to be module rather than default commonjs. Since we are using vite to build all the code, ES Module is more suitable for vite.

Example package.json:

{
  "name": "uniapp-component-library",
  "version": "0.0.0",
  "module": "src/index.js",
  "exports": {
    ".": {
      "import": "./src/index.js"
    },
    "./package.json": "./package.json"
  },
  "peerDependencies": {
    "@dcloudio/uni-app": "*",
    "@dcloudio/uni-ui": "*",
    "vue": "^3.2.0"
  },
}

As you can see, we should add vue, @dcloudio/uni-app and @dcloudio/uni-ui as peer dependencies, since we are using them in our components but we don't want to depend on them directly, and they are likely to be installed in the user's project.

Export Vue Components

We can use the export keyword or module.exports to export the Vue components into the src/index.js file:

src/components/index.js

import Button from './Button.vue'

export {
  Button
}

src/index.js

export * from './components'

TypeScript Type Definitions

As mentioned above, we can only use *.js files in our component library, but is it possible to provide type definitions for TypeScript users? The answer is an uppercase YES.

To achieve this, we should use vue-tsc to generate the type definitions files just like we do in a Vue 3 project:

{
  "scripts": {
    "build:dts": "vue-tsc --emitDeclarationOnly --declaration --outDir types"
  }
}

Note: You should make sure that *.vue files are listed in the include field in the tsconfig.json file.

Then we can use the types field in the package.json file to point to the generated type definitions files:

{
  "types": "types/index.d.ts",
  "exports": {
    ".": {
      "types": "./types/index.d.ts",
      "import": "./src/index.js"
    }
  }
}

Note: types in exports should always be before import or require so that TypeScript can find the type definitions. Reference

You might found that some of the types are resulted to any or unknown since we are using JS only. In this case, we can declare the types with JSDoc comments.

Component Props

const props = defineProps({
  /**
   * The button type
   */
  type: {
    type: /** @type {'default' | 'compact'} */ (String), // use parentheses to coerce the type
    default: 'default'
  },
})

Template Refs

const buttonRef = ref(/** @type {HTMLButtonElement | null} */ (null))

Wrap the components with InstanceType and typeof operator to get the correct type of the component instance:

import { RouterLink } from 'vue-router'

const routerLinkRef = ref(/** @type {InstanceType<typeof RouterLink> | null} */ (null))

Define Complex Types with JSDoc

Complex types can be defined with @typedef:

Say we have a Person interface like this:

interface Person {
  name: string
  age?: number
}

We can define it with JSDoc like this:

/**
 * @typedef {{ name: string; age?: number }} Person
 */

And then use it like this:

/**
 * @type {Person}
 */
const person = {
  name: 'John'
}