开发一个 React 和 Vue 都能用的组件?基于 Lit 和 Tailwind

简介: 开发一个 React 和 Vue 都能用的组件?基于 Lit 和 Tailwind

背景


最近在开发一个 HugGroup 组件。

你这么一听,可能会对这个名字可能有些疑惑。我说出它的另外一个名字,你可能就懂了,它还叫做 OnlineUser。

其实它就长这样。

image.png

HugGroup 是一个更专业的称呼。

因为这是一个开源项目,所以考虑多个框架的通用性,比如如何与 React、Solid、Angular 等框架进行结合。

其实这个需求应该是应用的开发者来考虑的。但是最近这两年,我们这类做底层库的人为了取悦做应用的开发者,把他们的心智负担都给分担没了。

所以我要先实现一个原生的 JavaScript 库,然后再创建几个基于框架的库,就像胶水一样把它们给粘起来。

所以在 npm 可能会有这么几个包。

  • @xx/hug-group 核心库,支持原生 JS。
  • @xx/hug-group-react:适配 React。
  • @xx/hug-group-solid:适配 Solid。
  • @xx/hug-group-angular:适配 Angular。
  • @xx/hug-group-vue:适配 Vue。
  • ......

有些恶心,但是没办法。

我也希望未来某个框架能够一统天下。


技术选型


既然是 Online 组件,那一定会有个服务器支撑了?

当然会有,但是不需要自己搭建,我们团队有开源免费的 presence.js。

通讯技术是用 WebTransport 和 presence.js 来做的,WebTransport 是一个没有线头阻塞;支持 QUIC(HTTP3) 和 UDP 的新一代通讯技术,但是国内知道 WebTransport 的人很少,大家还在玩 HTTP1/1 和 WebSocket。

除此之外,我还搞了个 webtransport-polyfill 和 react-cursor-chat,一个是在没实现 web transport API 的浏览器里面直接跑 WebTransport 代码,比如 Firefox。

另一个是用鼠标聊天的组件,在 VSCode 掘金插件中就用到了这个组件。

image.png

如果你用过 Figma,应该看到过这类组件。

对应的链接我都挂在下面了,有兴趣的同学可以去看下。

你可能会问了,这些东西既然没人用,你还搞它们干什么?国内确实没人用,国外可是不少人在关注呢。说不定几年之后 WebTransport 就成了未来前后端通讯的最佳实践了,具有前沿性的创新型公司当然要提前布好局啊。

当然,本文中通讯技术不是重点,重点是前端组件的开发。

我知道这才是大多数人喜欢看的东西,让我们快点儿开始吧,不然又有人要骂我标题党了。

项目原来是用 tsdx 做构建工具的,但是 tsdx 对 umd 和 iife 这类构建目标并不是很友好,它更适合 cjs 和 esm 这类构建目标。

于是我换了一套技术栈:

  • rollup:其实 tsdx 内部也包了 rollup,但我还是更喜欢直接写 rollup 配置。
  • babel:用 babel 做 ts 的转译。
  • typescript:写代码的基础语言。
  • lit:这个库出来好几年了,更专注于封装组件。Google 一直在推,可惜一直不温不火。
  • tailwindcss:写样式的基础库。
  • ESLint:用来检测代码。
  • prettier:用来格式化代码。

技术栈介绍完毕,开始搭建!


安装配置 Rollup


其实很多人不知道 rollup 有一个项目模板的,它很好用,只是 rollup 没有怎么介绍它而已。

这个仓库帮我们处理了一些事,这样我们就不需要从头开始了。

克隆这个仓库:github.com/rollup/roll…


git clone git@github.com:rollup/rollup-starter-app.git

之后进行以下几步操作:

  1. 修改项目名:mv rollup-starter-app hug-group。
  2. 进入项目:cd hug-group。
  3. 安装依赖:npm i。
  4. 启动开发服务器:npm dev。
  5. 打开浏览器,访问命令行的地址。看到下图,意味着 rollup 的 hello world 就跑起来了。

image.png

这个项目把最基本的 rollup 配置设置好了,并且用 serve 作为简单的开发服务器。


安装配置 Babel 和 TypeScript


babel 有支持 rollup 的插件,我们要用这个插件让 rollup 去调用 babel。然后通过 babel 去把 TypeScript 代码转换成 JavaScript 代码。

安装一堆依赖:

  • @babel/core
  • @rollup/plugin-babel
  • @babel/preset-env
  • typescript
  • @rollup/plugin-typescript
  • @babel/preset-typescript


pnpm --filter @yomo/hug-group i -D @babel/core @rollup/plugin-babel @babel/preset-env typescript @rollup/plugin-typescript @babel/preset-typescript

创建 src/main.ts 文件,并且把原来 src 目录的内容删掉。

在 rollup.config.js 中导入 babel 插件,把入口设置为 main.ts。

这是完整配置:


import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import typescript from '@rollup/plugin-typescript';
import { babel } from '@rollup/plugin-babel';
// `npm run build` -> `production` is true
// `npm run dev` -> `production` is false
const production = !process.env.ROLLUP_WATCH;
export default {
  input: 'src/main.ts',
  output: {
    file: 'public/bundle.js',
    format: 'es',
    sourcemap: true,
  },
  plugins: [
    resolve(),
    commonjs(),
    typescript({
      include: ['src/**/*.ts'],
    }),
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
    }),
    production && terser({ format: { comments: false } }), // minify, but only  in production
  ],
};

创建 .babelrc 文件,这是具体配置:


{
  "presets": ["@babel/preset-env"]
}

安装配置 ESLint 和 Prettier


安装一堆依赖:

  • eslint
  • prettier
  • eslint-config-prettier
  • eslint-plugin-prettier


pnpm --filter @yomo/hug-group i -D eslint prettier eslint-config-prettier eslint-plugin-prettier

然后运行以下命令来创建 eslint 的配置文件。


pnpm --filter @yomo/hug-group exec eslint --init

之后它会问一堆啰嗦的问题,你按照自己喜欢的内容去选择就好了。

之后会生成一个 .eslintrc.js 的配置文件。

在它的 plugin 中追加一个 prettier 就可以了。


安装插件


由于 Lit 用来表示 HTML 的方式是使用 JavaScript 模板字符串,在编辑器中并不会高亮。

为了实现在编辑器中高亮,需要下载对应的插件。

我使用的编辑器是 VSCode,安装的插件是 lit-element

没安装之前:

image.png

安装之后:

image.png


安装 Lit 和配置 TypeScript


现在我们已经把最基本工程化工具都配置好了,现在开始安装编写 UI 的 Lit 库。

Lit 就是在 Web Component 基础上的一个包装器,它为我们提供了一种将 UI 声明式地编写为状态函数的方法。

Lit 和 React 这类框架之间的区别是,Lit 是建立在 Web Component 之上的框架,并且它只专注于做组件。

第一步装包。


pnpm --filter @yomo/hug-group i lit

然后在 main.ts 里面编写一个 HelloWorld 组件。


import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
@customElement('hug-group')
export default class HugGroup extends LitElement {
  render() {
    return html` <h1>Hug Group</h1> `;
  }
}

最后修改 public/index.html 的内容。


<!DOCTYPE html>
<html>
  <head lang="en">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>rollup-starter-app</title>
    <style>
      body {
        font-family: 'Helvetica Neue', Arial, sans-serif;
        color: #333;
        font-weight: 300;
      }
    </style>
  </head>
  <body>
    <hug-group></hug-group>
    <script type="module" src="bundle.js"></script>
  </body>
</html>

这时应该会有一个错误,错误提示是无法使用 TypeScript 装饰器。

我们处理一下,在 tsconfig.json 中把 experimentalDecorators 设置为 true。

或者直接复制我的配置。


{
  "include": [
    "src",
    "types"
  ],
  "compilerOptions": {
    "target": "es6",
    "experimentalDecorators": true,
    "moduleResolution": "node"
  }
}

不出意外就跑起来了。

image.png


安装配置 Tailwind


安装一堆依赖:

  • rollup-plugin-postcss
  • tailwindcss
  • postcss
  • autoprefixer


pnpm --filter @yomo/hug-group i -D rollup-plugin-postcss tailwindcss postcss autoprefixer

运行以下命令生成 Tailwind 配置文件:


pnpm --filter @yomo/hug-group exec tailwindcss init

它会生成一个 tailwindcss.config.js 的配置文件。

稍微调整一下:


/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.ts'],
};

在 rollup.config.js 中导入 postcss 插件。


import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
import typescript from '@rollup/plugin-typescript';
import { babel } from '@rollup/plugin-babel';
import postcss from 'rollup-plugin-postcss'; // import postcss plugin
const production = !process.env.ROLLUP_WATCH;
export default {
  input: 'src/main.ts',
  output: {
    file: 'public/bundle.js',
    format: 'es',
    sourcemap: true,
  },
  plugins: [
    resolve(),
    commonjs(),
    postcss(), // use postcss plugin
    typescript({
      include: ['src/**/*.ts'],
    }),
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**',
    }),
    production && terser({ format: { comments: false } }),
  ],
};

创建 postcss.config.js。


module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

创建 src/styles.css 文件。


@tailwind base;
@tailwind components;
@tailwind utilities;

在 src/main.ts 中导入,并编写样式。


import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import './styles.css';
@customElement('hug-group')
export default class HugGroup extends LitElement {
  createRenderRoot() {
    return this; // turn off shadow dom to access external styles
  }
  render() {
    return html`
    <h1 class="mx-auto my-4 py-4 text-center shadow-lg text-xl w-1/2">
    Hug Group
    </h1>
    `;
  }
}

最后回到浏览器中,查看效果。

image.png

恭喜,现在我已经有了一个 Lit 和 Tailwind 脚手架。

经过一系列配置,我终于可以聚焦于开发这个 HugGroup 组件了。

于是,我开始开发 HugGroup。

image.png

好了,开发完了。


总结


可以看到,要想搭建一套稍微完善些的前端工程化是非常复杂的,这个过程需要借助大量的工具、库、插件来处理各种各样的问题。

一个不算复杂的 OnlineUser 组件,哦不,HugGroup 组件。就需要将近 20 个包来支撑它的开发,这还没算测试等工作。

等它的功能完善后,基于它封装适配 React、Solid、Svelte 和 Vue 等框架的组件。这又是个枯燥无味的过程,但是为了简化上层应用开发者的体验,我又不得不去做这些事。

最后差点儿忘记了解释一下标题,为什么这个组件,React、Solid、Svelte、Vue、Angular 们都能用呢?因为用 lit 做的组件,它本质上只是个标准的 WebComponent,像原生标签一样平平无奇。所以和框架无关。

如果你也想要构建与框架无关的通用组件,不妨试试我的这套技术栈。

如果你想低成本构建实时应用,比如不考虑服务器的实现,不妨试试我们团队开源的 presence.js。



相关文章
|
23天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
126 64
|
27天前
|
存储 缓存 JavaScript
如何优化React或Vue应用的性能
需要注意的是,性能优化是一个持续的过程,需要根据具体的应用场景和性能问题进行针对性的优化。同时,不同的项目和团队可能有不同的优化重点和方法,要结合实际情况灵活运用这些优化策略,以达到最佳的性能效果。
107 51
|
23天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
29 8
|
23天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
27天前
|
监控 JavaScript 前端开发
如何在实际应用中测试和比较React和Vue的性能?
总之,通过多种方法的综合运用,可以相对客观地比较 React 和 Vue 在实际应用中的性能表现,为项目的选择和优化提供有力的依据。
33 1
|
28天前
|
JavaScript 前端开发 开发者
React和Vue有什么区别?
React 和 Vue 都有各自的优势和特点,开发者可以根据项目的需求、团队的技术背景以及个人的喜好来选择使用。无论是 React 还是 Vue,它们都在不断发展和完善,为前端开发提供了强大的支持。
73 2
|
28天前
|
JavaScript 前端开发 测试技术
React和Vue的性能对比如何?
需要注意的是,性能不仅仅取决于框架本身,还与开发者的代码质量、架构设计以及项目的优化程度等密切相关。因此,在评估性能时,应该综合考虑多个因素,而不是仅仅局限于框架之间的比较。
108 1
|
1月前
|
JavaScript 前端开发 算法
React 框架和 Vue 框架的区别是什么?
React框架和Vue框架都是目前非常流行的前端JavaScript框架,它们在很多方面存在区别
|
1月前
|
JavaScript 前端开发 开发者
JavaScript框架React vs. Vue:一场性能与易用性的较量
JavaScript框架React vs. Vue:一场性能与易用性的较量
34 0