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

Commit

Permalink
feat: unplugin-vue-ce support ES module import css (#120)
Browse files Browse the repository at this point in the history
* feat: unplugin-vue-ce support ES module import css

* docs: updated @unplugin-vue-ce/sub-style es module import css document
  • Loading branch information
baiwusanyu-c authored Dec 7, 2023
1 parent 4d4f8ca commit 441ad70
Show file tree
Hide file tree
Showing 12 changed files with 437 additions and 19 deletions.
7 changes: 4 additions & 3 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { unVueCEVModel } from '@unplugin-vue-ce/v-model'
import { unVueCESubStyle } from '@unplugin-vue-ce/sub-style'
import { type UNVueCESubStyleOption, unVueCESubStyle } from '@unplugin-vue-ce/sub-style'
import { createUnplugin } from 'unplugin'
const unplugin = createUnplugin(() => {

const unplugin = createUnplugin<UNVueCESubStyleOption>((options: UNVueCESubStyleOption = {}) => {
return [
unVueCEVModel(),
...unVueCESubStyle(),
...unVueCESubStyle(options),
// unVueCEShadow(),
]
})
Expand Down
49 changes: 48 additions & 1 deletion packages/sub-style/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,53 @@ build({
```
</details>

## ES module import css( experimental )
via: https://github.com/unplugin/unplugin-vue-ce/issues/118
`@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.
This is an experimental feature.
```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { viteVueCESubStyle } from '@unplugin-vue-ce/sub-style'
import vue from '@vitejs/plugin-vue'
import type { PluginOption } from 'vite'
export default defineConfig({
plugins: [
vue(),
viteVueCESubStyle({
isESCSS: true
}) as PluginOption,
],
})
```
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,
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.
```vue
<template>
<div>
foo
</div>
</template>
<script setup>
import './test.css'
</script>
```
transform result

```vue
<template>
<div>
foo
</div>
</template>
<script setup>
</script>
<style lang="css">
@import './test.css';
</style>
```

## About Tailwind CSS
Since vue enables shadow dom by default,
it will isolate the style,
Expand All @@ -126,7 +173,7 @@ or (only vite)
```

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

```html
Expand Down
26 changes: 24 additions & 2 deletions packages/sub-style/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { createUnplugin } from 'unplugin'
import { normalizePath, setGlobalPrefix } from 'baiwusanyu-utils'
import { extend, normalizePath, setGlobalPrefix } from 'baiwusanyu-utils'
import MagicString from 'magic-string'
import { NAME } from '@unplugin-vue-ce/utils'
import { injectVueRuntime } from './src/inject/inject-vue-runtime'
import { atomicCSSPreset, virtualTailwind, virtualUno } from './src/atomic-css'
import { parseESCSS } from './src/parse'
import { transformESCSS } from './src/transform'
import { injectStyleESCSS } from './src/inject/inject-style-escss'
import type { SFCParseOptions } from '@vue/compiler-sfc'
export interface UNVueCESubStyleOption {
isESCSS?: boolean
sfcParseOptions?: SFCParseOptions
}

export const unVueCESubStyle = (options: UNVueCESubStyleOption): any => {
const defaultOptions = {
isESCSS: false,
sfcParseOptions: {},
}

const resolvedOptions = extend(defaultOptions, options)

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

// esm import css transform to style tag
if (id.endsWith('.vue') && resolvedOptions.isESCSS) {
const parseRes = parseESCSS(mgcStr.toString(), resolvedOptions.sfcParseOptions)
transformESCSS(mgcStr, parseRes)
injectStyleESCSS(mgcStr, parseRes)
}

return {
code: mgcStr.toString(),
get map() {
Expand Down
33 changes: 33 additions & 0 deletions packages/sub-style/src/inject/inject-style-escss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { MagicStringBase } from 'magic-string-ast'
import type { ESCSSImport, ESCSSParseRes, IESCSSValuePos, ILoc } from '../parse'

export function injectStyleESCSS(
mgcStr: MagicStringBase,
parseRes: ESCSSParseRes,
) {
if (parseRes.scriptSetup) {
const { loc, cssImport } = parseRes.scriptSetup
genStyleTagCode(mgcStr, loc, cssImport)
}

if (parseRes.script) {
const { loc, cssImport } = parseRes.script
genStyleTagCode(mgcStr, loc, cssImport)
}
}

function genStyleTagCode(
mgcStr: MagicStringBase,
loc: ILoc,
cssImport: ESCSSImport,
) {
if (loc && cssImport) {
for (const [key, css] of Object.entries(cssImport)) {
css.forEach((v: IESCSSValuePos) => {
const offset = mgcStr.original.length - mgcStr.length()
mgcStr.appendRight(mgcStr.length() + offset, `<style lang="${key}"> @import "${v.value}";</style>\n
`)
})
}
}
}
114 changes: 114 additions & 0 deletions packages/sub-style/src/parse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { parse } from '@vue/compiler-sfc'
import { log, setGlobalPrefix } from 'baiwusanyu-utils'
import { parse as babelParse } from '@babel/parser'
import { walk } from 'estree-walker-ts'
import type { SFCParseOptions } from '@vue/compiler-sfc'
import type { ImportDeclaration, StringLiteral } from '@babel/types'

interface ILocPos {
column: number
line: number
offset: number
}
export interface ILoc {
source: string
start: ILocPos
end: ILocPos
}

export interface IESCSSValuePos {
value: string
start: number
end: number
}

export interface ESCSSImport {
css: IESCSSValuePos[]
sass: IESCSSValuePos[]
less: IESCSSValuePos[]
scss: IESCSSValuePos[]
}
export interface ESCSSParseRes {
script: {
loc?: ILoc // 替换 sfc 源码
cssImport: ESCSSImport
}
scriptSetup: {
loc?: ILoc // 替换 sfc 源码
cssImport: ESCSSImport
}
}
export function parseESCSS(
code: string,
options: SFCParseOptions) {
const parseSFCRes = parse(code, options)
if (parseSFCRes.errors) {
setGlobalPrefix('[unplugin-vue-ce]:')
parseSFCRes.errors.forEach((error) => {
log('error', error.message)
})
}

const { descriptor } = parseSFCRes
const parseRes: ESCSSParseRes = {
script: {
cssImport: {
css: [],
sass: [],
less: [],
scss: [],
},
},
scriptSetup: {
cssImport: {
css: [],
sass: [],
less: [],
scss: [],
},
},
}
if (descriptor.scriptSetup) {
const { loc } = descriptor.scriptSetup
parseRes.scriptSetup.loc = loc
parseScript(loc.source, parseRes, 'scriptSetup')
}
if (descriptor.script) {
const { loc } = descriptor.script
parseRes.script.loc = loc
parseScript(loc.source, parseRes, 'script')
}
return parseRes
}

function parseScript(
code: string,
parseRes: ESCSSParseRes,
type: 'scriptSetup' | 'script',
) {
const ast = babelParse(code, {
sourceType: 'module',
plugins: ['typescript'],
})

const regx = /\.(css|sass|less|scss)$/
;(walk as any)(ast, {
enter(
node: ImportDeclaration | StringLiteral,
parent: ImportDeclaration | StringLiteral,
) {
if (parent && node.type === 'StringLiteral'
&& parent.type === 'ImportDeclaration') {
const matched = node.value.match(regx)
if (matched) {
const key = matched[1] as 'css' | 'sass' | 'less' | 'scss'
parseRes[type].cssImport[key].push({
value: node.value,
start: parent.start!,
end: parent.end!,
})
}
}
},
})
}
32 changes: 32 additions & 0 deletions packages/sub-style/src/transform/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { ESCSSImport, ESCSSParseRes, IESCSSValuePos, ILoc } from '../parse'
import type { MagicStringBase } from 'magic-string-ast'

export function transformESCSS(
mgcStr: MagicStringBase,
parseRes: ESCSSParseRes,
) {
if (parseRes.scriptSetup) {
const { loc, cssImport } = parseRes.scriptSetup
overwriteCode(mgcStr, loc, cssImport)
}

if (parseRes.script) {
const { loc, cssImport } = parseRes.script
overwriteCode(mgcStr, loc, cssImport)
}
}

function overwriteCode(
mgcStr: MagicStringBase,
loc: ILoc,
cssImport: ESCSSImport,
) {
if (loc && cssImport) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_, css] of Object.entries(cssImport)) {
css.forEach((v: IESCSSValuePos) => {
mgcStr.update(v.start + loc.start.offset, v.end + loc.start.offset, '')
})
}
}
}
1 change: 1 addition & 0 deletions play/sub-style/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"fs-extra": "^11.1.1",
"magic-string": "^0.30.5",
"unplugin": "^1.5.0",
"vite-plugin-inspect": "latest",
"vue": "^3.3.6"
}
}
9 changes: 9 additions & 0 deletions play/sub-style/src/bwsy-ce-foo.ce.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@
</template>

<script setup>
import { onMounted } from 'vue'
import BwsyBar from './bwsy-bar.vue'
import './test.css'
// onMounted(() => {
// debugger
// })
</script>

<script>
</script>

<style scoped>
Expand Down
22 changes: 11 additions & 11 deletions play/sub-style/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
defineCustomElement,
} from '../patches/vue.esm-browser.js'; */

import { defineCustomElement } from 'vue'
import { createApp, defineCustomElement } from 'vue'

/* import App from './App.vue';
import Foo from './bwsy-ce-foo.ce.vue';
const app = createApp(App);
customElements.define('bwsy-ce-foo', defineCustomElement(Foo));
/!*app.config.compilerOptions.isCustomElement = (tag=>{
return tag === 'bwsy-ce-foo'
})*!/
app.mount('#app'); */
import App from './App.vue'
import Foo from './bwsy-ce-foo.ce.vue'
const app = createApp(App)
customElements.define('bwsy-ce-foo', defineCustomElement(Foo))
app.config.compilerOptions.isCustomElement = (tag) => {
return tag === 'bwsy-ce-foo'
}
app.mount('#app')

/* import App from './nested/vue-app.ce.vue'
const app = createApp(App);
Expand Down Expand Up @@ -50,6 +50,6 @@ const ceApp = defineCustomElement(App)
customElements.define('vue-app', ceApp)
*/

import App from './edison/A.ce.vue'
/* import App from './edison/A.ce.vue'
const ceApp = defineCustomElement(App)
customElements.define('vue-app', ceApp)
customElements.define('vue-app', ceApp) */
6 changes: 6 additions & 0 deletions play/sub-style/src/test.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
p {
background-color: #ffcd94;
}
body {
background-color: aliceblue;
}
6 changes: 5 additions & 1 deletion play/sub-style/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Inspect from 'vite-plugin-inspect'
import { viteVueCE } from '../../dist'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
customElement: true,
}),
viteVueCE(),
viteVueCE({
isESCSS: true,
}),
Inspect(),
],
build: {
minify: false,
Expand Down
Loading

0 comments on commit 441ad70

Please sign in to comment.