Skip to content
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