Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added ce child component styles #284

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
34 changes: 31 additions & 3 deletions packages/plugin-vue/src/index.ts
Expand Up @@ -25,7 +25,7 @@ import { handleHotUpdate, handleTypeDepChange } from './handleHotUpdate'
import { transformTemplateAsModule } from './template'
import { transformStyle } from './style'
import { EXPORT_HELPER_ID, helperCode } from './helper'

import { VIRTUAL_CE, VIRTUAL_CE_MODULE } from './virtualCE'
export { parseVueRequest } from './utils/query'
export type { VueQuery } from './utils/query'

Expand Down Expand Up @@ -140,6 +140,9 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
: createFilter(options.value.customElement),
)

// Record the child components of custom element
const ceChildRecord = new Map<string, string>()

return {
name: 'vite:vue',

Expand Down Expand Up @@ -216,6 +219,12 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
},

async resolveId(id) {
// Virtual module that handles
// child components of custom element
if (id.startsWith(VIRTUAL_CE)) {
return `\0${id}`
}

// component export helper
if (id === EXPORT_HELPER_ID) {
return id
Expand All @@ -228,6 +237,15 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {

load(id, opt) {
const ssr = opt?.ssr === true

// Returns the virtual module content of
// the custom element's child component
if (id.startsWith(VIRTUAL_CE_MODULE)) {
const path = ceChildRecord.get(id)
if (!path) return
return fs.readFileSync(path, 'utf-8')
}

if (id === EXPORT_HELPER_ID) {
return helperCode
}
Expand Down Expand Up @@ -261,9 +279,16 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
},

transform(code, id, opt) {
let finalId = id
const ssr = opt?.ssr === true
const { filename, query } = parseVueRequest(id)

// The virtual module of the custom element's child component
// is converted into a legal SFC module id.
if (id.startsWith(VIRTUAL_CE_MODULE)) {
finalId = ceChildRecord.get(id)!
}

const { filename, query } = parseVueRequest(finalId)
if (query.raw || query.url) {
return
}
Expand All @@ -273,14 +298,17 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
}

if (!query.vue) {
const isCustomElement =
customElementFilter.value(filename) || ceChildRecord.has(id)
// main request
return transformMain(
code,
filename,
options.value,
this,
ssr,
customElementFilter.value(filename),
isCustomElement,
ceChildRecord,
)
} else {
// sub block request
Expand Down
47 changes: 47 additions & 0 deletions packages/plugin-vue/src/main.ts
Expand Up @@ -8,6 +8,8 @@
import type { EncodedSourceMap as GenEncodedSourceMap } from '@jridgewell/gen-mapping'
import { addMapping, fromMap, toEncodedMap } from '@jridgewell/gen-mapping'
import { normalizePath, transformWithEsbuild } from 'vite'
// eslint-disable-next-line node/no-extraneous-import

Check failure on line 11 in packages/plugin-vue/src/main.ts

View workflow job for this annotation

GitHub Actions / Lint: node-LTS, ubuntu-latest

Definition for rule 'node/no-extraneous-import' was not found
import type { ImportDeclaration, StringLiteral } from '@babel/types'

Check failure on line 12 in packages/plugin-vue/src/main.ts

View workflow job for this annotation

GitHub Actions / Lint: node-LTS, ubuntu-latest

"@babel/types" is extraneous
import {
createDescriptor,
getDescriptor,
Expand All @@ -34,6 +36,7 @@
pluginContext: TransformPluginContext,
ssr: boolean,
customElement: boolean,
ceChildRecord: Map<string, string>,
) {
const { devServer, isProduction, devToolsEnabled } = options

Expand Down Expand Up @@ -75,6 +78,8 @@
pluginContext,
ssr,
customElement,
ceChildRecord,
filename,
)

// template
Expand Down Expand Up @@ -327,6 +332,8 @@
pluginContext: PluginContext,
ssr: boolean,
customElement: boolean,
ceChildRecord: Map<string, string>,
filename: string,
): Promise<{
code: string
map: RawSourceMap | undefined
Expand Down Expand Up @@ -372,6 +379,16 @@
`import _sfc_main from ${request}\n` + `export * from ${request}` // support named exports
}
}

if (customElement) {
scriptCode = transformCEImportSFC(
scriptCode,
options,
ceChildRecord,
filename,
)
}

return {
code: scriptCode,
map,
Expand Down Expand Up @@ -544,3 +561,33 @@
}
return query
}

function transformCEImportSFC(
code: string,
options: ResolvedOptions,
ceChildRecord: Map<string, string>,
filename: string,
) {
const s = new options.compiler.MagicString(code)
const ast = options.compiler.babelParse(code, {
sourceType: 'module',
plugins: ['typescript'],
})
options.compiler.walk(ast, {
enter(node: StringLiteral, parent: ImportDeclaration) {
if (
node.type === 'StringLiteral' &&
parent.type === 'ImportDeclaration' &&
/\.vue$/.test(node.value)
) {
const virtualModule = `virtual:ce${node.value}`
const pathSFC = normalizePath(
path.resolve(path.dirname(filename), node.value),
)
ceChildRecord.set(`\0${virtualModule}`, pathSFC)
s.overwrite(node.start!, node.end!, `"${virtualModule}"`)
}
},
})
return s.toString()
}
3 changes: 3 additions & 0 deletions packages/plugin-vue/src/virtualCE.ts
@@ -0,0 +1,3 @@
export const VIRTUAL_CE = 'virtual:ce'

export const VIRTUAL_CE_MODULE = '\0virtual:ce'
4 changes: 3 additions & 1 deletion playground/vue/CustomElement.ce.vue
Expand Up @@ -3,11 +3,12 @@
<button class="custom-element" type="button" @click="state.count++">
{{ label }}: {{ state.count }}
</button>
<Child>{{ childStyles }}</Child>
</template>

<script setup>
import { reactive, onBeforeMount } from 'vue'

import Child from './CustomElementChild.vue'
defineProps({
label: String,
})
Expand All @@ -17,6 +18,7 @@ const state = reactive({ count: 0 })
onBeforeMount(() => {
state.count = 1
})
const childStyles = Child.styles
</script>

<style scoped>
Expand Down
25 changes: 25 additions & 0 deletions playground/vue/CustomElementChild.vue
@@ -0,0 +1,25 @@
<template>
<h3>Custom Element Child</h3>
<button class="custom-element-c" type="button" @click="state.count++">
count: {{ state.count }} <slot></slot>
</button>
</template>

<script setup>
import { reactive, onBeforeMount } from 'vue'
defineProps({
label: String,
})

const state = reactive({ count: 0 })

onBeforeMount(() => {
state.count = 1
})
</script>

<style lang="scss">
.custom-element-c {
color: red;
}
</style>
8 changes: 8 additions & 0 deletions playground/vue/__tests__/vue.spec.ts
Expand Up @@ -284,6 +284,14 @@ describe('custom element', () => {
expect(await page.textContent('.custom-element')).toMatch('count: 2')
expect(await getColor('.custom-element')).toBe('green')
})

test('child component should have styles', async () => {
await page.click('.custom-element-c')
expect(await page.textContent('.custom-element-c')).toMatch('count: 2')
expect(await page.textContent('.custom-element-c')).toMatch(
'[\n' + ' ".custom-element-c {\\n color: red;\\n}"\n' + ']',
)
})
})

describe('setup import template', () => {
Expand Down