Skip to content

Commit

Permalink
feat: support web components (#641)
Browse files Browse the repository at this point in the history
closes #628
  • Loading branch information
kazupon committed Aug 23, 2021
1 parent 556e6fd commit 24b6d60
Show file tree
Hide file tree
Showing 27 changed files with 1,971 additions and 74 deletions.
8 changes: 8 additions & 0 deletions docs/.vitepress/config.js
Expand Up @@ -184,6 +184,10 @@ const config = {
text: 'TypeScript Support',
link: '/guide/advanced/typescript',
},
{
text: 'Web components',
link: '/guide/advanced/wc',
},
{
text: 'Optimization',
link: '/guide/advanced/optimization',
Expand Down Expand Up @@ -355,6 +359,10 @@ const config = {
text: 'TypeScript Support',
link: '/ja/guide/advanced/typescript',
},
{
text: 'Web components',
link: '/ja/guide/advanced/wc',
},
{
text: 'Optimization',
link: '/ja/guide/advanced/optimization',
Expand Down
140 changes: 140 additions & 0 deletions docs/guide/advanced/wc.md
@@ -0,0 +1,140 @@
# Web components

:::tip Support Version
:new: 9.2+
:::

Vue 3.2 later, we can use the WebComponents as described in the [official documentation](https://v3.vuejs.org/guide/web-components.html).

This will support the use of Vue I18n in Web Components starting with Vue I18n v9.2.

There are a few things to keep in mind when using Vue I18n with Web Components.

## Make preparetion for Web Components to host the I18n instance

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.

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`.

Web Components that host the i18n instance:
```html
<script lang="ts">
import { defineComponent, provide } from 'vue'
import { createI18n, I18nInjectionKey } from 'vue-i18n'
/**
* create an i18n instance to host for other web components
*/
const i18n = createI18n<false>({
legacy: false, // must set to `false`
locale: 'en',
messages: {
en: {
hello: 'Hello!'
},
ja: {
hello: 'こんにちは!'
}
}
})
export default defineComponent({
// ...
setup(props) {
/**
* provide i18n instance with `I18nInjectionKey` for other web components
*/
provide(I18nInjectionKey, i18n)
// ...
return {}
}
})
</script>

<!-- template to slot the content -->
<template>
<slot />
</template>
```

The above code has the following three points.

- Call `createI18n` to create an i18n instance
- In `setup`, specify the i18n instance created with `createI18n` along with `I18nInjectionKey` in `provide`
- template has `slot` element only

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.

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.

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`.

Then, to host other Web Components, the `template` block makes it possible by using the `slot` element.

Export this hosted Web Components as follows:

```javascript
import { defineCustomElement } from 'vue'
import I18nHost from './components/I18nHost.ce.vue'

const I18nHostElement = defineCustomElement(I18nHost)

export { I18nHostElement }
```

The following `useI18n` implements and exports Web Components to:

```html
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>

<template>
<p>{{ t('hello') }}</p>
</template>
```

```javascript
import { defineCustomElement } from 'vue'
import HelloI18n from './components/HelloI18n.ce.vue'

const HelloI18nElement = defineCustomElement(HelloI18n)
export { HelloI18nElement }
```

When the following Vue application is registered as a custom element of Web Components:

```javascript
import { createApp } from 'vue'
import { I18nHostElement } from './paht/to/I18nHostElement'
import { HelloI18nElement } from './paht/to/HelloI18nElement'
import App from './App.vue'

customElements.define('i18n-host', I18nHostElement)
customElements.define('hello-i18n', HelloI18nElement)

createApp(App).mount('#app')
```

So, In `App.vue`, which is the entry point of a Vue application, the following template will work:

```html
<template>
<i18n-host>
<h1>Vue I18n in Web component</h1>
<hello-i18n />
</i18n-host>
</template>
```

The complete example described so far can be looked [here](https://github.com/intlify/vue-i18n-next/tree/master/examples/web-components).

## Limitations
1. The Vue I18n that can be used to implement Web Components is only **Composition API**.
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.


141 changes: 141 additions & 0 deletions docs/ja/guide/advanced/wc.md
@@ -0,0 +1,141 @@
# Web components

:::tip Support Version
:new: 9.2+
:::

Vue 3.2 以降から、[公式ドキュメント](https://v3.vuejs.org/guide/web-components.html)に記載されているとおり、Vue.js で Web components を利用できるようになりました。

それに伴い、Vue I18n v9.2 から Web Components で Vue I18n を使うことができるようサポートとしています。

Vue I18n を Web Components で利用するにあたって、いくつか注意点があります。


## I18n インスタンスをホストする Web Components を用意する

Vue 3.2 からサポートされた `defineCustomElement` を使って、SFC で実装した Vue コンポーネントを Web Components として提供することができます。つまり、`useI18n` を使って実装された Vue コンポーネントは、i18n がサポートされた Web Components として提供できることを意味します。

しかしながら、その提供された Web Components をそのまま HTML に挿入して使うことはできません。`createI18n` で生成された i18n インスタンスをホストする以下のような Web Components を用意する必要があります。

i18n インスタンスをホストする Web Components:
```html
<script lang="ts">
import { defineComponent, provide } from 'vue'
import { createI18n, I18nInjectionKey } from 'vue-i18n'
/**
* create an i18n instance to host for other web components
*/
const i18n = createI18n<false>({
legacy: false, // must set to `false`
locale: 'en',
messages: {
en: {
hello: 'Hello!'
},
ja: {
hello: 'こんにちは!'
}
}
})
export default defineComponent({
// ...
setup(props) {
/**
* provide i18n instance with `I18nInjectionKey` for other web components
*/
provide(I18nInjectionKey, i18n)
// ...
return {}
}
})
</script>

<!-- template to slot the content -->
<template>
<slot />
</template>
```

上記コードのポイントは以下の3つです。

- `createI18n` を呼び出して、i18n インスタンスを生成
- `setup` で、`createI18n` で生成した i18n インスタンスを `provide``I18nInjectionKey` といっしょに指定する
- template は `slot` 要素のみ

`script` ブロックでは、まず `createI18n` を使って i18n インスタンスを生成しています。`ceateI18n` は Vue I18n を使う上で、最初にセットアップが必要な関数です。Vue アプリケーションにおいては、`createI18n` で生成された i18n インスタンスを、`createApp` で生成された Vue アプリケーション `app.use` に i18n インスタンスを指定することで、Vue I18n を Vue プラグインとして Vue アプリケーションにインストールする必要がありました。

Vue コンポーネントに対して `defineCustomElement` 使うと、その Vue コンポーネントはもはやVue アプリケーション側から制御できなくなってしまうため、例え Vue アプリケーション上で、その Web Components 化されたものを動かしたとしても、Vue アプリケーション側から `createI18n` で生成された i18n インスタンスを `app.use` 経由で対象先の Web Components にアタッチできません。

そのため、Web Components に i18n インスタンスをアタッチするために、`setup` 内で `provide` を使って i18n インスタンスを他の Web Components に公開しています。これにより、`useI18n` で i18n が実装されている Web Components が、`provide` が実行されている Web Components にホストされることで、動作するようになります。

そして、他の Web Components をホストするために、`template` ブロックでは、`slot` 要素を使うことで可能にしています。

このホストする Web Components を以下のようにexportsし:

```javascript
import { defineCustomElement } from 'vue'
import I18nHost from './components/I18nHost.ce.vue'

const I18nHostElement = defineCustomElement(I18nHost)

export { I18nHostElement }
```

以下のような `useI18n` が実装、そして exportされた Web Components を:

```html
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>

<template>
<p>{{ t('hello') }}</p>
</template>
```

```javascript
import { defineCustomElement } from 'vue'
import HelloI18n from './components/HelloI18n.ce.vue'

const HelloI18nElement = defineCustomElement(HelloI18n)
export { HelloI18nElement }
```

以下のような Vue アプリケーションで Web Components のカスタム要素として登録した場合:

```javascript
import { createApp } from 'vue'
import { I18nHostElement } from './paht/to/I18nHostElement'
import { HelloI18nElement } from './paht/to/HelloI18nElement'
import App from './App.vue'

customElements.define('i18n-host', I18nHostElement)
customElements.define('hello-i18n', HelloI18nElement)

createApp(App).mount('#app')
```

Vue アプリケーションのエントリポイントとなる `App.vue` では以下のように template することで動作します:

```html
<template>
<i18n-host>
<h1>Vue I18n in Web component</h1>
<hello-i18n />
</i18n-host>
</template>
```

ここまで説明した完全なexampleは、[こちら](https://github.com/intlify/vue-i18n-next/tree/master/examples/web-components)にあります。

## 制限事項
1. Web Components を実装する際に利用できる Vue I18n は **Composition API のみ**です。
2. Web Components を実装する際に `useI18n` で実装された **Vue コンポーネントをimportして一緒に使用することはできません。**これは、Vue.jsの Web Components 向けの [Provide / Inject](https://v3.vuejs.org/guide/web-components.html#definecustomelement) の制限によるためです。


3 changes: 3 additions & 0 deletions examples/web-components/README.md
@@ -0,0 +1,3 @@
# web-components

This is an example of how to use vue-i18n with web-components.
13 changes: 13 additions & 0 deletions examples/web-components/index.html
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
23 changes: 23 additions & 0 deletions examples/web-components/package.json
@@ -0,0 +1,23 @@
{
"name": "web-components",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"watch": "vite build --watch --minify false",
"serve": "vite preview"
},
"dependencies": {
"vue": "^3.2.4",
"vue-i18n": "link:../packages/vue-i18n"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.4.0",
"@vue/compiler-sfc": "^3.2.4",
"@intlify/vite-plugin-vue-i18n": "^2.4.0",
"typescript": "^4.3.2",
"vite": "^2.5.0",
"vue-tsc": "^0.3.0"
}
}
Binary file added examples/web-components/public/favicon.ico
Binary file not shown.
31 changes: 31 additions & 0 deletions examples/web-components/src/App.vue
@@ -0,0 +1,31 @@
<template>
<form>
<label for="locale-select">select language: </label>
<select id="locale-select" v-model="locale">
<option value="en">en</option>
<option value="ja">ja</option>
</select>
</form>
<i18n-host .locale="locale">
<h1>Vue I18n in Web component</h1>
<hello-i18n />
<hello-block />
</i18n-host>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const locale = ref<string>('en')
</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>
Empty file.

0 comments on commit 24b6d60

Please sign in to comment.