-
-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #516 from lumeland/feat/unocss
feat(unocss): support css transformers
- Loading branch information
Showing
6 changed files
with
3,698 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,20 @@ | ||
export { default } from "npm:@unocss/[email protected]"; | ||
export { presetUno } from "npm:@unocss/[email protected]"; | ||
// https://github.com/denoland/deno/issues/19096 | ||
import transformerVariantGroupImport from "npm:@unocss/[email protected]"; | ||
import transformerDirectivesImport from "npm:@unocss/[email protected]"; | ||
|
||
export type { UserConfig } from "npm:@unocss/[email protected]"; | ||
export { | ||
createGenerator, | ||
type SourceCodeTransformer, | ||
type UnocssPluginContext, | ||
type UserConfig, | ||
} from "npm:@unocss/[email protected]"; | ||
export { presetUno } from "npm:@unocss/[email protected]"; | ||
export { default as MagicString } from "npm:[email protected]"; | ||
|
||
// https://github.com/denoland/deno/issues/16458#issuecomment-1295003089 | ||
export const transformerVariantGroup = | ||
transformerVariantGroupImport as unknown as typeof transformerDirectivesImport.default; | ||
export const transformerDirectives = | ||
transformerDirectivesImport as unknown as typeof transformerDirectivesImport.default; | ||
|
||
export const resetUrl = "https://unpkg.com/@unocss/[email protected]"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,145 @@ | ||
import unocss, { presetUno } from "../deps/unocss.ts"; | ||
import { getExtension } from "../core/utils/path.ts"; | ||
import { merge } from "../core/utils/object.ts"; | ||
import { read } from "../core/utils/read.ts"; | ||
import { | ||
createGenerator, | ||
MagicString, | ||
presetUno, | ||
resetUrl, | ||
transformerDirectives, | ||
transformerVariantGroup, | ||
} from "../deps/unocss.ts"; | ||
|
||
import type { UserConfig } from "../deps/unocss.ts"; | ||
import type Site from "../core/site.ts"; | ||
import type { | ||
SourceCodeTransformer, | ||
UnocssPluginContext, | ||
UserConfig, | ||
} from "../deps/unocss.ts"; | ||
|
||
export interface Options { | ||
/** Extensions processed by this plugin to extract the utility classes */ | ||
extensions?: string[]; | ||
|
||
/** | ||
* Options passed to UnoCSS. | ||
* @see https://unocss.dev/guide/config-file | ||
* Configurations for UnoCSS. | ||
* @see {@link https://unocss.dev/guide/config-file} | ||
*/ | ||
config: UserConfig; | ||
/** | ||
* Set the css filename for all generated styles, | ||
* Set to `false` to insert a <style> tag per page. | ||
* @defaultValue `false` | ||
*/ | ||
options?: Omit<UserConfig, "content">; | ||
cssFile?: false | string; | ||
/** The list of extensions this plugin applies to */ | ||
cssFileExtensions: string[]; | ||
/** | ||
* Process CSS files using UnoCSS transformers. | ||
* @defaultValue `[transformerVariantGroup(), transformerDirectives()]` | ||
*/ | ||
cssFileTransformers: SourceCodeTransformer[]; | ||
/** | ||
* Supported CSS reset options. | ||
* @see {@link https://github.com/unocss/unocss/tree/main/packages/reset} | ||
* @defaultValue `tailwind` | ||
*/ | ||
reset?: false | "tailwind" | "tailwind-compat" | "eric-meyer"; | ||
} | ||
|
||
export const defaults: Options = { | ||
extensions: [".html"], | ||
options: { | ||
config: { | ||
presets: [presetUno()], | ||
}, | ||
cssFile: false, | ||
cssFileExtensions: [".css"], | ||
cssFileTransformers: [ | ||
transformerVariantGroup(), | ||
transformerDirectives(), | ||
], | ||
reset: "tailwind", | ||
}; | ||
|
||
export default function (userOptions?: Options) { | ||
const options = merge(defaults, userOptions); | ||
export default function (userOptions?: Partial<Options>) { | ||
const options: Options = merge(defaults, userOptions); | ||
|
||
return (site: Site) => { | ||
// deno-lint-ignore no-explicit-any | ||
let unoPlugins: any[]; | ||
const uno = createGenerator(options.config); | ||
|
||
if (site.hooks.postcss) { | ||
throw new Error( | ||
"PostCSS plugin is required to be installed AFTER UnoCSS plugin", | ||
); | ||
if (options.cssFileTransformers!.length > 0) { | ||
site.loadAssets(options.cssFileExtensions); | ||
site.process(options.cssFileExtensions, async (files) => { | ||
for (const file of files) { | ||
if (file.content) { | ||
const code = new MagicString(file.content.toString()); | ||
for await (const { transform } of options.cssFileTransformers!) { | ||
await transform( | ||
code, | ||
file.src.path, | ||
{ uno } as unknown as UnocssPluginContext, | ||
); | ||
} | ||
file.content = code.toString(); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
site.process(options.extensions, (pages) => { | ||
// Get the content of all HTML pages (sorted by path) | ||
const content = pages.sort((a, b) => a.src.path.localeCompare(b.src.path)) | ||
.map((page) => ({ | ||
raw: page.content as string, | ||
extension: getExtension(page.outputPath).substring(1), | ||
})); | ||
if (options.cssFile === false) { | ||
// Insert a <style> tag for each page | ||
site.process([".html"], async (pages) => { | ||
const reset = await getResetCss(options); | ||
|
||
Promise.all(pages.map(async (page) => { | ||
const document = page.document!; | ||
const result = await uno.generate( | ||
document.documentElement?.innerHTML ?? "", | ||
); | ||
const css = reset ? `${reset}\n${result.css}` : result.css; | ||
|
||
// Create UnoCSS plugin | ||
// @ts-ignore: This expression is not callable. | ||
const plugin = unocss({ | ||
configOrPath: options.options, | ||
content, | ||
if (css) { | ||
const style = document.createElement("style"); | ||
style.innerText = css; | ||
page.document?.head?.appendChild(style); | ||
} | ||
})); | ||
}); | ||
return; | ||
} | ||
|
||
// Ensure PostCSS plugin is installed | ||
if (!site.hooks.postcss) { | ||
throw new Error( | ||
"PostCSS plugin is required to be installed AFTER UnoCSS plugin", | ||
); | ||
} | ||
// Generate the stylesheets for all pages | ||
site.process([".html"], async (pages) => { | ||
const classes = new Set<string>(); | ||
|
||
// Replace the old UnoCSS plugin configuration from PostCSS plugins | ||
// deno-lint-ignore no-explicit-any | ||
site.hooks.postcss((runner: any) => { | ||
unoPlugins?.forEach((plugin) => { | ||
runner.plugins.splice(runner.plugins.indexOf(plugin), 1); | ||
}); | ||
unoPlugins = runner.normalize([plugin]); | ||
runner.plugins = runner.plugins.concat(unoPlugins); | ||
}); | ||
await Promise.all( | ||
pages.map(async (page) => | ||
await uno.generate( | ||
page.document?.documentElement?.innerHTML ?? "", | ||
) | ||
.then((res) => res.matched) | ||
.then((matched) => matched.forEach((match) => classes.add(match))) | ||
), | ||
); | ||
|
||
// Create & merge stylesheets for all pages | ||
const reset = await getResetCss(options); | ||
const result = await uno.generate(classes); | ||
const css = reset ? `${reset}\n${result.css}` : result.css; | ||
|
||
// Output the CSS file | ||
const output = await site.getOrCreatePage(options.cssFile as string); | ||
if (output.content) { | ||
output.content += `\n${css}`; | ||
} else { | ||
output.content = css; | ||
} | ||
}); | ||
}; | ||
} | ||
|
||
/** | ||
* TODO: Replace with CSS Modules Import | ||
* @remarks Deno does not currently support CSS Modules. | ||
* @see {@link https://github.com/denoland/deno/issues/11961} | ||
*/ | ||
async function getResetCss(options: Options) { | ||
return options.reset === false | ||
? "" | ||
: await read(`${resetUrl}/${options.reset}.css`, false); | ||
} |
Oops, something went wrong.