Skip to content

Commit

Permalink
feat(plugin-vue): support importing vue files as custom elements
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jul 22, 2021
1 parent a1f6ac9 commit 3a3af6e
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 5 deletions.
30 changes: 30 additions & 0 deletions packages/plugin-vue/README.md
Expand Up @@ -21,6 +21,15 @@ export interface Options {
ssr?: boolean
isProduction?: boolean

/**
* Transform Vue SFCs into custom elements (requires Vue >= 3.2.0)
* - `true` -> all `*.vue` imports are converted into custom elements
* - `string | RegExp` -> matched files are converted into custom elements
*
* @default /\.ce\.vue$/
*/
customElement?: boolean | string | RegExp | (string | RegExp)[]

// options to pass on to @vue/compiler-sfc
script?: Partial<SFCScriptCompileOptions>
template?: Partial<SFCTemplateCompileOptions>
Expand Down Expand Up @@ -71,6 +80,27 @@ export default {
}
```

## Using Vue SFCs as Custom Elements

> Requires `vue@^3.2.0`
By default, files ending in `*.ce.vue` will be processed as native Custom Elements when imported (created with `defineCustomElement` from Vue core):

```js
import Example from './Example.ce.vue'

// register
customElements.define('my-example', Example)

// can also be instantiated
const myExample = new Example()
```

The `customElement` plugin option can be used to configure the behavior:

- `{ customElement: true }` will import all `*.vue` files as Custom Elements.
- 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).

## License

MIT
24 changes: 23 additions & 1 deletion packages/plugin-vue/src/index.ts
Expand Up @@ -43,6 +43,16 @@ export interface Options {
script?: Partial<SFCScriptCompileOptions>
template?: Partial<SFCTemplateCompileOptions>
style?: Partial<SFCStyleCompileOptions>

/**
* Transform Vue SFCs into custom elements (requires Vue >= 3.2.0)
* - `true` -> all `*.vue` imports are converted into custom elements
* - `string | RegExp` -> matched files are converted into custom elements
*
* @default /\.ce\.vue$/
*/
customElement?: boolean | string | RegExp | (string | RegExp)[]

/**
* @deprecated the plugin now auto-detects whether it's being invoked for ssr.
*/
Expand All @@ -66,6 +76,11 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
rawOptions.exclude
)

const customElementFilter =
rawOptions.customElement === true
? () => true
: createFilter(rawOptions.customElement || /\.ce\.vue$/)

return {
name: 'vite:vue',

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

if (!query.vue) {
// main request
return transformMain(code, filename, options, this, ssr)
return transformMain(
code,
filename,
options,
this,
ssr,
customElementFilter(filename)
)
} else {
// sub block request
const descriptor = getDescriptor(filename)!
Expand Down
38 changes: 34 additions & 4 deletions packages/plugin-vue/src/main.ts
Expand Up @@ -14,14 +14,16 @@ import { transformTemplateInMain } from './template'
import { isOnlyTemplateChanged, isEqualBlock } from './handleHotUpdate'
import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'
import { createRollupError } from './utils/error'
import { transformStyle } from './style'

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export async function transformMain(
code: string,
filename: string,
options: ResolvedOptions,
pluginContext: TransformPluginContext,
ssr: boolean
ssr: boolean,
asCustomElement: boolean
) {
const { root, devServer, isProduction } = options

Expand Down Expand Up @@ -92,7 +94,9 @@ export async function transformMain(
}

// styles
const stylesCode = await genStyleCode(descriptor, pluginContext)
const stylesCode = asCustomElement
? await genCustomElementStyleCode(descriptor, options, pluginContext)
: await genStyleCode(descriptor, pluginContext)

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

// HMR
if (
Expand All @@ -132,7 +135,9 @@ export async function transformMain(
output.push(`export const _rerender_only = true`)
}
output.push(
`import.meta.hot.accept(({ default: updated, _rerender_only }) => {`,
`import.meta.hot.accept(({ default: ${
asCustomElement ? `{ def: updated }` : `updated`
}, _rerender_only }) => {`,
` if (_rerender_only) {`,
` __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)`,
` } else {`,
Expand Down Expand Up @@ -185,6 +190,15 @@ export async function transformMain(
resolvedMap.sourcesContent = templateMap.sourcesContent
}

if (asCustomElement) {
output.push(
`import { defineCustomElement as __ce } from 'vue'`,
`export default __ce(_sfc_main)`
)
} else {
output.push(`export default _sfc_main`)
}

return {
code: output.join('\n'),
map: resolvedMap || {
Expand Down Expand Up @@ -397,3 +411,19 @@ function attrsToQuery(
}
return query
}

async function genCustomElementStyleCode(
descriptor: SFCDescriptor,
options: ResolvedOptions,
pluginContext: TransformPluginContext
) {
const styles = (
await Promise.all(
descriptor.styles.map((style, index) =>
transformStyle(style.content, descriptor, index, options, pluginContext)
)
)
).map((res) => JSON.stringify(res!.code))

return `_sfc_main.styles = [${styles.join(',')}]`
}

0 comments on commit 3a3af6e

Please sign in to comment.