Skip to content

Commit

Permalink
Merge pull request #516 from lumeland/feat/unocss
Browse files Browse the repository at this point in the history
feat(unocss): support css transformers
  • Loading branch information
oscarotero authored Nov 27, 2023
2 parents 3e40ed3 + b0e4c1c commit 07f2c05
Show file tree
Hide file tree
Showing 6 changed files with 3,698 additions and 135 deletions.
22 changes: 19 additions & 3 deletions deps/unocss.ts
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]";
3 changes: 0 additions & 3 deletions init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,6 @@ function initPlugins(plugins: string[], denoConfig: DenoConfigResult) {
// Ensure that tailwindcss is loaded before postcss
fixPluginOrder(plugins, "tailwindcss", "postcss");

// Ensure that unocss is loaded before postcss
fixPluginOrder(plugins, "unocss", "postcss");

// Ensure that picture is loaded before imagick
fixPluginOrder(plugins, "picture", "imagick");
}
Expand Down
165 changes: 119 additions & 46 deletions plugins/unocss.ts
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);
}
Loading

0 comments on commit 07f2c05

Please sign in to comment.