在React或Vue应用中集成用Svelte创建的Web组件
04/05/2021 更新 1):
- 修复 React app 中的 bug;
- 增加 bolt 分支,使用 Bolt 对项目进行管理,简单测试可操作如下:
git clone https://github.com/vulcangz/svelte-webcomponent-in-react-vue.git -b bolt test1 cd test1 bolt install bolt svelte bolt vue # 在另一个终端窗口执行 bolt react
前言
近来读到一篇文章——《制作 Web 组件的所有方法》,其中比较了 51 种制作 Web 组件的不同方法的编码风格、包大小和性能。结论有:“GitHub 的 Catalyst 是新的冠军。"“由于出色的摇树优化,Svelte 在很小的库运行时成本下也做得很好。”,等等......来看 "Get" 到的关键点:1)现在浏览器已经很好地支持 Web 组件了;2)很多JS 框架和库支持 Web 组件(以上 51 种);3)有些库的大小竟然比裸 HTMLElement 还要小,这意味着 -- 潜力!感兴趣的可以去了解一下,在这里就不多说了。
上述文章着重描述的是各种方法的具体实现、和横向比较。而对于现实的开发人员而言,可能更关心于 Web 组件的可访问性。换个说法:我用 Svelte 开发的组件,能否正常在 React 或者 Vue 应用中使用?或者反过来使用。带着这个问题,借用了上述文章中的 Svelte 计数器代码,来创建一个名为 “my-counter” 的、实现计数器功能的、简单的 Web 组件,来尝试下在 React 和 Vue 中去使用这个组件。
需求与用例
- 1)Svelte 组件 UI 中包含 3 个按钮和 1 个计数器值显示:点击 ‘+’ 号计数值加 1;点击 '-' 号计数值减 1;点击最右边的自定义按钮触发 'customOnClick' 自定义事件。
- 2)在 React 或 Vue 应用中,引入 Svelte 组件、并在 HTML 页面显示,对于 '+'、'-' 操作,结果应该与原生组件表现一致;同时设置一个计数器,对于 'customOnClick' 自定义事件,设置相应的监听器和回调函数,实现点击 Web 组件的自定义按钮时,操作本应用的 '计数器' 加 1。
- 3)简单比较构建后的捆绑包的大小。
- 4)参考 UI 如下图所示。
|
|
实现过程(Step by step)
以下就如何创建 Svelte Web 组件、如何在 React 应用中集成 Svelte Web 组件、如何在 Vue 应用中集成 Svelte Web 组件分别进行实现,并写出了主要的步骤。对这些方面很熟悉的,就不需要花时间去细看了。我已将代码上传到 GitHub,Repo 地址是:https://github.com/vulcangz/svelte-webcomponent-in-react-vue。如果这篇文章和代码对您有帮助,还请给个'star',谢谢!
1、Web 组件简介
Web 组件 —— 采用一套不同的技术,允许您创建可重复使用的自定义元素,并在您的 Web 应用中使用它们。详细介绍请参见 MDN - Web Components。
2、使用 Svelte 创建 Web 组件
Svelte 组件可以编译成自定义元素(也就是 Web 组件),我们来实际测试一下。示例是一个计数器,基本元素包括3个按钮。创建完成后的组件效果如下所示:
1)使用 Svelte 官方组件模板 创建组件
npx degit sveltejs/component-template svelte-webcomponent
其中 svelte-webcomponent 是你的组件目录。
2)定制生成 Web 组件
a.先修改包名: package.json --> "name": "MyCounter"
b.修改组件名:src\Component.svelte --> src\MyCounter.svelte
c.代码内容改为:
<svelte:options tag="my-counter" />
<script>
import { createEventDispatcher } from 'svelte';
// Event handling
const dispatch = createEventDispatcher();
export let title = '?';
let count = 0;
let ref;
function dispatchClick(e) {
// dispatch("customOnClick", { text: "Sir, I don't understand your command, I only know that the current count value is: " + count });
const event = new CustomEvent("customOnClick", {
detail: { text: "Hi, I'm custom event from Svelte web component! My own count value is: " + count },
bubbles: true,
cancelable: true,
composed: true, // this line allows the event to leave the Shadow DOM
});
// console.log(event)
ref.dispatchEvent(event);
}
function inc() {
count++;
}
function dec() {
count--;
}
</script>
<style>
* {
font-size: 200%;
}
span {
width: 4rem;
display: inline-block;
text-align: center;
}
button {
width: 64px;
height: 64px;
border: none;
border-radius: 10px;
background-color: seagreen;
color: white;
}
</style>
<button on:click={dec}>
-
</button>
<span>{count}</span>
<button on:click={inc}>
+
</button>
<button on:click={dispatchClick} bind:this={ref}>
{title}
</button>
d.修改编译配置文件 rollup.config.js,增加编译选项:customElement: true
最终的 rollup.config.js 是这样的:
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import pkg from './package.json';
const name = pkg.name
.replace(/^(@\S+\/)?(svelte-)?(\S+)/, '$3')
.replace(/^\w/, m => m.toUpperCase())
.replace(/-\w/g, m => m[1].toUpperCase());
export default {
input: 'src/index.js',
output: [
{ file: pkg.module, 'format': 'es' },
{ file: pkg.main, 'format': 'umd', name }
],
plugins: [
svelte({
customElement: true,
}),
resolve()
]
};
e. 编译生成 Web 组件
cd svelte-webcomponent yarn yarn build
生成的组件代码在 dist/ 目录下。
3)验证该 Web 组件。
在根目录下创建一个 index.html 文件,内容是这样的:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Counter</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<my-counter title="咣"></my-counter>
<script type="text/javascript" src="dist/index.js"></script>
<script type="text/javascript">
function handleClick(e) {
console.log(e.detail.text)
}
document
.querySelector("my-counter")
.addEventListener('customOnClick', handleClick)
</script>
</body>
</html>
4)用你常用的 http server 测试该 Web 组件。
比如 Live Server(https://www.npmjs.com/package/live-server)。我用的是 Serv:
cd svelte-webcomponent live-server
命令执行后会自动打开浏览器访问 http://127.0.0.1:8080/,分别点击"+"、"-"号、或 "咣" 按钮,查看计数器变化。同时你可以 F12 打开开发者工具,在 console 查看输出信息。
2、创建 React 应用并集成 my-counter 组件
1)创建新的 React 应用
npx create-react-app react-with-webcomponent
2)分别修改 src 目录下的 App.js、App.css,最终代码如下:
App.jsimport React, { useState, useEffect, useRef } from "react";
import "MyCounter";
import './App.css';
function App() {
const [count, setCount] = useState(0);
const buttonRef = useRef();
// console.log(buttonRef)
useEffect(() => {
buttonRef.current.addEventListener("customOnClick", (e) => {
setCount(count + 1)
console.log(e.detail.text)
});
});
return (
<div>
<ul class="list-group">
<li>React counter
<span class="badge">{count}</span>
</li>
</ul>
<my-counter
title="咣"
ref={buttonRef}
// the classic onClick also works
onClick={(e) => {
console.log("on click", e);
}}
/>
</div>
);
}
export default App;
App.css
/* https://github.com/mdn/css-examples/blob/master/css-cookbook/list-group-badges--download.html */
body {
background-color: #fff;
color: #333;
font: 1.2em / 1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 0;
margin: 1em;
}
.list-group {
list-style: none;
margin: 0 0 0 1.6em;
padding: 0;
border: 1px solid #ccc;
border-radius: .5em;
width: 14em;
}
.list-group li {
border-top: 1px solid #ccc;
padding: .5em;
display: flex;
justify-content: space-between;
align-items: center;
}
.list-group li:first-child {
border-top: 0;
}
.list-group .badge {
background-color: rebeccapurple;
color: #fff;
font-weight: bold;
font-size: 80%;
border-radius: 10em;
min-width: 1.5em;
padding: .25em;
text-align: center
}
3)链接 MyCounter 包到本项目
注意到上面 App.js 中的 `import "MyCounter";` 了吗?运行之前,我们需要将之前编译好的包链接过来:
// 在 svelte-webcomponent 执行 cd svelte-webcomponent npm link // 在 react-with-webcomponent 执行 cd react-with-webcomponent npm link MyCounter npm start
命令执行后会自动打开浏览器访问 http://localhost:3000/,分别点击"+"、"-"号、或 "咣" 按钮,查看计数器变化。同时你可以 F12 打开开发者工具,在 console 查看输出信息。
3、创建 Vue 应用并集成 my-counter 组件
1)创建新的 Vue 应用
// 如果还没有 vue 命令,用 yarn global add @vue/cli 安装。 vue create vue-with-webcomponent cd vue-with-webcomponent npm link MyCounter yarn serve
命令执行后默认打开浏览器窗口,访问 http://localhost:8080/
2)修改 src 目录下的 App.vue,最终代码如下:
App.vue<template>
<div id="app">
<ul class="list-group">
<li>
Vue counter
<span class="badge">{{ count }}</span>
</li>
<li>
<my-counter
:title="myTitle"
@click="handleClick"
@customOnClick="handleCustomClick"
></my-counter>
</li>
</ul>
</div>
</template>
<script>
import "MyCounter";
import "./App.css";
export default {
name: "App",
data() {
return {
count: 0,
myTitle: `咣`,
};
},
methods: {
handleClick: function (event) {
console.log(event);
this.show = event.detail;
},
handleCustomClick: function (event) {
this.count++;
console.log(event.detail.text);
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
增加 App.css 样式文件
同上述 React 应用中的 app.css 相同。
3)计数器测试
由于当前处于编译和热加载状态:在浏览器访问 http://localhost:8080/,即可看到计数器界面。
分别点击"+"、"-"号、或 "咣" 按钮,查看计数器变化。同时你可以 F12 打开开发者工具,在 console 查看输出信息。
构建后的目标代码比较
React 与 Vue 应用构建后的目标代码对比
| 框架(Framework) | 文件名(File) | 大小 KB(Size) | 压缩后(Gzipped) |
|---|---|---|---|
| React | build\static\js\2.cb6ed4ec.chunk.js build\static\js\main.d13c8b99.chunk.js |
136 8.0 |
42.65 2.96 KB (+6 B) |
| Vue | dist\js\chunk-vendors.b5503860.js | 114.84 | 40.42 |
| React | build\static\js\main.d13c8b99.chunk.js | 8.0 | 2.96 KB (+6 B) |
| Vue | dist\js\app.4230b026.js | 8.34 | 3.66 |
| React | build\static\js\runtime-main.0b036b94.js | 4.0 | 1.18 |
| Vue | |||
| React | build\static\css\main.baa84833.chunk.css | 4.0 | 519 B (-5 B) |
| Vue | dist\css\app.c8a0ffb5.css | 0.67 | 0.36 |
从大小比较,在 JS 方面,两者大小差不多,Vue 略小一点。在 CSS 方面,React 压缩之后要小一些。
参考链接
- All the Ways to Make a Web Component - January 2021 Update on WebComponents.dev By <div>riots
- USING SVELTE IN PRODUCTION By Daniel Mies
- Integrate Web Components with Your Vue.js App By Joshua Bemenderfer
