Skip to content

feat: add unplugin-vuetify package #347

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
],
"scripts": {
"lint": "eslint packages/*/src/**/* && tsc --noEmit",
"lint:fix": "eslint packages/*/src/**/* --fix",
"dev": "lerna run dev",
"dev:unplugin-vuetify": "lerna run dev:unplugin-vuetify --stream --no-prefix",
"dev:vite": "lerna run dev:vite --stream --no-prefix",
"dev:webpack": "lerna run dev:webpack --stream --no-prefix",
"build": "lerna run build",
Expand Down
23 changes: 23 additions & 0 deletions packages/unplugin-vuetify/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineBuildConfig } from 'unbuild'

export default defineBuildConfig({
entries: [
'src/index',
'src/types',
'src/unimport-presets',
'src/unplugin-vue-component-resolvers',
'src/utils',
],
externals: [
'pathe',
'upath',
'vite',
'vuetify',
'vuetify/directives',
'vuetify/components',
'vuetify/labs/components',
'unplugin-vue-components/types',
],
declaration: 'node16',
clean: true,
})
91 changes: 91 additions & 0 deletions packages/unplugin-vuetify/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"name": "unplugin-vuetify",
"version": "1.0.0",
"type": "module",
"description": "A set of utilities for Vuetify components, directives, styles and more",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/types.d.mts",
"exports": {
".": "./dist/index.mjs",
"./types": {
"types": "./dist/types.d.mts"
},
"./unimport-presets": "./dist/unimport-presets.mjs",
"./unplugin-vue-component-resolvers": "./dist/unplugin-vue-component-resolvers.mjs",
"./utils": "./dist/utils.mjs"
},
"typesVersions": {
"*": {
"types": [
"dist/types.d.mts"
],
"unimport-presets": [
"dist/unimport-presets.d.mts"
],
"unplugin-vue-component-resolvers": [
"dist/unplugin-vue-component-resolvers.d.mts"
],
"utils": [
"dist/utils.d.mts"
]
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuetifyjs/vuetify-loader.git"
},
"scripts": {
"build": "unbuild",
"dev": "unbuild --stub"
},
"author": "Kael Watts-Deuchar",
"contributors": [
{
"name": "Joaquín Sánchez <[email protected]> @userquin"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/vuetifyjs/vuetify-loader/issues"
},
"homepage": "https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin",
"dependencies": {
"debug": "^4.4.0",
"pathe": "2.0.3",
"upath": "^2.0.1"
},
"peerDependencies": {
"unimport": "^4.0.0 || ^5.0.0",
"unplugin-vue-components": "^28.4.1",
"vite": "^5.0.0 || ^6.0.0",
"vue": "^3.0.0",
"vuetify": "^3.0.0"
},
"peerDependenciesMeta": {
"unimport": {
"optional": true
},
"unplugin-vue-components": {
"optional": true
},
"vite": {
"optional": true
}
},
"devDependencies": {
"unbuild": "^2.0.0",
"unimport": "^5.0.0",
"unplugin-vue-components": "^28.4.1",
"vite": "^5.0.0"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"files": [
"dist/"
],
"publishConfig": {
"access": "public"
}
}
202 changes: 202 additions & 0 deletions packages/unplugin-vuetify/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import type { PluginOption } from 'vite'
import process from 'node:process'
import fs from 'node:fs'
import fsp from 'node:fs/promises'
import { pathToFileURL } from 'node:url'
import { resolveVuetifyBase } from './utils'
import { isAbsolute, relative as relativePath } from 'pathe'
import path from 'upath'
import { version as VITE_VERSION } from 'vite'

export interface VuetifyStylesOptions {
/**
* What CSS SASS/SCSS API should the plugin register?
* - `false` - don't register any SASS/SCSS API
* - `modern` - register SASS/SCSS 'modern' API => when using `sass`
* - `modern-compiler` - register SASS/SCSS 'modern-compiler' API => when using `sass-embedded`
*
* When using `modern` API, the plugin will enable `preprocessorMaxWorkers` in Vite CSS config.
*
* @default 'modern-compiler'
*/
registerApi?: 'modern' | 'modern-compiler' | false
/**
* Mode to use for styles:
* - `none`: remove all style imports
* - `source`: import sass/scss from source (old `sass` option)
* - `configFile`: customising variables
*/
mode?: true | 'none' | 'source' | {
configFile: string,
}
}

export function VuetifyStylesVitePlugin(options: VuetifyStylesOptions = {}) {
let configFile: string | undefined
// let cacheDir: string | undefined
const vuetifyBase = resolveVuetifyBase()
const noneFiles = new Set<string>()
let isNone = false
let sassVariables = false
let fileImport = false
const PREFIX = 'vuetify-styles/'
const SSR_PREFIX = `/@${PREFIX}`
const resolveCss = resolveCssFactory()
const api = options.registerApi ?? 'modern-compiler'

const [major, minor, patch] = VITE_VERSION.split('.')
.map((v: string) => v.includes('-') ? v.split('-')[0] : v)
.map(v => Number.parseInt(v))

return <PluginOption>{
name: 'unplugin-vuetify:styles',
enforce: 'pre',
config(config) {
if (!api)
return null

if (api === 'modern-compiler') {
return {
css: {
preprocessorOptions: {
sass: { api },
scss: { api },
},
},
}
}

if (config.css && !('preprocessorMaxWorkers' in config.css)) {
return {
css: {
preprocessorOptions: {
sass: { api },
scss: { api },
},
},
}
}

return {
css: {
preprocessorOptions: {
sass: { api },
scss: { api },
},
preprocessorMaxWorkers: true,
},
}
},
configResolved(config) {
if (config.plugins.findIndex(plugin => plugin.name === 'vuetify:styles') > -1)
throw new Error('Remove vite-plugin-vuetify from your Nuxt config file, this module registers a modified version.')

if (isObject(options.mode)) {
sassVariables = true
// 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)
if (path.isAbsolute(options.mode.configFile))
configFile = path.resolve(options.mode.configFile)
else
configFile = path.resolve(path.join(config.root || process.cwd(), options.mode.configFile))

configFile = fileImport
? pathToFileURL(configFile).href
: normalizePath(configFile)
}
else {
isNone = options.mode === 'none'
}
},
async resolveId(source, importer, { custom, ssr }) {
if (source.startsWith(PREFIX) || source.startsWith(SSR_PREFIX)) {
if (source.match(/\.s[ca]ss$/))
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.mode === 'source')
return this.resolve(await resolveCss(source), importer, { skipSelf: true, custom })

const resolution = await this.resolve(source, importer, { skipSelf: true, custom })
if (!resolution)
return undefined

const target = await resolveCss(resolution.id)
if (isNone) {
noneFiles.add(target)
return target
}

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

return undefined
},
load(id) {
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

if (target) {
const suffix = target.match(/\.scss/) ? ';\n' : '\n'
return {
code: `@use "${configFile}"${suffix}@use "${fileImport ? pathToFileURL(target).href : normalizePath(target)}"${suffix}`,
map: {
mappings: '',
},
}
}
}
return isNone && noneFiles.has(id) ? '' : undefined
},
}
}

function resolveCssFactory() {
const mappings = new Map<string, string>()
return async (source: string) => {
let mapping = mappings.get(source)
if (!mapping) {
try {
mapping = source.replace(/\.css$/, '.sass')
await fsp.access(mapping, fs.constants.R_OK)
}
catch (err) {
if (!(err instanceof Error && 'code' in err && err.code === 'ENOENT'))
throw err
mapping = source.replace(/\.css$/, '.scss')
}
mappings.set(source, mapping)
}
return mapping
}
}

function isObject (value: any): value is object {
return value !== null && typeof value === 'object'
}

// Add leading slash to absolute paths on windows
function normalizePath (p: string) {
p = path.normalize(p)
return /^[a-z]:\//i.test(p) ? '/' + p : p
}

function isSubdir(root: string, test: string) {
const relative = relativePath(root, test)
return relative && !relative.startsWith('..') && !isAbsolute(relative)
}
20 changes: 20 additions & 0 deletions packages/unplugin-vuetify/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface VuetifyComponent {
from: string
}
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export type ComponentName = keyof typeof import('vuetify/components')
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export type LabComponentName = keyof typeof import('vuetify/labs/components')
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
export type DirectiveName = keyof typeof import('vuetify/directives')
export interface VuetifyComponents {
[key: string]: VuetifyComponent
}
export interface ImportComponents {
components: VuetifyComponents
directives: DirectiveName[]
}
export interface ImportLabsComponents {
[key: string]: VuetifyComponent
}
export type ImportMaps = [importMaps: Promise<ImportComponents>, importMapsLabs: Promise<ImportLabsComponents>]
Loading