在Typescript中使用Web Component

161 阅读3分钟

导入Web Component

在使用一个Web Component前必须先定义它,而定义过程实际上是调用浏览器环境中的customElements函数
在入口文件(main.js或者index.js)直接导入即可

import 'some-component'

模块中的内容会自动执行 并注册自定义元素
ssr框架中要在确保存在浏览器环境的情况下 使用import('xx')动态导入
浏览器会在自定义元素注册完毕后重新解析已经存在的自定义元素

类型声明

拓展JSX.IntrinsicElements以确保类型正确
创建xxx.d.ts文件 或者修改某个已有的类型声明 加入这样的代码

namespace JSX {
  interface IntrinsicElements {
    'a-a': { a?: string }
  }
}

然后就可以合法使用<a-a />组件了 它会接受一个可选的a参数 类型是string
如果想使用slot 还需要加上{ children?: any }
也可以通过已有类型进行拓展

namespace JSX {
  interface IntrinsicElements extends SomeComponent {}
}

各个框架对于web compontnent的解析各不相同 可能通过js设置 也可能设置在html标签上 所以字符串最稳妥

命名空间相关

如果当前的JSX并不来自命名空间 则要这样写

// 以solidjs为例
import 'solid-js'

declare module 'solid-js' {
  namespace JSX {
    interface IntrinsicElements extends SomeComponent {}
  }
}

如果想创建一个Web Component库

//components/index.ts
import './a'

export interface SomeComponent {}

//components/a/index.tsx

declare module '../index' {
  interface SomeComponent {
    'a-a': xxx
  }
}

然后可以导入components/index.ts 并用components下全部组件的类型拓展JSX.IntrinsicElements
如果需要自定义事件 还可以拓展GlobalEventHandlersEventMap类型 可以在addEventListener中得到代码提示

// 这里始终是global模块
declare global {
  interface GlobalEventHandlersEventMap {
    'some-event': CustomEvent<{a:1}>; // 类型参数是e.detail的类型
  }
}

此外 还可以以当前框架的类型为基础 将props属性拓展

创建一个能够按需导入的库

上述方法是全量导入 也可以按需导入

// components/index.ts
export * from './a'

// components/a/index.ts
export {} // 不能省略 这是为了让本文件成为模块 否则ts不允许declare global

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'a-a': { a?: string }
    }
  }
}

此时 导入components/a.ts可以自动拓展jsx类型
这个文件的类型可能会被ts自动收集 不过开发中不影响
测试时 可以排除掉打包后的文件
如果JSX不来自全局命名空间 依旧可以在全局命名空间JSX拓展类型 但要同步到'solid-js'中

import 'solid-js'

declare module 'solid-js' {
  namespace JSX {
    interface IntrinsicElements extends globalThis.JSX.IntrinsicElements {}
  }
}

打包配置(vite)

import tailwindcss from '@tailwindcss/vite'
import { resolve } from 'path'
import { defineConfig } from 'vite'
import dtsPlugin from 'vite-plugin-dts'
import solidPlugin from 'vite-plugin-solid'
import pkg from './package.json'

const outDir = 'leafer-component'
const excludeDeps: any = Object.keys(pkg.dependencies) // 不把任何依赖打包 全采用peerDenpencies的形式

export default defineConfig({
  plugins: [
    eslintPlugin(),
    solidPlugin(),
    tailwindcss(),
    dtsPlugin({
      include: ['src/leafer-component'],
      outDir,
    }),
  ],
  server: {
    port: 3000,
    host: '0.0.0.0',
  },
  build: {
    target: 'esnext',
    outDir,
    lib: {
      entry:{
          index:'src/leafer-component/index.ts',
          `leafer-box/index`:`src/leafer-component/leafer-box/index.tsx`
          // 通过fs等方法构建entry
      },
      formats: ['es'],
      name: 'LeaferComponent',
    },
    rollupOptions: {
      external: excludeDeps,
    },
  },
})

package.json

参考(这里)[juejin.cn/post/721774…]

有关的小知识

使用solid-element创建web component

customElement('my-component', {someProp: 'one', otherProp: 'two'}, (props, { element }) => {
  // ... Solid code
})

第二个参数是默认值 没在这里面提及的参数会被忽略
而组件函数的第二个参数的element属性可以取到创建的自定义元素本身