From d051b5c071a6f373b05a711fa5a187955897bfc8 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 9 May 2022 12:57:13 +0800 Subject: [PATCH] feat(compile-class): new transformer (#950) * feat(compile-class): new transformer * chore: update * chore: update * chore: update --- alias.ts | 1 + packages/README.md | 1 + packages/transformer-compile-class/README.md | 70 ++++++++++++++++ .../transformer-compile-class/build.config.ts | 15 ++++ .../transformer-compile-class/package.json | 44 +++++++++++ .../transformer-compile-class/src/index.ts | 79 +++++++++++++++++++ packages/transformer-directives/README.md | 6 +- packages/transformer-variant-group/README.md | 6 +- packages/unocss/package.json | 1 + packages/unocss/src/index.ts | 1 + playground/src/App.vue | 2 +- playground/src/auto-imports.d.ts | 2 + playground/unocss.config.ts | 12 ++- pnpm-lock.yaml | 11 +++ test/__snapshots__/cli.test.ts.snap | 6 -- test/transformer-compile-class.test.ts | 59 ++++++++++++++ tsconfig.json | 1 + 17 files changed, 303 insertions(+), 14 deletions(-) create mode 100644 packages/transformer-compile-class/README.md create mode 100644 packages/transformer-compile-class/build.config.ts create mode 100644 packages/transformer-compile-class/package.json create mode 100644 packages/transformer-compile-class/src/index.ts create mode 100644 test/transformer-compile-class.test.ts diff --git a/alias.ts b/alias.ts index 13920d9d3a..ff8b724fa2 100644 --- a/alias.ts +++ b/alias.ts @@ -19,6 +19,7 @@ export const alias: Record = { '@unocss/preset-wind': r('./packages/preset-wind/src/'), '@unocss/transformer-directives': r('./packages/transformer-directives/src/'), '@unocss/transformer-variant-group': r('./packages/transformer-variant-group/src/'), + '@unocss/transformer-compile-class': r('./packages/transformer-compile-class/src/'), '@unocss/vite': r('./packages/vite/src/'), 'unocss': r('./packages/unocss/src/'), } diff --git a/packages/README.md b/packages/README.md index 2c07dbf7ab..cd576d4cd9 100644 --- a/packages/README.md +++ b/packages/README.md @@ -14,6 +14,7 @@ | [@unocss/preset-typography](./preset-typography) | The typography preset | ✅ | No | | [@unocss/transformer-variant-group](./transformer-variant-group) | Transformer for Windi CSS's variant group feature | ✅ | No | | [@unocss/transformer-directives](./transformer-directives) | Transformer for CSS directives like `@apply` | ✅ | No | +| [@unocss/transformer-compile-class](./transformer-compile-class) | Compile group of classes into one class | ✅ | No | | [@unocss/extractor-pug](./extractor-pug) | Extractor for Pug | No | - | | [@unocss/autocomplete](./autocomplete) | Utils for autocomplete | No | - | | [@unocss/config](./config) | Configuration file loader | ✅ | - | diff --git a/packages/transformer-compile-class/README.md b/packages/transformer-compile-class/README.md new file mode 100644 index 0000000000..e39c1aa5a4 --- /dev/null +++ b/packages/transformer-compile-class/README.md @@ -0,0 +1,70 @@ +# @unocss/transformer-compile-class + + + +Compile group of classes into one class. Inspried by [WindiCSS's compilation mode](https://windicss.org/posts/modes.html#compilation-mode) and [#948](https://github.com/unocss/unocss/issues/948) by [@UltraCakeBakery](https://github.com/UltraCakeBakery). + +## Install + +```bash +npm i -D @unocss/transformer-compile-class +``` + +```ts +// uno.config.js +import { defineConfig } from 'unocss' +import transformerCompileClass from '@unocss/transformer-compile-class' + +export default defineConfig({ + // ... + transformers: [ + transformerCompileClass(), + ], +}) +``` + +## Usage + +At the begin of your class strings, **add `:uno:` at the begin of the strings** to mark them for compilation. For example: + +```html +
+
+
+``` + +Will be compiled to: + +```html +
+
+
+``` + +```css +.uno-qlmcrp { + text-align: center; +} +.uno-0qw2gr { + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 700; +} +.uno-0qw2gr:hover { + --un-text-opacity: 1; + color: rgba(248, 113, 113, var(--un-text-opacity)); +} +@media (min-width: 640px) { + .uno-qlmcrp { + text-align: left; + } +} +``` + +## Options + +You can config the trigger string and prefix for compile class with the options. Refers to [the types](https://github.com/antfu/unocss/blob/main/packages/transformer-compile-class/src/index.ts#L4) for details. + +## License + +MIT License © 2021-PRESENT [Anthony Fu](https://github.com/antfu) diff --git a/packages/transformer-compile-class/build.config.ts b/packages/transformer-compile-class/build.config.ts new file mode 100644 index 0000000000..d20739e914 --- /dev/null +++ b/packages/transformer-compile-class/build.config.ts @@ -0,0 +1,15 @@ +import { defineBuildConfig } from 'unbuild' + +export default defineBuildConfig({ + entries: [ + 'src/index', + ], + clean: true, + declaration: true, + externals: [ + 'magic-string', + ], + rollup: { + emitCJS: true, + }, +}) diff --git a/packages/transformer-compile-class/package.json b/packages/transformer-compile-class/package.json new file mode 100644 index 0000000000..dabbbe01ee --- /dev/null +++ b/packages/transformer-compile-class/package.json @@ -0,0 +1,44 @@ +{ + "name": "@unocss/transformer-compile-class", + "version": "0.33.1", + "description": "Compile group of classes into one class", + "keywords": [ + "unocss", + "unocss-transformer" + ], + "homepage": "https://github.com/unocss/unocss/tree/main/packages/transformer-compile-class#readme", + "bugs": { + "url": "https://github.com/unocss/unocss/issues" + }, + "license": "MIT", + "author": "Anthony Fu ", + "repository": { + "type": "git", + "url": "git+https://github.com/unocss/unocss.git", + "directory": "packages/transformer-compile-class" + }, + "funding": "https://github.com/sponsors/antfu", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "require": "./dist/index.cjs", + "import": "./dist/index.mjs" + } + }, + "files": [ + "dist" + ], + "sideEffects": false, + "scripts": { + "build": "unbuild", + "stub": "unbuild --stub" + }, + "dependencies": { + "@unocss/core": "workspace:*" + }, + "devDependencies": { + "magic-string": "^0.26.1" + } +} diff --git a/packages/transformer-compile-class/src/index.ts b/packages/transformer-compile-class/src/index.ts new file mode 100644 index 0000000000..3486713449 --- /dev/null +++ b/packages/transformer-compile-class/src/index.ts @@ -0,0 +1,79 @@ +import type { SourceCodeTransformer } from '@unocss/core' +import { escapeRegExp } from '@unocss/core' + +export interface CompileClassOptions { + /** + * Trigger string + * @default ':uno:' + */ + trigger?: string + + /** + * Prefix for compile class name + * @default 'uno-' + */ + classPrefix?: string + + /** + * Hash function + */ + hashFn?: (str: string) => string + + /** + * Left unknown classes inside the string + * + * @default true + */ + keepUnknown?: boolean +} + +export default function transformerCompileClass(options: CompileClassOptions = {}): SourceCodeTransformer { + const { + trigger = ':uno:', + classPrefix = 'uno-', + hashFn = hash, + keepUnknown = true, + } = options + const regex = new RegExp(`(["'\`])${escapeRegExp(trigger)}\\s([^\\1]*?)\\1`, 'g') + + return { + name: 'compile-class', + enforce: 'pre', + async transform(s, _, { uno }) { + const matches = [...s.original.matchAll(regex)] + if (!matches.length) + return + + for (const match of matches) { + let body = match[2].trim() + const start = match.index! + const replacements = [] + if (keepUnknown) { + const result = await Promise.all(body.split(/\s+/).filter(Boolean).map(async i => [i, !!await uno.parseToken(i)] as const)) + const known = result.filter(([, matched]) => matched).map(([i]) => i) + const unknown = result.filter(([, matched]) => !matched).map(([i]) => i) + replacements.push(...unknown) + body = known.join(' ') + } + if (body) { + const hash = hashFn(body) + const className = `${classPrefix}${hash}` + replacements.unshift(className) + uno.config.shortcuts.push([className, body]) + } + s.overwrite(start + 1, start + match[0].length - 1, replacements.join(' ')) + } + }, + } +} + +function hash(str: string) { + let i; let l + let hval = 0x811C9DC5 + + for (i = 0, l = str.length; i < l; i++) { + hval ^= str.charCodeAt(i) + hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24) + } + return (`00000${(hval >>> 0).toString(36)}`).slice(-6) +} diff --git a/packages/transformer-directives/README.md b/packages/transformer-directives/README.md index b843f76070..be0f69b92f 100644 --- a/packages/transformer-directives/README.md +++ b/packages/transformer-directives/README.md @@ -11,10 +11,12 @@ npm i -D @unocss/transformer-directives ``` ```ts -import Unocss from 'unocss/vite' +// uno.config.js +import { defineConfig } from 'unocss' import transformerDirective from '@unocss/transformer-directives' -Unocss({ +export default defineConfig({ + // ... transformers: [ transformerDirective(), ], diff --git a/packages/transformer-variant-group/README.md b/packages/transformer-variant-group/README.md index ae5c243ce1..b398f0c382 100644 --- a/packages/transformer-variant-group/README.md +++ b/packages/transformer-variant-group/README.md @@ -11,10 +11,12 @@ npm i -D @unocss/transformer-variant-group ``` ```ts -import Unocss from 'unocss/vite' +// uno.config.js +import { defineConfig } from 'unocss' import transformerVariantGroup from '@unocss/transformer-variant-group' -Unocss({ +export default defineConfig({ + // ... transformers: [ transformerVariantGroup(), ], diff --git a/packages/unocss/package.json b/packages/unocss/package.json index d47d5970fd..e0a9bc69a5 100644 --- a/packages/unocss/package.json +++ b/packages/unocss/package.json @@ -93,6 +93,7 @@ "@unocss/reset": "workspace:*", "@unocss/transformer-directives": "workspace:*", "@unocss/transformer-variant-group": "workspace:*", + "@unocss/transformer-compile-class": "workspace:*", "@unocss/vite": "workspace:*" }, "engines": { diff --git a/packages/unocss/src/index.ts b/packages/unocss/src/index.ts index 68b6aece9d..e375208d37 100644 --- a/packages/unocss/src/index.ts +++ b/packages/unocss/src/index.ts @@ -10,6 +10,7 @@ export { default as presetMini } from '@unocss/preset-mini' export { default as presetWind } from '@unocss/preset-wind' export { default as transformerDirectives } from '@unocss/transformer-directives' export { default as transformerVariantGroup } from '@unocss/transformer-variant-group' +export { default as transformerCompileClass } from '@unocss/transformer-compile-class' export function defineConfig(config: UserConfig) { return config diff --git a/playground/src/App.vue b/playground/src/App.vue index 82fddc783d..af282f7acf 100644 --- a/playground/src/App.vue +++ b/playground/src/App.vue @@ -1,5 +1,5 @@ diff --git a/playground/src/auto-imports.d.ts b/playground/src/auto-imports.d.ts index e9ff9a17c4..dd68b78bb0 100644 --- a/playground/src/auto-imports.d.ts +++ b/playground/src/auto-imports.d.ts @@ -112,6 +112,7 @@ declare global { const useCssModule: typeof import('vue')['useCssModule'] const useCssVar: typeof import('@vueuse/core')['useCssVar'] const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] const useCycleList: typeof import('@vueuse/core')['useCycleList'] const useDark: typeof import('@vueuse/core')['useDark'] const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] @@ -178,6 +179,7 @@ declare global { const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] const useScroll: typeof import('@vueuse/core')['useScroll'] diff --git a/playground/unocss.config.ts b/playground/unocss.config.ts index a58872329d..4305311c00 100644 --- a/playground/unocss.config.ts +++ b/playground/unocss.config.ts @@ -1,6 +1,11 @@ -import { defineConfig, presetAttributify, presetUno } from 'unocss' -import transformerVariantGroup from '@unocss/transformer-variant-group' -import transformerDirectives from '@unocss/transformer-directives' +import { + defineConfig, + presetAttributify, + presetUno, + transformerCompileClass, + transformerDirectives, + transformerVariantGroup, +} from 'unocss' export function createConfig({ strict = true, dev = true } = {}) { return defineConfig({ @@ -18,6 +23,7 @@ export function createConfig({ strict = true, dev = true } = {}) { transformers: [ transformerVariantGroup(), transformerDirectives(), + transformerCompileClass(), ], }) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abbbb1398d..96abde7481 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -448,6 +448,15 @@ importers: '@unocss/core': link:../core '@unocss/scope': link:../scope + packages/transformer-compile-class: + specifiers: + '@unocss/core': workspace:* + magic-string: ^0.26.1 + dependencies: + '@unocss/core': link:../core + devDependencies: + magic-string: 0.26.1 + packages/transformer-directives: specifiers: '@unocss/core': workspace:* @@ -480,6 +489,7 @@ importers: '@unocss/preset-web-fonts': workspace:* '@unocss/preset-wind': workspace:* '@unocss/reset': workspace:* + '@unocss/transformer-compile-class': workspace:* '@unocss/transformer-directives': workspace:* '@unocss/transformer-variant-group': workspace:* '@unocss/vite': workspace:* @@ -494,6 +504,7 @@ importers: '@unocss/preset-web-fonts': link:../preset-web-fonts '@unocss/preset-wind': link:../preset-wind '@unocss/reset': link:../reset + '@unocss/transformer-compile-class': link:../transformer-compile-class '@unocss/transformer-directives': link:../transformer-directives '@unocss/transformer-variant-group': link:../transformer-variant-group '@unocss/vite': link:../vite diff --git a/test/__snapshots__/cli.test.ts.snap b/test/__snapshots__/cli.test.ts.snap index c5506fce2d..dde56f1ae2 100644 --- a/test/__snapshots__/cli.test.ts.snap +++ b/test/__snapshots__/cli.test.ts.snap @@ -5,9 +5,3 @@ exports[`cli > builds uno.css 1`] = ` .max-w-screen-md{max-width:768px;} .p-4{padding:1rem;}" `; - -exports[`cli > supports unocss.config.js 1`] = ` -"/* layer: shortcuts */ -.box{--un-shadow-inset:var(--un-empty,/*!*/ /*!*/);--un-shadow:0 0 #0000;} -.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, 0 0 #0000), var(--un-ring-shadow, 0 0 #0000), var(--un-shadow);}" -`; diff --git a/test/transformer-compile-class.test.ts b/test/transformer-compile-class.test.ts new file mode 100644 index 0000000000..4a13dcb62a --- /dev/null +++ b/test/transformer-compile-class.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from 'vitest' +import type { UnoGenerator } from '@unocss/core' +import { createGenerator } from '@unocss/core' +import MagicString from 'magic-string' +import transformerCompileClass from '@unocss/transformer-compile-class' +import presetUno from '@unocss/preset-uno' + +describe('transformer-compile-class', () => { + const uno = createGenerator({ + presets: [ + presetUno(), + ], + }) + const transformer = transformerCompileClass() + + async function transform(code: string, _uno: UnoGenerator = uno) { + const s = new MagicString(code) + await transformer.transform(s, 'foo.js', { uno: _uno } as any) + const result = s.toString() + const { css } = await uno.generate(result) + return { + code: result, + css, + } + } + + test('basic', async () => { + const result = await transform(` +
+
+ +
+
+
+ `) + expect(result).toMatchInlineSnapshot(` + { + "code": " +
+
+ +
+
+
+ ", + "css": "/* layer: shortcuts */ + .uno-qe05lz{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));} + .uno-qe05lz{--un-scale-x:0.05;--un-scale-y:0.05;transform:var(--un-transform);border-width:1px;border-style:solid;--un-border-opacity:1;border-color:rgba(229,231,235,var(--un-border-opacity));--un-bg-opacity:1;background-color:rgba(239,68,68,var(--un-bg-opacity));font-size:1.25rem;line-height:1.75rem;font-weight:700;} + .dark .uno-qe05lz:hover{--un-bg-opacity:1;background-color:rgba(34,197,94,var(--un-bg-opacity));} + .uno-qlmcrp{text-align:center;} + .uno-0qw2gr{font-size:0.875rem;line-height:1.25rem;font-weight:700;} + .uno-0qw2gr:hover{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));} + @media (min-width: 640px){ + .uno-qlmcrp{text-align:left;} + }", + } + `) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 8c5d633ede..134bfc861d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,6 +35,7 @@ "@unocss/shared-common": ["./packages/shared-common/src/index.ts"], "@unocss/transformer-directives": ["./packages/transformer-directives/src/index.ts"], "@unocss/transformer-variant-group": ["./packages/transformer-variant-group/src/index.ts"], + "@unocss/transformer-compile-class": ["./packages/transformer-compile-class/src/index.ts"], "@unocss/vite": ["./packages/vite/src/index.ts"], "unocss": ["./packages/unocss/src/index.ts"], "unocss/vite": ["./packages/unocss/src/vite.ts"]