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: refactor vite styles plugin #338

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions packages/vite-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ export default createVuetify()

`settings.scss` can be used in your own components to access vuetify's variables.

#### Configure modern SASS compiler

Performance can be improved by using the modern SASS compiler. Replace your `sass` dependency with `sass-embedded`, update vite to 5.4 or later, and set
[css.preprocessorOptions.sass.api](https://vitejs.dev/config/shared-options#css-preprocessoroptions) to 'modern-compiler' in the vite config.

If you're using Nuxt 3, you can configure the modern SASS compiler in the Nuxt configuration file using [vite](https://nuxt.com/docs/api/nuxt-config#vite) option.


### Remove all style imports
```js
// vite.config.js
Expand Down
117 changes: 68 additions & 49 deletions packages/vite-plugin/src/stylesPlugin.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,105 @@
import path from 'upath'
import { resolveVuetifyBase, normalizePath, isObject } from '@vuetify/loader-shared'

import type { Plugin } from 'vite'
import { version as VITE_VERSION } from 'vite'
import type { Options } from '@vuetify/loader-shared'

function isSubdir (root: string, test: string) {
const relative = path.relative(root, test)
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
}

const PLUGIN_VIRTUAL_PREFIX = "virtual:"
const PLUGIN_VIRTUAL_NAME = "plugin-vuetify"
const VIRTUAL_MODULE_ID = `${PLUGIN_VIRTUAL_PREFIX}${PLUGIN_VIRTUAL_NAME}`
import { pathToFileURL } from 'node:url'

export function stylesPlugin (options: Options): Plugin {
const vuetifyBase = resolveVuetifyBase()

let configFile: string
const tempFiles = new Map<string, string>()
let configFile: string | undefined
const noneFiles = new Set<string>()
let isNone = false
let sassVariables = false
let fileImport = false
const PREFIX = 'vuetify-styles/'
const SSR_PREFIX = `/@${PREFIX}`

return {
name: 'vuetify:styles',
enforce: 'pre',
configResolved (config) {
if (isObject(options.styles)) {
if (path.isAbsolute(options.styles.configFile)) {
configFile = options.styles.configFile
} else {
configFile = path.join(config.root || process.cwd(), options.styles.configFile)
}
sassVariables = true
const [major, minor, patch] = VITE_VERSION.split('.')
.map((v: string) => v.includes('-') ? v.split('-')[0] : v)
.map(v => Number.parseInt(v))
// use file import when vite version > 5.4.2
// check https://github.com/vitejs/vite/pull/17909
fileImport = major > 5 || (major === 5 && minor > 4) || (major === 5 && minor === 4 && patch > 2)
configFile = path.isAbsolute(options.styles.configFile)
? path.resolve(options.styles.configFile)
: path.resolve(path.join(config.root || process.cwd(), options.styles.configFile))
configFile = fileImport
? pathToFileURL(configFile).href
: normalizePath(configFile)
}
else {
isNone = options.styles === 'none'
}
},
async resolveId (source, importer, { custom }) {
async resolveId (source, importer, { custom, ssr }) {
if (source.startsWith(PREFIX) || source.startsWith(SSR_PREFIX)) {
if (source.endsWith('.sass')) {
return source
}

const idx = source.indexOf('?')
return idx > -1 ? source.slice(0, idx) : source
}

if (
source === 'vuetify/styles' || (
importer &&
source.endsWith('.css') &&
isSubdir(vuetifyBase, path.isAbsolute(source) ? source : importer)
)
) {
if (options.styles === 'none') {
return `${PLUGIN_VIRTUAL_PREFIX}__void__`
} else if (options.styles === 'sass') {
if (options.styles === 'sass') {
const target = source.replace(/\.css$/, '.sass')
return this.resolve(target, importer, { skipSelf: true, custom })
} else if (isObject(options.styles)) {
const resolution = await this.resolve(source, importer, { skipSelf: true, custom })

if (!resolution) return null
}

const target = resolution.id.replace(/\.css$/, '.sass')
const file = path.relative(path.join(vuetifyBase, 'lib'), target)
const contents = `@use "${normalizePath(configFile)}"\n@use "${normalizePath(target)}"`
const resolution = await this.resolve(source, importer, { skipSelf: true, custom })

tempFiles.set(file, contents)
if (!resolution) {
return undefined
}

return `${VIRTUAL_MODULE_ID}:${file}`
const target = resolution.id.replace(/\.css$/, '.sass')
if (isNone) {
noneFiles.add(target)
return target
}
} else if (source.startsWith(`/${PLUGIN_VIRTUAL_NAME}:`)) {
return PLUGIN_VIRTUAL_PREFIX + source.slice(1)
} else if (source.startsWith(`/@id/__x00__${PLUGIN_VIRTUAL_NAME}:`)) {
return PLUGIN_VIRTUAL_PREFIX + source.slice(12)
} else if (source.startsWith(`/${VIRTUAL_MODULE_ID}:`)) {
return source.slice(1)

return `${ssr ? SSR_PREFIX : PREFIX}${path.relative(vuetifyBase, target)}`
}

return null
return undefined
},
load (id) {
// When Vite is configured with `optimizeDeps.exclude: ['vuetify']`, the
// received id contains a version hash (e.g. \0__void__?v=893fa859).
if (new RegExp(`^${PLUGIN_VIRTUAL_PREFIX}__void__(\\?.*)?$`).test(id)) {
return ''
}

if (id.startsWith(`${VIRTUAL_MODULE_ID}`)) {
const file = new RegExp(`^${VIRTUAL_MODULE_ID}:(.*?)(\\?.*)?$`).exec(id)![1]
if (sassVariables) {
const target = id.startsWith(PREFIX)
? path.resolve(vuetifyBase, id.slice(PREFIX.length))
: id.startsWith(SSR_PREFIX)
? path.resolve(vuetifyBase, id.slice(SSR_PREFIX.length))
: undefined

return tempFiles.get(file)
if (target) {
return {
code: `@use "${configFile}"\n@use "${fileImport ? pathToFileURL(target).href : normalizePath(target)}"`,
userquin marked this conversation as resolved.
Show resolved Hide resolved
map: {
mappings: '',
},
}
}
}

return null
return isNone && noneFiles.has(id) ? '' : undefined
},
}
}

function isSubdir (root: string, test: string) {
const relative = path.relative(root, test)
return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
}