Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 441ad70

Browse files
authored
feat: unplugin-vue-ce support ES module import css (#120)
* feat: unplugin-vue-ce support ES module import css * docs: updated @unplugin-vue-ce/sub-style es module import css document
1 parent 4d4f8ca commit 441ad70

File tree

12 files changed

+437
-19
lines changed

12 files changed

+437
-19
lines changed

packages/core/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { unVueCEVModel } from '@unplugin-vue-ce/v-model'
2-
import { unVueCESubStyle } from '@unplugin-vue-ce/sub-style'
2+
import { type UNVueCESubStyleOption, unVueCESubStyle } from '@unplugin-vue-ce/sub-style'
33
import { createUnplugin } from 'unplugin'
4-
const unplugin = createUnplugin(() => {
4+
5+
const unplugin = createUnplugin<UNVueCESubStyleOption>((options: UNVueCESubStyleOption = {}) => {
56
return [
67
unVueCEVModel(),
7-
...unVueCESubStyle(),
8+
...unVueCESubStyle(options),
89
// unVueCEShadow(),
910
]
1011
})

packages/sub-style/README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,53 @@ build({
105105
```
106106
</details>
107107

108+
## ES module import css( experimental )
109+
via: https://github.com/unplugin/unplugin-vue-ce/issues/118
110+
`@unplugin-vue-ce/sub-style` Starting from version "1.0.0-beta.19", a new option `isESCSS` is added, which is turned off by default.
111+
This is an experimental feature.
112+
```ts
113+
// vite.config.ts
114+
import { defineConfig } from 'vite'
115+
import { viteVueCESubStyle } from '@unplugin-vue-ce/sub-style'
116+
import vue from '@vitejs/plugin-vue'
117+
import type { PluginOption } from 'vite'
118+
export default defineConfig({
119+
plugins: [
120+
vue(),
121+
viteVueCESubStyle({
122+
isESCSS: true
123+
}) as PluginOption,
124+
],
125+
})
126+
```
127+
When `isESCSS` is turned on,`@unplugin-vue-ce/sub-style` will automatically move the css import part of the script block to the style block,
128+
so that vue-plugin can compile its style. If you do not do this , it will be injected into the head of the document as a global style.
129+
```vue
130+
<template>
131+
<div>
132+
foo
133+
</div>
134+
</template>
135+
<script setup>
136+
import './test.css'
137+
</script>
138+
```
139+
transform result
140+
141+
```vue
142+
<template>
143+
<div>
144+
foo
145+
</div>
146+
</template>
147+
<script setup>
148+
149+
</script>
150+
<style lang="css">
151+
@import './test.css';
152+
</style>
153+
```
154+
108155
## About Tailwind CSS
109156
Since vue enables shadow dom by default,
110157
it will isolate the style,
@@ -126,7 +173,7 @@ or (only vite)
126173
```
127174

128175
## About Uno CSS
129-
Only postcss plugins are supported (See: https://unocss.dev/integrations/postcss#install),
176+
Only postcss plugins are supported (via: https://unocss.dev/integrations/postcss#install),
130177
you need to add the root component of each web component to add the reference style:
131178

132179
```html

packages/sub-style/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import { createUnplugin } from 'unplugin'
2-
import { normalizePath, setGlobalPrefix } from 'baiwusanyu-utils'
2+
import { extend, normalizePath, setGlobalPrefix } from 'baiwusanyu-utils'
33
import MagicString from 'magic-string'
44
import { NAME } from '@unplugin-vue-ce/utils'
55
import { injectVueRuntime } from './src/inject/inject-vue-runtime'
66
import { atomicCSSPreset, virtualTailwind, virtualUno } from './src/atomic-css'
7+
import { parseESCSS } from './src/parse'
8+
import { transformESCSS } from './src/transform'
9+
import { injectStyleESCSS } from './src/inject/inject-style-escss'
10+
import type { SFCParseOptions } from '@vue/compiler-sfc'
11+
export interface UNVueCESubStyleOption {
12+
isESCSS?: boolean
13+
sfcParseOptions?: SFCParseOptions
14+
}
15+
16+
export const unVueCESubStyle = (options: UNVueCESubStyleOption): any => {
17+
const defaultOptions = {
18+
isESCSS: false,
19+
sfcParseOptions: {},
20+
}
21+
22+
const resolvedOptions = extend(defaultOptions, options)
723

8-
export const unVueCESubStyle = (): any => {
924
setGlobalPrefix(`[${NAME}]:`)
1025
// just vite
1126
return [
@@ -35,6 +50,13 @@ export const unVueCESubStyle = (): any => {
3550
if (id.endsWith('.vue') && code.includes(virtualUno))
3651
mgcStr.prependRight(mgcStr.length(), atomicCSSPreset[virtualUno])
3752

53+
// esm import css transform to style tag
54+
if (id.endsWith('.vue') && resolvedOptions.isESCSS) {
55+
const parseRes = parseESCSS(mgcStr.toString(), resolvedOptions.sfcParseOptions)
56+
transformESCSS(mgcStr, parseRes)
57+
injectStyleESCSS(mgcStr, parseRes)
58+
}
59+
3860
return {
3961
code: mgcStr.toString(),
4062
get map() {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { MagicStringBase } from 'magic-string-ast'
2+
import type { ESCSSImport, ESCSSParseRes, IESCSSValuePos, ILoc } from '../parse'
3+
4+
export function injectStyleESCSS(
5+
mgcStr: MagicStringBase,
6+
parseRes: ESCSSParseRes,
7+
) {
8+
if (parseRes.scriptSetup) {
9+
const { loc, cssImport } = parseRes.scriptSetup
10+
genStyleTagCode(mgcStr, loc, cssImport)
11+
}
12+
13+
if (parseRes.script) {
14+
const { loc, cssImport } = parseRes.script
15+
genStyleTagCode(mgcStr, loc, cssImport)
16+
}
17+
}
18+
19+
function genStyleTagCode(
20+
mgcStr: MagicStringBase,
21+
loc: ILoc,
22+
cssImport: ESCSSImport,
23+
) {
24+
if (loc && cssImport) {
25+
for (const [key, css] of Object.entries(cssImport)) {
26+
css.forEach((v: IESCSSValuePos) => {
27+
const offset = mgcStr.original.length - mgcStr.length()
28+
mgcStr.appendRight(mgcStr.length() + offset, `<style lang="${key}"> @import "${v.value}";</style>\n
29+
`)
30+
})
31+
}
32+
}
33+
}

packages/sub-style/src/parse/index.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { parse } from '@vue/compiler-sfc'
2+
import { log, setGlobalPrefix } from 'baiwusanyu-utils'
3+
import { parse as babelParse } from '@babel/parser'
4+
import { walk } from 'estree-walker-ts'
5+
import type { SFCParseOptions } from '@vue/compiler-sfc'
6+
import type { ImportDeclaration, StringLiteral } from '@babel/types'
7+
8+
interface ILocPos {
9+
column: number
10+
line: number
11+
offset: number
12+
}
13+
export interface ILoc {
14+
source: string
15+
start: ILocPos
16+
end: ILocPos
17+
}
18+
19+
export interface IESCSSValuePos {
20+
value: string
21+
start: number
22+
end: number
23+
}
24+
25+
export interface ESCSSImport {
26+
css: IESCSSValuePos[]
27+
sass: IESCSSValuePos[]
28+
less: IESCSSValuePos[]
29+
scss: IESCSSValuePos[]
30+
}
31+
export interface ESCSSParseRes {
32+
script: {
33+
loc?: ILoc // 替换 sfc 源码
34+
cssImport: ESCSSImport
35+
}
36+
scriptSetup: {
37+
loc?: ILoc // 替换 sfc 源码
38+
cssImport: ESCSSImport
39+
}
40+
}
41+
export function parseESCSS(
42+
code: string,
43+
options: SFCParseOptions) {
44+
const parseSFCRes = parse(code, options)
45+
if (parseSFCRes.errors) {
46+
setGlobalPrefix('[unplugin-vue-ce]:')
47+
parseSFCRes.errors.forEach((error) => {
48+
log('error', error.message)
49+
})
50+
}
51+
52+
const { descriptor } = parseSFCRes
53+
const parseRes: ESCSSParseRes = {
54+
script: {
55+
cssImport: {
56+
css: [],
57+
sass: [],
58+
less: [],
59+
scss: [],
60+
},
61+
},
62+
scriptSetup: {
63+
cssImport: {
64+
css: [],
65+
sass: [],
66+
less: [],
67+
scss: [],
68+
},
69+
},
70+
}
71+
if (descriptor.scriptSetup) {
72+
const { loc } = descriptor.scriptSetup
73+
parseRes.scriptSetup.loc = loc
74+
parseScript(loc.source, parseRes, 'scriptSetup')
75+
}
76+
if (descriptor.script) {
77+
const { loc } = descriptor.script
78+
parseRes.script.loc = loc
79+
parseScript(loc.source, parseRes, 'script')
80+
}
81+
return parseRes
82+
}
83+
84+
function parseScript(
85+
code: string,
86+
parseRes: ESCSSParseRes,
87+
type: 'scriptSetup' | 'script',
88+
) {
89+
const ast = babelParse(code, {
90+
sourceType: 'module',
91+
plugins: ['typescript'],
92+
})
93+
94+
const regx = /\.(css|sass|less|scss)$/
95+
;(walk as any)(ast, {
96+
enter(
97+
node: ImportDeclaration | StringLiteral,
98+
parent: ImportDeclaration | StringLiteral,
99+
) {
100+
if (parent && node.type === 'StringLiteral'
101+
&& parent.type === 'ImportDeclaration') {
102+
const matched = node.value.match(regx)
103+
if (matched) {
104+
const key = matched[1] as 'css' | 'sass' | 'less' | 'scss'
105+
parseRes[type].cssImport[key].push({
106+
value: node.value,
107+
start: parent.start!,
108+
end: parent.end!,
109+
})
110+
}
111+
}
112+
},
113+
})
114+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { ESCSSImport, ESCSSParseRes, IESCSSValuePos, ILoc } from '../parse'
2+
import type { MagicStringBase } from 'magic-string-ast'
3+
4+
export function transformESCSS(
5+
mgcStr: MagicStringBase,
6+
parseRes: ESCSSParseRes,
7+
) {
8+
if (parseRes.scriptSetup) {
9+
const { loc, cssImport } = parseRes.scriptSetup
10+
overwriteCode(mgcStr, loc, cssImport)
11+
}
12+
13+
if (parseRes.script) {
14+
const { loc, cssImport } = parseRes.script
15+
overwriteCode(mgcStr, loc, cssImport)
16+
}
17+
}
18+
19+
function overwriteCode(
20+
mgcStr: MagicStringBase,
21+
loc: ILoc,
22+
cssImport: ESCSSImport,
23+
) {
24+
if (loc && cssImport) {
25+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
26+
for (const [_, css] of Object.entries(cssImport)) {
27+
css.forEach((v: IESCSSValuePos) => {
28+
mgcStr.update(v.start + loc.start.offset, v.end + loc.start.offset, '')
29+
})
30+
}
31+
}
32+
}

play/sub-style/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"fs-extra": "^11.1.1",
1919
"magic-string": "^0.30.5",
2020
"unplugin": "^1.5.0",
21+
"vite-plugin-inspect": "latest",
2122
"vue": "^3.3.6"
2223
}
2324
}

play/sub-style/src/bwsy-ce-foo.ce.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@
88
</template>
99

1010
<script setup>
11+
import { onMounted } from 'vue'
1112
import BwsyBar from './bwsy-bar.vue'
13+
import './test.css'
14+
// onMounted(() => {
15+
// debugger
16+
// })
17+
</script>
18+
19+
<script>
20+
1221
</script>
1322

1423
<style scoped>

play/sub-style/src/main.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
defineCustomElement,
44
} from '../patches/vue.esm-browser.js'; */
55

6-
import { defineCustomElement } from 'vue'
6+
import { createApp, defineCustomElement } from 'vue'
77

8-
/* import App from './App.vue';
9-
import Foo from './bwsy-ce-foo.ce.vue';
10-
const app = createApp(App);
11-
customElements.define('bwsy-ce-foo', defineCustomElement(Foo));
12-
/!*app.config.compilerOptions.isCustomElement = (tag=>{
13-
return tag === 'bwsy-ce-foo'
14-
})*!/
15-
app.mount('#app'); */
8+
import App from './App.vue'
9+
import Foo from './bwsy-ce-foo.ce.vue'
10+
const app = createApp(App)
11+
customElements.define('bwsy-ce-foo', defineCustomElement(Foo))
12+
app.config.compilerOptions.isCustomElement = (tag) => {
13+
return tag === 'bwsy-ce-foo'
14+
}
15+
app.mount('#app')
1616

1717
/* import App from './nested/vue-app.ce.vue'
1818
const app = createApp(App);
@@ -50,6 +50,6 @@ const ceApp = defineCustomElement(App)
5050
customElements.define('vue-app', ceApp)
5151
*/
5252

53-
import App from './edison/A.ce.vue'
53+
/* import App from './edison/A.ce.vue'
5454
const ceApp = defineCustomElement(App)
55-
customElements.define('vue-app', ceApp)
55+
customElements.define('vue-app', ceApp) */

play/sub-style/src/test.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
p {
2+
background-color: #ffcd94;
3+
}
4+
body {
5+
background-color: aliceblue;
6+
}

play/sub-style/vite.config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { defineConfig } from 'vite'
22
import vue from '@vitejs/plugin-vue'
3+
import Inspect from 'vite-plugin-inspect'
34
import { viteVueCE } from '../../dist'
45
// https://vitejs.dev/config/
56
export default defineConfig({
67
plugins: [
78
vue({
89
customElement: true,
910
}),
10-
viteVueCE(),
11+
viteVueCE({
12+
isESCSS: true,
13+
}),
14+
Inspect(),
1115
],
1216
build: {
1317
minify: false,

0 commit comments

Comments
 (0)