Skip to content

Commit 1ba31c4

Browse files
committed
feat(plugin-vue): support importing vue files as custom elements
1 parent e6272fe commit 1ba31c4

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

packages/plugin-vue/README.md

+30
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ export interface Options {
2121
ssr?: boolean
2222
isProduction?: boolean
2323

24+
/**
25+
* Transform Vue SFCs into custom elements (requires Vue >= 3.2.0)
26+
* - `true` -> all `*.vue` imports are converted into custom elements
27+
* - `string | RegExp` -> matched files are converted into custom elements
28+
*
29+
* @default /\.ce\.vue$/
30+
*/
31+
customElement?: boolean | string | RegExp | (string | RegExp)[]
32+
2433
// options to pass on to @vue/compiler-sfc
2534
script?: Partial<SFCScriptCompileOptions>
2635
template?: Partial<SFCTemplateCompileOptions>
@@ -71,6 +80,27 @@ export default {
7180
}
7281
```
7382

83+
## Using Vue SFCs as Custom Elements
84+
85+
> Requires `vue@^3.2.0`
86+
87+
By default, files ending in `*.ce.vue` will be processed as native Custom Elements when imported (created with `defineCustomElement` from Vue core):
88+
89+
```js
90+
import Example from './Example.ce.vue'
91+
92+
// register
93+
customElements.define('my-example', Example)
94+
95+
// can also be instantiated
96+
const myExample = new Example()
97+
```
98+
99+
The `customElement` plugin option can be used to configure the behavior:
100+
101+
- `{ customElement: true }` will import all `*.vue` files as Custom Elements.
102+
- Use a string or regex pattern to change how files should be loaded as Custom Elements (this check is applied after `include` and `exclude` matches).
103+
74104
## License
75105

76106
MIT

packages/plugin-vue/src/index.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ export interface Options {
4343
script?: Partial<SFCScriptCompileOptions>
4444
template?: Partial<SFCTemplateCompileOptions>
4545
style?: Partial<SFCStyleCompileOptions>
46+
47+
/**
48+
* Transform Vue SFCs into custom elements (requires Vue >= 3.2.0)
49+
* - `true` -> all `*.vue` imports are converted into custom elements
50+
* - `string | RegExp` -> matched files are converted into custom elements
51+
*
52+
* @default /\.ce\.vue$/
53+
*/
54+
customElement?: boolean | string | RegExp | (string | RegExp)[]
55+
4656
/**
4757
* @deprecated the plugin now auto-detects whether it's being invoked for ssr.
4858
*/
@@ -66,6 +76,11 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
6676
rawOptions.exclude
6777
)
6878

79+
const customElementFilter =
80+
rawOptions.customElement === true
81+
? () => true
82+
: createFilter(rawOptions.customElement || /\.ce\.vue$/)
83+
6984
return {
7085
name: 'vite:vue',
7186

@@ -144,7 +159,14 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
144159

145160
if (!query.vue) {
146161
// main request
147-
return transformMain(code, filename, options, this, ssr)
162+
return transformMain(
163+
code,
164+
filename,
165+
options,
166+
this,
167+
ssr,
168+
customElementFilter(filename)
169+
)
148170
} else {
149171
// sub block request
150172
const descriptor = getDescriptor(filename)!

packages/plugin-vue/src/main.ts

+34-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ import { transformTemplateInMain } from './template'
1414
import { isOnlyTemplateChanged, isEqualBlock } from './handleHotUpdate'
1515
import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'
1616
import { createRollupError } from './utils/error'
17+
import { transformStyle } from './style'
1718

1819
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
1920
export async function transformMain(
2021
code: string,
2122
filename: string,
2223
options: ResolvedOptions,
2324
pluginContext: TransformPluginContext,
24-
ssr: boolean
25+
ssr: boolean,
26+
asCustomElement: boolean
2527
) {
2628
const { root, devServer, isProduction } = options
2729

@@ -92,7 +94,9 @@ export async function transformMain(
9294
}
9395

9496
// styles
95-
const stylesCode = await genStyleCode(descriptor, pluginContext)
97+
const stylesCode = asCustomElement
98+
? await genCustomElementStyleCode(descriptor, options, pluginContext)
99+
: await genStyleCode(descriptor, pluginContext)
96100

97101
// custom blocks
98102
const customBlocksCode = await genCustomBlockCode(descriptor, pluginContext)
@@ -113,7 +117,6 @@ export async function transformMain(
113117
// expose filename during serve for devtools to pickup
114118
output.push(`_sfc_main.__file = ${JSON.stringify(filename)}`)
115119
}
116-
output.push('export default _sfc_main')
117120

118121
// HMR
119122
if (
@@ -132,7 +135,9 @@ export async function transformMain(
132135
output.push(`export const _rerender_only = true`)
133136
}
134137
output.push(
135-
`import.meta.hot.accept(({ default: updated, _rerender_only }) => {`,
138+
`import.meta.hot.accept(({ default: ${
139+
asCustomElement ? `{ def: updated }` : `updated`
140+
}, _rerender_only }) => {`,
136141
` if (_rerender_only) {`,
137142
` __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)`,
138143
` } else {`,
@@ -185,6 +190,15 @@ export async function transformMain(
185190
resolvedMap.sourcesContent = templateMap.sourcesContent
186191
}
187192

193+
if (asCustomElement) {
194+
output.push(
195+
`import { defineCustomElement as __ce } from 'vue'`,
196+
`export default __ce(_sfc_main)`
197+
)
198+
} else {
199+
output.push(`export default _sfc_main`)
200+
}
201+
188202
return {
189203
code: output.join('\n'),
190204
map: resolvedMap || {
@@ -397,3 +411,19 @@ function attrsToQuery(
397411
}
398412
return query
399413
}
414+
415+
async function genCustomElementStyleCode(
416+
descriptor: SFCDescriptor,
417+
options: ResolvedOptions,
418+
pluginContext: TransformPluginContext
419+
) {
420+
const styles = (
421+
await Promise.all(
422+
descriptor.styles.map((style, index) =>
423+
transformStyle(style.content, descriptor, index, options, pluginContext)
424+
)
425+
)
426+
).map((res) => JSON.stringify(res!.code))
427+
428+
return `_sfc_main.styles = [${styles.join(',')}]`
429+
}

0 commit comments

Comments
 (0)