每个开发人员都应该关注代码中的可重用性以及代码的业务隔离,这样可以让业务逻辑与应用架构分离,提升系统的扩展性。而 Web Component 就是这样一个技术,可以让我们创建一个独立的可重用组件。
本文将介绍使用 Svelte 创建通用的 Web Component 的完成过程。『通用』指的是该组件不局限于 Svelte 应用,还可以用于任何 JavaScript 应用程序(如 Vue、React 等),同时还将介绍使用 Svelte 创建 Web Component 的一些局限性。
什么是 Web Component?
Web Component 可以让我们创建可重用的、自定义的封装了样式以及功能的 HTML 元素。
例如,我们使用如下代码来创建一个导航条:
<style> /* CSS code for our navbar */ </style> <navbar> <!-- Some long code for our navbar --> </navbar>
如果用 Web Component 技术,我们可以定义一个自定义元素(如 <custom-navbar/>),然后你可以在网页任一地方使用该元素显示导航条,同时这个导航条的样式和页面其他元素的样式是完全隔离的,不会相互影响。而这项技术被称之为 Shadow DOM (影子 DOM)。
什么是 Shadow DOM?
Shadow DOM 是一个较小的、独立的 DOM,它与主 DOM 分开渲染,允许我们将样式和标记行为隔离到单个组件中。Shadow DOM 本质上允许我们将组件功能进行封装和隔离,我们可以单独对其设置样式、编写功能脚本,而不会干扰应用程序的其余元素。
你大概对 Web Component 有一个基本的认识,下面我们开始用 Svelte 来编写一个简单的组件。
构建 Web Component
准备工作
为了完成本文剩余的内容学习,需要你对掌握以下知识:
- 对 HTML, CSS 和 JavaScript 有基本的认识
- 熟悉命令行操作环境
- 需要一个文本编辑器
- 最好对 Svelte 有一些基本的了解(可以看看我之前翻译的文章 https://my.oschina.net/javayou/blog/5309554 )
开始
本文我们将开发两个组件:
- 第一个组件是一个卡片组件,接收三个属性分别是:卡片标题、卡片描述以及图片,该组件名为
<my-card />
- 第二个组件是一个带样式的按钮,接收一个
type
的属性,通过该属性来确定按钮的显示样式,组件名称为<cool-button />
同时我们将使用两种组件的发布方式,一个是打包到单一文件,另外一个是每个组件发布到一个独立的文件中。
下图显示的是两个组件运行后的外观:
我们从创建一个 Svelte 应用开始,并安装必须的包:
npx degit sveltejs/template web-component-tut cd web-component-tut npm install
上述命令执行完毕就可以使用如下命令启动一个测试环境:
npm run dev
然后我们打开浏览器访问 http://localhost:8080 就可以看到一个最基础的 Svelte 应用运行起来后的样子:
开发一个组件
使用 Svelte 生成通用 Web Component 的过程类似于创建常规 Svelte 组件的过程,只是进行了一些修改。
为了创建第一个卡片组件,我们首先创建一个文件 src/Card.svelte
并定义组件的属性、样式以及 HTML 标签,代码如下:
<script> // component props // Camel case not supported for props, see drawback section. export let card_title, card_desc, card_img; </script> <main> <div class="card-container"> <div class="card"> <img src={card_img} alt="My product" /> <div class="card-body"> <div class="row"> <div class="card-title"> <h2>{card_title}</h2> </div> </div> <p> {card_desc} </p> <button>Do Something</button> </div> </div> </div> </main> <style> .card { max-width: 350px; border-radius: 5px; box-shadow: 0 4px 6px 0 #00000033; padding: 0 0 10px 0; } .card img { width: 100%; height: auto; } .card-body { padding: 5px 10px; } .card-body p { color: #575757; margin-bottom: 20px; font-size: 14px; } </style>
你可以在其他组件中使用这个卡片组件,如下所示(这一步可忽略):
<script> import Card from "./Card.svelte"; </script> <main> <Card card_title="My Card Title" card_desc="Lorem ipsum dolor…" card_img="path/to/my-image.png" /> </main>
接下来是按钮组件,文件名 /src/Button.svelte
代码如下:
<script> // Component props export let type = "solid"; </script> <button class={type == "solid" ? "btn-solid" : "btn-outline"}> <slot /> </button> <style> button { padding: 10px; color: #fff; font-size: 17px; border-radius: 5px; border: 1px solid #ccc; cursor: pointer; } .btn-solid { background: #20c997; border-color: #4cae4c; } .btn-outline { color: #20c997; background: transparent; border-color: #20c997; } </style>
该组件的使用方法如下(可忽略):
import Button from "./Button.svelte"; <Button type="outline">Click me</Button>
将自定义组件转成通用组件
我们需要将自定义 Svelte 组件转成通用的 Web Component ,这样才可以在其他框架中直接使用。
要做这个转换操作,我们需要在 Svelte 配置文件中设置让编译器生成自定义元素。打开 rollup.config.js
在 plugins 增加一个 compilerOptions
配置项,在该配置项下增加 customElement: true 配置信息,如下所示:
... plugins: [ svelte({ compilerOptions: { dev: !production, customElement: true, ...
然后我们需要给两个组件分别定义一个元素名称,打开 Card.svelte
文件,在文件开头第一行插入如下内容:
<svelte:options tag="my-card" />
上述 tag
属性值代表组件的标签名称。
同样的,打开 Button.svelte 给第二个组件指定一个标签名称:
<svelte:options tag="cool-button" />
最后一步是在 main.js
中引入这两个组件,打开 /src/main.js
将里面的内容替换成如下两行:
import Button from "./Button.svelte"; import Card from "./Card.svelte";
这里我们已经完成了两个组件的开发步骤,下一步就是打包组件以便其他的应用可以使用这两个组件,打开命令行窗口进入项目所在目录执行如下命令:
npm run build
该命令将生成两个文件,分别是 build.js
和 build.map.js
, 文件位于项目下的 /build
目录。其中build.js
是打包的两个组件,而 build.map.js
是 build.js
源代码映射文件。
如果上述步骤顺利完成,我们就可以将 bundle.js
拷贝到一个新目录,然后创建一个 index.html
文件,内容如下:
<!DOCTYPE html> <html> <head> <title>My website</title> <script src="./build.js"></script> </head> <body> <div class="container"> <div class="row"> <div class="col"> <my-card card_title="Red Person" card_desc=" Lorem ipsum dolor sit, amet consectetur.." card_img="https://bit.ly/34B3zHX" > </my-card> <!-- Image credit - Shubham Dhage on unsplash.com --> </div> <div class="col"> <div class="border-bottom py-5"> <cool-button> Solid Cool Button </cool-button> <cool-button type="outline"> Outlined Cool Button </cool-button> </div> </div> </div> </div> </body> </html>
这是一个再简单不过的 HTML 页面了,该页面使用了上述两个组件,在浏览器中显示为如下效果:
组件分割
在某些情况下我们不需要将多个组件打包到同一个 js 文件中,我们希望每个组件有一个独立的 js 文件。要实现这个场景只需在 rollup.config.js
的 input 和 output 中进行配置即可。
在 input 的配置中我们可以指定一个数组,数组的元素就是每个组件的文件路径,而 output 指定为输出的目录即可:
export default { input: ["src/Card.svelte", "./src/Button.svelte"], output: { format: "iife", dir: "public/build/", }, ...
修改完成后再次执行 npm run build
,我们就可以在 build 目录中看到两个文件 Button.js
和 Card.js
。
使用方法类似:
<script src="Button.js" type="module"></script> <cool-button type="outline">Click Me</cool-button> <!-- another-page.html --> <script src="Card.js" type="module"></script> <my-card card_title="..."></my-card>
主要缺点
到这里我们已经掌握了使用 Svelte 开发 Web Component 的方法,这个过程非常之简单,但是,使用 Svelte 开发 Web 组件会有一些缺点如下:
- 组件的属性名称不允许使用驼峰命名法,例如你会发现使用形如 cardTitle 这样的属性名就无法正常工作,而驼峰命名法又是 JavaScript 推荐的命名风格。这是一个 Bug,不过如果你使用的是 Vite ,那么有一个 workaround plugin 可以解决这个问题。
- 如果不标记Web组件,就无法在Svelte中重用它们 - 不幸的是,您还必须标记要在自定义Web组件中使用的每个Svelte组件
加入你有一个Header.svelte
文件需要作为<my-header />
组件输出,但同时这个组件又依赖另外一个Nav.svelte
文件,而 Nav.svelte 我们不希望作为 Web 组件输出,这个问题使得我们必须将 Nav.svelte 也作为组件输出,否则程序就会报错。好在这个问题现在也有解了(详情请看 这里),我们可以通过配置来解决这个问题,虽然看起来不是那么的爽,就这样吧,又不是不能用。 - 浏览器的支持问题 — JavaScript
customElement
API 就是用来创建 Web Component 的底层 API,该 API 目前并没有被所有的浏览器支持,我们需要引入 Polyfill 来解决这个文件,详情请看 webcomponents official 。
总结
在本文中,我们学习了如何使用 Svelte 创建通用卡片和按钮组件,生成捆绑文件,拆分它们,甚至在单独的 HTML 页面中重用此组件。
动手试试吧?