Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 packages/compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"prepublishOnly": "npm run build"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"@vitejs/plugin-vue": "^6.0.0",
"tailwindcss": "^4.0.0",
"tsup": "^8.5.0",
"vite": "^5.0.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import TestPage from './pages/TestPage.vue';
</template>
</VaNavbar>

<div style="padding: 16px">
<div style="padding: 16px" class="bg-my-custom-color">
<TestPage>
Default slot
<template #footer>
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/playground/src/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "tailwindcss";
1 change: 1 addition & 0 deletions packages/compiler/playground/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createApp } from 'vue'
import App from './App.vue'
import './main.css'

createApp(App)
.mount('#app')
2 changes: 2 additions & 0 deletions packages/compiler/playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Inspect from 'vite-plugin-inspect'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'

import { vuestic } from '../vite-plugin'

Expand All @@ -18,6 +19,7 @@ export default defineConfig({
config: true,
cssLayers: true,
}),
tailwindcss(),
vue(),
Inspect(),
],
Expand Down
26 changes: 26 additions & 0 deletions packages/compiler/shared/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { existsSync, watch } from 'fs'
import { dirname } from 'path'

export const watchFileChangeOnce = (path: string, cb: () => void) => {
if (!existsSync(path)) {
const watcher = watch(dirname(path), { recursive: true }, (eventType, filename) => {
if (filename === path) {
cb()
}

watcher.close()
})

return
}

const watcher = watch(
path,
(eventType, filename) => {
if (filename) {
cb()
watcher.close()
}
}
)
}
48 changes: 48 additions & 0 deletions packages/compiler/shared/ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import ts from 'typescript'
import { basename, dirname, resolve } from "path";
import { writeFile, mkdtemp, rm } from 'fs/promises'
import { randomUUID } from 'crypto'
import { logger } from '../logger';

export const executeModule = async <T>(scriptCode: string, filePath: string) => {
if (!filePath) {
filePath = randomUUID()
}

const fileName = basename(filePath)
const dirName = dirname(filePath)

const tempFileName = resolve(dirName, fileName + `${randomUUID()}-vc.mjs`)

try {
await writeFile(tempFileName, scriptCode)

const module = await import(tempFileName)

return module as T
}
catch (e) {
logger.error(typeof e === 'string' ? e : e instanceof Error ? e.message : 'Unknown error', {
timestamp: true
})
}
finally {
await rm(tempFileName, { recursive: true, force: true })
}
};

export const executeTsModule = async <T>(scriptCode: string, filePath: string) => {
const transpiled = transpileTs(scriptCode)

return executeModule<T>(transpiled.outputText, filePath)
}

export const transpileTs = (code: string) => {
return ts.transpileModule(code, {
compilerOptions: {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
strict: false,
},
})
}
49 changes: 49 additions & 0 deletions packages/compiler/shared/vuestic-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { executeTsModule } from './ts'
import { type GlobalConfig } from 'vuestic-ui'
import { watchFileChangeOnce } from './fs'

const POSSIBLE_CONFIG_FILES = [
'./vuestic.config.ts',
'./vuestic.config.js',
'./vuestic.config.mjs',
'./vuestic.config.cjs',
'./vuestic.config.mts',
]

export const resolveVuesticConfigPath = () => {
for (const file of POSSIBLE_CONFIG_FILES) {
const absolutePath = resolve(file)
if (existsSync(absolutePath)) {
return absolutePath
}
}
}

export const tryToReadConfig = async (path: string | undefined = resolveVuesticConfigPath()) => {
if (path && existsSync(path)) {
const absolutePath = resolve(path)
const source = await readFile(absolutePath)
const { default: config } = await executeTsModule<{ default: GlobalConfig }>(source.toString(), absolutePath) ?? {}

return config
}

return null
}

export const watchVuesticConfigOnce = async (onChange: () => void) => {
let config = await tryToReadConfig()

for (const file of POSSIBLE_CONFIG_FILES) {
const absolutePath = resolve(file)

watchFileChangeOnce(absolutePath, () => {
onChange()
})
}

return config
}
79 changes: 79 additions & 0 deletions packages/compiler/tailwindcss/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { readFile, writeFile } from 'fs/promises';
import { MagicString } from '@vue/compiler-sfc';
import { Plugin } from 'vite';
import { watchVuesticConfigOnce } from '../shared/vuestic-config';
import { colorsPreset } from 'vuestic-ui'
import { logger } from '../logger';

const kebabCase = (str: string) => {
return str.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
.toLowerCase();
}

const defaultColors = Object.keys(colorsPreset.light)

function uniqueArray<T>(array: T[]): T[] {
return Array.from(new Set(array));
}

function addCssVariables(colors: Record<string, string>): string {
const colorVariables = uniqueArray([...defaultColors, ...Object.keys(colors)]).map((key) => `--color-${kebabCase(key)}: var(--va-${kebabCase(key)});`).join('\n ');

return `
@theme {
${colorVariables}
}
`
}

const TAILWIND_CSS_IMPORT_TEMPLATE = `@import "tailwindcss";`

export const vuesticTailwind = () => {
let server: import('vite').ViteDevServer

return {
name: 'vuestic:tailwindcss',

enforce: 'pre',

configResolved(config) {
const tailwindPluginIndex = config.plugins.findIndex((p) => p.name === '@tailwindcss/vite:scan')
const vuesticTailwindPluginIndex = config.plugins.findIndex((p) => p.name === 'vuestic:tailwindcss')

if (tailwindPluginIndex !== -1 && vuesticTailwindPluginIndex !== -1 && tailwindPluginIndex < vuesticTailwindPluginIndex) {
logger.warn('[Vuestic] vuestic plugin should be placed before tailwindcss plugin in the Vite config plugins array.', { timestamp: true });
}
},

configureServer(_s) {
server = _s;
},

async transform(code, id, options) {
if (!id.endsWith('css')) {
return
}

if (!code.includes(TAILWIND_CSS_IMPORT_TEMPLATE)) {
return
}

const ms = new MagicString(code);

const config = (await watchVuesticConfigOnce(async () => {
// trigger full re-transform of id
await writeFile(id, ((await readFile(id)).toString()))

server?.ws.send({ type: 'full-reload', path: '*' });
})) ?? { colors: { variables: {} } };

ms.appendRight(code.indexOf(TAILWIND_CSS_IMPORT_TEMPLATE) + TAILWIND_CSS_IMPORT_TEMPLATE.length, addCssVariables(config.colors.variables));

return {
code: ms.toString(),
map: ms.generateMap()
}
},
} as Plugin
}
8 changes: 8 additions & 0 deletions packages/compiler/vite-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { devtools, PluginOptions as DevtoolsPluginOptions } from "../devtools"
import { cssLayers } from "../css-layers"
import { vuesticConfig, Options as VuesticConfigPluginOptions } from "../vuestic-config"
import { vuesticAutoImport } from "../auto-import"
import { vuesticTailwind } from "../tailwindcss"
import { mergeDeep } from "../shared/merge-deep"
import { logger } from "../logger"
import { getProjectEnv } from "../shared/project-env"
Expand Down Expand Up @@ -92,6 +93,13 @@ export const vuestic = (options: Options = {}): Plugin[] => {
plugins.push(cssLayers)
}

if (env.hasTailwindCSS) {
logger.info(formatString('Using [vuestic:tailwind] plugin.'), {
timestamp: true,
})
plugins.push(vuesticTailwind())
}

if (Boolean(options.autoImport)) {
logger.info(formatString('Using [vuestic:auto-import] plugin.'), {
timestamp: true,
Expand Down
86 changes: 6 additions & 80 deletions packages/create-vuestic/src/steps/2.2.addTailwind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,13 @@ const installInVite = async () => {

const css = resolveCorrectExt('src/assets/main', ['css', 'scss', 'sass'])

return Promise.all([
addFile('tailwind.config.mjs', `
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
screens: {
xs: '0px',
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
},
},
plugins: [],
}
`.trim()),
addFile('postcss.config.mjs', `
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
`.trim()),
replaceFileContent(css!, (content) =>
return replaceFileContent(css!, (content) =>
content.replace("@import './base.css';", `
@import './base.css';

@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
`.trim())
)
])
}

const installInNuxt = async () => {
Expand All @@ -54,48 +23,9 @@ const installInNuxt = async () => {
const nuxtConfig = resolveCorrectExt('nuxt.config', ['ts', 'js'])

return Promise.all([
addFile('tailwind.config.js', `
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./nuxt.config.{js,ts}",
"./app.vue",
],
theme: {
extend: {},
screens: {
xs: '0px',
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
},
},
plugins: [],
}
`.trim()),
addFile('./assets/css/main.css', `
@tailwind base;
@tailwind components;
@tailwind utilities;
`.trim()
),
replaceFileContent(nuxtConfig!, (content) =>
content.replace('export default defineNuxtConfig({', `
export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
`.trim())
),
@import "tailwindcss";
`.trim()),
])
}

Expand All @@ -115,13 +45,9 @@ export const addTailwind = async (options: UserAnswers) => {

await Promise.all([
addDependencies({
dependencies: {
'@vuestic/tailwind': versions['@vuestic/tailwind'],
},
devDependencies: {
tailwindcss: versions['tailwindcss'],
autoprefixer: versions['autoprefixer'],
postcss: versions['postcss'],
"@tailwindcss/vite": versions['@tailwindcss/vite'],
"tailwindcss": versions['tailwindcss'],
}
}),
])
Expand Down
4 changes: 2 additions & 2 deletions packages/create-vuestic/src/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export const versions = {
'@vuestic/nuxt': '^1.0.19',
'@vuestic/ag-grid-theme': '^1.1.4',
'@vuestic/compiler': '^0.2.7',
'@vuestic/tailwind': '^0.1.3',
'tailwindcss': '^3.3.3',
'@tailwindcss/vite': '^4.1.11',
'tailwindcss': '^4.1.11',
'postcss': '^8.4.28',
'autoprefixer': '^10.4.15'
}
Loading
Loading