Skip to content

Commit 24b6d60

Browse files
authored
feat: support web components (#641)
closes #628
1 parent 556e6fd commit 24b6d60

File tree

27 files changed

+1971
-74
lines changed

27 files changed

+1971
-74
lines changed

docs/.vitepress/config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ const config = {
184184
text: 'TypeScript Support',
185185
link: '/guide/advanced/typescript',
186186
},
187+
{
188+
text: 'Web components',
189+
link: '/guide/advanced/wc',
190+
},
187191
{
188192
text: 'Optimization',
189193
link: '/guide/advanced/optimization',
@@ -355,6 +359,10 @@ const config = {
355359
text: 'TypeScript Support',
356360
link: '/ja/guide/advanced/typescript',
357361
},
362+
{
363+
text: 'Web components',
364+
link: '/ja/guide/advanced/wc',
365+
},
358366
{
359367
text: 'Optimization',
360368
link: '/ja/guide/advanced/optimization',

docs/guide/advanced/wc.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Web components
2+
3+
:::tip Support Version
4+
:new: 9.2+
5+
:::
6+
7+
Vue 3.2 later, we can use the WebComponents as described in the [official documentation](https://v3.vuejs.org/guide/web-components.html).
8+
9+
This will support the use of Vue I18n in Web Components starting with Vue I18n v9.2.
10+
11+
There are a few things to keep in mind when using Vue I18n with Web Components.
12+
13+
## Make preparetion for Web Components to host the I18n instance
14+
15+
Using `defineCustomElement`, which is supported since Vue 3.2, we can provide Vue components implemented in SFC as Web Components. This means that Vue components implemented using `useI18n` can be served as Web Components with i18n support.
16+
17+
However, the provided Web Components cannot be inserted directly into HTML. You need to prepare the following Web Components to host the i18n instance created by `createI18n`.
18+
19+
Web Components that host the i18n instance:
20+
```html
21+
<script lang="ts">
22+
import { defineComponent, provide } from 'vue'
23+
import { createI18n, I18nInjectionKey } from 'vue-i18n'
24+
25+
/**
26+
* create an i18n instance to host for other web components
27+
*/
28+
const i18n = createI18n<false>({
29+
legacy: false, // must set to `false`
30+
locale: 'en',
31+
messages: {
32+
en: {
33+
hello: 'Hello!'
34+
},
35+
ja: {
36+
hello: 'こんにちは!'
37+
}
38+
}
39+
})
40+
41+
export default defineComponent({
42+
// ...
43+
setup(props) {
44+
/**
45+
* provide i18n instance with `I18nInjectionKey` for other web components
46+
*/
47+
provide(I18nInjectionKey, i18n)
48+
49+
// ...
50+
51+
return {}
52+
}
53+
})
54+
</script>
55+
56+
<!-- template to slot the content -->
57+
<template>
58+
<slot />
59+
</template>
60+
```
61+
62+
The above code has the following three points.
63+
64+
- Call `createI18n` to create an i18n instance
65+
- In `setup`, specify the i18n instance created with `createI18n` along with `I18nInjectionKey` in `provide`
66+
- template has `slot` element only
67+
68+
In the `script` block, we first use `createI18n` to create an i18n instance. In a Vue application, the i18n instance created by `createI18n` can be used as a Vue plugin by specifying the i18n instance in the Vue application `app.use` created by `createApp`. 18n instance to the Vue application `app.use` generated by `createApp`, we needed to install Vue I18n as a Vue plugin to the Vue application.
69+
70+
If you use `defineCustomElement`, the Vue component can no longer be controlled from the Vue application side, so even if you run the Web Components version of the component in your Vue application, you can't attach the i18n instance created with `createI18n` to the target Web Components via `app.use` from the Vue application side.
71+
72+
So, in order to attach i18n instances to Web Components, we use `provide` in `setup` to expose i18n instances to other Web Components. This allows Web Components that implement i18n with `useI18n` to work by being hosted by Web Components that work `provide`.
73+
74+
Then, to host other Web Components, the `template` block makes it possible by using the `slot` element.
75+
76+
Export this hosted Web Components as follows:
77+
78+
```javascript
79+
import { defineCustomElement } from 'vue'
80+
import I18nHost from './components/I18nHost.ce.vue'
81+
82+
const I18nHostElement = defineCustomElement(I18nHost)
83+
84+
export { I18nHostElement }
85+
```
86+
87+
The following `useI18n` implements and exports Web Components to:
88+
89+
```html
90+
<script setup lang="ts">
91+
import { useI18n } from 'vue-i18n'
92+
93+
const { t } = useI18n()
94+
</script>
95+
96+
<template>
97+
<p>{{ t('hello') }}</p>
98+
</template>
99+
```
100+
101+
```javascript
102+
import { defineCustomElement } from 'vue'
103+
import HelloI18n from './components/HelloI18n.ce.vue'
104+
105+
const HelloI18nElement = defineCustomElement(HelloI18n)
106+
export { HelloI18nElement }
107+
```
108+
109+
When the following Vue application is registered as a custom element of Web Components:
110+
111+
```javascript
112+
import { createApp } from 'vue'
113+
import { I18nHostElement } from './paht/to/I18nHostElement'
114+
import { HelloI18nElement } from './paht/to/HelloI18nElement'
115+
import App from './App.vue'
116+
117+
customElements.define('i18n-host', I18nHostElement)
118+
customElements.define('hello-i18n', HelloI18nElement)
119+
120+
createApp(App).mount('#app')
121+
```
122+
123+
So, In `App.vue`, which is the entry point of a Vue application, the following template will work:
124+
125+
```html
126+
<template>
127+
<i18n-host>
128+
<h1>Vue I18n in Web component</h1>
129+
<hello-i18n />
130+
</i18n-host>
131+
</template>
132+
```
133+
134+
The complete example described so far can be looked [here](https://github.com/intlify/vue-i18n-next/tree/master/examples/web-components).
135+
136+
## Limitations
137+
1. The Vue I18n that can be used to implement Web Components is only **Composition API**.
138+
2. When implementing Web Components, **Vue components implemented with `useI18n` cannot be imported and used together**. This is due to the [Provide / Inject](https://v3.vuejs.org/guide/web-components.html#definecustomelement) limitations of Vue.js for Web Components.
139+
140+

docs/ja/guide/advanced/wc.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Web components
2+
3+
:::tip Support Version
4+
:new: 9.2+
5+
:::
6+
7+
Vue 3.2 以降から、[公式ドキュメント](https://v3.vuejs.org/guide/web-components.html)に記載されているとおり、Vue.js で Web components を利用できるようになりました。
8+
9+
それに伴い、Vue I18n v9.2 から Web Components で Vue I18n を使うことができるようサポートとしています。
10+
11+
Vue I18n を Web Components で利用するにあたって、いくつか注意点があります。
12+
13+
14+
## I18n インスタンスをホストする Web Components を用意する
15+
16+
Vue 3.2 からサポートされた `defineCustomElement` を使って、SFC で実装した Vue コンポーネントを Web Components として提供することができます。つまり、`useI18n` を使って実装された Vue コンポーネントは、i18n がサポートされた Web Components として提供できることを意味します。
17+
18+
しかしながら、その提供された Web Components をそのまま HTML に挿入して使うことはできません。`createI18n` で生成された i18n インスタンスをホストする以下のような Web Components を用意する必要があります。
19+
20+
i18n インスタンスをホストする Web Components:
21+
```html
22+
<script lang="ts">
23+
import { defineComponent, provide } from 'vue'
24+
import { createI18n, I18nInjectionKey } from 'vue-i18n'
25+
26+
/**
27+
* create an i18n instance to host for other web components
28+
*/
29+
const i18n = createI18n<false>({
30+
legacy: false, // must set to `false`
31+
locale: 'en',
32+
messages: {
33+
en: {
34+
hello: 'Hello!'
35+
},
36+
ja: {
37+
hello: 'こんにちは!'
38+
}
39+
}
40+
})
41+
42+
export default defineComponent({
43+
// ...
44+
setup(props) {
45+
/**
46+
* provide i18n instance with `I18nInjectionKey` for other web components
47+
*/
48+
provide(I18nInjectionKey, i18n)
49+
50+
// ...
51+
52+
return {}
53+
}
54+
})
55+
</script>
56+
57+
<!-- template to slot the content -->
58+
<template>
59+
<slot />
60+
</template>
61+
```
62+
63+
上記コードのポイントは以下の3つです。
64+
65+
- `createI18n` を呼び出して、i18n インスタンスを生成
66+
- `setup` で、`createI18n` で生成した i18n インスタンスを `provide``I18nInjectionKey` といっしょに指定する
67+
- template は `slot` 要素のみ
68+
69+
`script` ブロックでは、まず `createI18n` を使って i18n インスタンスを生成しています。`ceateI18n` は Vue I18n を使う上で、最初にセットアップが必要な関数です。Vue アプリケーションにおいては、`createI18n` で生成された i18n インスタンスを、`createApp` で生成された Vue アプリケーション `app.use` に i18n インスタンスを指定することで、Vue I18n を Vue プラグインとして Vue アプリケーションにインストールする必要がありました。
70+
71+
Vue コンポーネントに対して `defineCustomElement` 使うと、その Vue コンポーネントはもはやVue アプリケーション側から制御できなくなってしまうため、例え Vue アプリケーション上で、その Web Components 化されたものを動かしたとしても、Vue アプリケーション側から `createI18n` で生成された i18n インスタンスを `app.use` 経由で対象先の Web Components にアタッチできません。
72+
73+
そのため、Web Components に i18n インスタンスをアタッチするために、`setup` 内で `provide` を使って i18n インスタンスを他の Web Components に公開しています。これにより、`useI18n` で i18n が実装されている Web Components が、`provide` が実行されている Web Components にホストされることで、動作するようになります。
74+
75+
そして、他の Web Components をホストするために、`template` ブロックでは、`slot` 要素を使うことで可能にしています。
76+
77+
このホストする Web Components を以下のようにexportsし:
78+
79+
```javascript
80+
import { defineCustomElement } from 'vue'
81+
import I18nHost from './components/I18nHost.ce.vue'
82+
83+
const I18nHostElement = defineCustomElement(I18nHost)
84+
85+
export { I18nHostElement }
86+
```
87+
88+
以下のような `useI18n` が実装、そして exportされた Web Components を:
89+
90+
```html
91+
<script setup lang="ts">
92+
import { useI18n } from 'vue-i18n'
93+
94+
const { t } = useI18n()
95+
</script>
96+
97+
<template>
98+
<p>{{ t('hello') }}</p>
99+
</template>
100+
```
101+
102+
```javascript
103+
import { defineCustomElement } from 'vue'
104+
import HelloI18n from './components/HelloI18n.ce.vue'
105+
106+
const HelloI18nElement = defineCustomElement(HelloI18n)
107+
export { HelloI18nElement }
108+
```
109+
110+
以下のような Vue アプリケーションで Web Components のカスタム要素として登録した場合:
111+
112+
```javascript
113+
import { createApp } from 'vue'
114+
import { I18nHostElement } from './paht/to/I18nHostElement'
115+
import { HelloI18nElement } from './paht/to/HelloI18nElement'
116+
import App from './App.vue'
117+
118+
customElements.define('i18n-host', I18nHostElement)
119+
customElements.define('hello-i18n', HelloI18nElement)
120+
121+
createApp(App).mount('#app')
122+
```
123+
124+
Vue アプリケーションのエントリポイントとなる `App.vue` では以下のように template することで動作します:
125+
126+
```html
127+
<template>
128+
<i18n-host>
129+
<h1>Vue I18n in Web component</h1>
130+
<hello-i18n />
131+
</i18n-host>
132+
</template>
133+
```
134+
135+
ここまで説明した完全なexampleは、[こちら](https://github.com/intlify/vue-i18n-next/tree/master/examples/web-components)にあります。
136+
137+
## 制限事項
138+
1. Web Components を実装する際に利用できる Vue I18n は **Composition API のみ**です。
139+
2. Web Components を実装する際に `useI18n` で実装された **Vue コンポーネントをimportして一緒に使用することはできません。**これは、Vue.jsの Web Components 向けの [Provide / Inject](https://v3.vuejs.org/guide/web-components.html#definecustomelement) の制限によるためです。
140+
141+

examples/web-components/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# web-components
2+
3+
This is an example of how to use vue-i18n with web-components.

examples/web-components/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite App</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "web-components",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vue-tsc --noEmit && vite build",
8+
"watch": "vite build --watch --minify false",
9+
"serve": "vite preview"
10+
},
11+
"dependencies": {
12+
"vue": "^3.2.4",
13+
"vue-i18n": "link:../packages/vue-i18n"
14+
},
15+
"devDependencies": {
16+
"@vitejs/plugin-vue": "^1.4.0",
17+
"@vue/compiler-sfc": "^3.2.4",
18+
"@intlify/vite-plugin-vue-i18n": "^2.4.0",
19+
"typescript": "^4.3.2",
20+
"vite": "^2.5.0",
21+
"vue-tsc": "^0.3.0"
22+
}
23+
}
4.19 KB
Binary file not shown.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<form>
3+
<label for="locale-select">select language: </label>
4+
<select id="locale-select" v-model="locale">
5+
<option value="en">en</option>
6+
<option value="ja">ja</option>
7+
</select>
8+
</form>
9+
<i18n-host .locale="locale">
10+
<h1>Vue I18n in Web component</h1>
11+
<hello-i18n />
12+
<hello-block />
13+
</i18n-host>
14+
</template>
15+
16+
<script setup lang="ts">
17+
import { ref } from 'vue'
18+
19+
const locale = ref<string>('en')
20+
</script>
21+
22+
<style>
23+
#app {
24+
font-family: Avenir, Helvetica, Arial, sans-serif;
25+
-webkit-font-smoothing: antialiased;
26+
-moz-osx-font-smoothing: grayscale;
27+
text-align: center;
28+
color: #2c3e50;
29+
margin-top: 60px;
30+
}
31+
</style>

examples/web-components/src/assets/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)