diff --git a/packages/cli/package.json b/packages/cli/package.json index 91aa622be7..dbb55360a7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,6 +43,8 @@ "stub": "unbuild --stub" }, "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@rollup/pluginutils": "^4.2.1", "@unocss/config": "workspace:*", "@unocss/core": "workspace:*", "@unocss/preset-uno": "workspace:*", @@ -51,6 +53,7 @@ "colorette": "^2.0.19", "consola": "^2.15.3", "fast-glob": "^3.2.11", + "magic-string": "^0.26.3", "pathe": "^0.3.5", "perfect-debounce": "^0.1.3" } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c0dc0428f3..db8601be51 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -4,9 +4,12 @@ import fg from 'fast-glob' import consola from 'consola' import { cyan, dim, green } from 'colorette' import { debounce } from 'perfect-debounce' -import { createGenerator, toArray } from '@unocss/core' +import { toArray } from '@unocss/core' import { loadConfig } from '@unocss/config' +import type { SourceCodeTransformerEnforce, UserConfig } from '@unocss/core' import { version } from '../package.json' +import { createContext } from '../../shared-integration/src/context' +import { applyTransformers } from '../../shared-integration/src/transformers' import { PrettyError, handleError } from './errors' import { defaultConfig } from './config' import type { CliOptions, ResolvedCliOptions } from './types' @@ -30,10 +33,7 @@ export async function build(_options: CliOptions) { const options = await resolveOptions(_options) const { config, sources: configSources } = await loadConfig(cwd, options.config) - const uno = createGenerator( - config, - defaultConfig, - ) + const ctx = createContext(config, defaultConfig) const files = await fg(options.patterns, { cwd, absolute: true }) await Promise.all( @@ -74,7 +74,7 @@ export async function build(_options: CliOptions) { const absolutePath = resolve(cwd, file) if (configSources.includes(absolutePath)) { - uno.setConfig((await loadConfig()).config) + await ctx.reloadConfig() consola.info(`${cyan(basename(file))} changed, setting new config`) } else { @@ -99,12 +99,38 @@ export async function build(_options: CliOptions) { await generate(options) - startWatcher().catch(handleError) + await startWatcher().catch(handleError) + + function transformFiles(sources: { id: string; code: string; transformedCode?: string | undefined }[], enforce: SourceCodeTransformerEnforce = 'default') { + return Promise.all( + sources.map(({ id, code, transformedCode }) => new Promise<{ id: string; code: string; transformedCode: string | undefined }>((resolve) => { + applyTransformers(ctx, code, id, enforce) + .then((transformsRes) => { + resolve({ id, code, transformedCode: transformsRes?.code || transformedCode }) + }) + }))) + } async function generate(options: ResolvedCliOptions) { + const sourceCache = Array.from(fileCache).map(([id, code]) => ({ id, code })) + const outFile = resolve(options.cwd || process.cwd(), options.outFile ?? 'uno.css') - const { css, matched } = await uno.generate( - [...fileCache].join('\n'), + + const preTransform = await transformFiles(sourceCache, 'pre') + const defaultTransform = await transformFiles(preTransform) + const postTransform = await transformFiles(defaultTransform, 'post') + + // update source file + await Promise.all( + postTransform.filter(({ transformedCode }) => !!transformedCode) + .map(({ transformedCode, id }) => new Promise((resolve) => { + if (existsSync(id)) + fs.writeFile(id, transformedCode as string, 'utf-8').then(resolve) + })), + ) + + const { css, matched } = await ctx.uno.generate( + [...postTransform.map(({ code, transformedCode }) => transformedCode ?? code)].join('\n'), { preflights: options.preflights, minify: options.minify, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d91bfda341..ad114f0f1c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,7 +163,7 @@ importers: simple-git-hooks: 2.8.0 splitpanes: 3.1.1 terser: 5.15.0 - tsup: 6.2.3_lluugpswbu4parudwrjubsxgua + tsup: 6.2.3_typescript@4.8.2 typescript: 4.8.2 unbuild: 0.8.10 unocss: link:packages/unocss @@ -197,7 +197,7 @@ importers: fs-extra: 10.1.0 local-pkg: 0.4.2 postcss: 8.4.16 - tailwindcss: 3.1.8 + tailwindcss: 3.1.8_postcss@8.4.16 unocss: link:../packages/unocss vite: 3.0.9 vite-plugin-windicss: 1.8.7_vite@3.0.9 @@ -268,6 +268,8 @@ importers: packages/cli: specifiers: + '@ampproject/remapping': ^2.2.0 + '@rollup/pluginutils': ^4.2.1 '@unocss/config': workspace:* '@unocss/core': workspace:* '@unocss/preset-uno': workspace:* @@ -276,9 +278,12 @@ importers: colorette: ^2.0.19 consola: ^2.15.3 fast-glob: ^3.2.11 + magic-string: ^0.26.3 pathe: ^0.3.5 perfect-debounce: ^0.1.3 dependencies: + '@ampproject/remapping': 2.2.0 + '@rollup/pluginutils': 4.2.1 '@unocss/config': link:../config '@unocss/core': link:../core '@unocss/preset-uno': link:../preset-uno @@ -287,6 +292,7 @@ importers: colorette: 2.0.19 consola: 2.15.3 fast-glob: 3.2.11 + magic-string: 0.26.3 pathe: 0.3.5 perfect-debounce: 0.1.3 @@ -9585,10 +9591,12 @@ packages: tslib: 2.4.0 dev: true - /tailwindcss/3.1.8: + /tailwindcss/3.1.8_postcss@8.4.16: resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==} engines: {node: '>=12.13.0'} hasBin: true + peerDependencies: + postcss: ^8.0.9 dependencies: arg: 5.0.2 chokidar: 3.5.3 @@ -9872,7 +9880,7 @@ packages: - ts-node dev: true - /tsup/6.2.3_lluugpswbu4parudwrjubsxgua: + /tsup/6.2.3_typescript@4.8.2: resolution: {integrity: sha512-J5Pu2Dx0E1wlpIEsVFv9ryzP1pZ1OYsJ2cBHZ7GrKteytNdzaSz5hmLX7/nAxtypq+jVkVvA79d7S83ETgHQ5w==} engines: {node: '>=14'} hasBin: true @@ -9896,8 +9904,7 @@ packages: execa: 5.1.1 globby: 11.1.0 joycon: 3.1.1 - postcss: 8.4.16 - postcss-load-config: 3.1.4_postcss@8.4.16 + postcss-load-config: 3.1.4 resolve-from: 5.0.0 rollup: 2.79.0 source-map: 0.8.0-beta.0 diff --git a/test/__snapshots__/cli.test.ts.snap b/test/__snapshots__/cli.test.ts.snap index a5ce88c447..5fae978fae 100644 --- a/test/__snapshots__/cli.test.ts.snap +++ b/test/__snapshots__/cli.test.ts.snap @@ -6,7 +6,20 @@ exports[`cli > builds uno.css 1`] = ` .p-4{padding:1rem;}" `; +exports[`cli > supports directives transformer 1`] = `""`; + +exports[`cli > supports directives transformer 2`] = `".btn-center{margin-top:0rem;margin-bottom:0rem;text-align:center;font-weight:500;}"`; + exports[`cli > supports unocss.config.js 1`] = ` "/* layer: shortcuts */ .box{margin-left:auto;margin-right:auto;max-width:80rem;border-radius:0.375rem;--un-bg-opacity:1;background-color:rgba(243,244,246,var(--un-bg-opacity));padding:1rem;--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color, rgba(0,0,0,0.05));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}" `; + +exports[`cli > supports variantGroup transformer 1`] = ` +"/* layer: default */ +.border-red{--un-border-opacity:1;border-color:rgba(248,113,113,var(--un-border-opacity));} +.border-solid{border-style:solid;} +.p-4{padding:1rem;}" +`; + +exports[`cli > supports variantGroup transformer 2`] = `"
"`; diff --git a/test/cli.test.ts b/test/cli.test.ts index ebc1680b8c..c07be36a70 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -37,6 +37,34 @@ export default defineConfig({ expect(output).toMatchSnapshot() }) + it('supports variantGroup transformer', async () => { + const { output, transform } = await runCli({ + 'views/index.html': '
', + 'unocss.config.js': ` +import { defineConfig, transformerVariantGroup } from 'unocss' +export default defineConfig({ + transformers: [transformerVariantGroup()] +}) + `.trim(), + }, 'views/index.html') + expect(output).toMatchSnapshot() + expect(transform).toMatchSnapshot() + }) + + it('supports directives transformer', async () => { + const { output, transform } = await runCli({ + 'views/index.css': '.btn-center{@apply text-center my-0 font-medium;}', + 'unocss.config.js': ` +import { defineConfig, transformerDirectives } from 'unocss' +export default defineConfig({ + transformers: [transformerDirectives()] +}) + `.trim(), + }, 'views/index.css') + expect(output).toMatchSnapshot() + expect(transform).toMatchSnapshot() + }) + it('uno.css exclude initialized class after changing file', async () => { const fileName = 'views/index.html' const initializedContent = '
' @@ -55,7 +83,7 @@ export default defineConfig({ // polling until update for (let i = 100; i >= 0; i--) { await sleep(100) - const output = await readUnocssFile(testDir) + const output = await readFile(testDir) if (i === 0 || output.includes('.bg-red')) { expect(output).toContain('.bg-red') break @@ -89,17 +117,25 @@ function runAsyncChildProcess(cwd: string, ...args: string[]) { return startCli(cwd, ['', '', ...args, '--no-preflights']) } -function readUnocssFile(testDir: string) { - return fs.readFile(resolve(testDir, 'uno.css'), 'utf8') +function readFile(testDir: string, targetFile?: string) { + return fs.readFile(resolve(testDir, targetFile ?? 'uno.css'), 'utf8') } -async function runCli(files: Record) { +async function runCli(files: Record, transformFile?: string) { const testDir = getTestDir() await initOutputFiles(testDir, files) await runAsyncChildProcess(testDir, 'views/**/*') - const output = await readUnocssFile(testDir) + const output = await readFile(testDir) + + if (transformFile) { + const transform = await readFile(testDir, transformFile) + return { + output, + transform, + } + } return { output,