Skip to content

Commit

Permalink
Support AOT build in UnoCSS plugin
Browse files Browse the repository at this point in the history
- Add separate SSR, CSR & AOT config options
- Bump UnoCSS version to 0.56.5
  • Loading branch information
adamgreg committed Nov 2, 2023
1 parent 8533dfe commit ec746d2
Showing 1 changed file with 101 additions and 38 deletions.
139 changes: 101 additions & 38 deletions plugins/unocss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,55 @@ import { JSX, options as preactOptions, VNode } from "preact";
import {
UnoGenerator,
type UserConfig,
} from "https://esm.sh/@unocss/core@0.55.1";
import type { Theme } from "https://esm.sh/@unocss/preset-uno@0.55.1";
} from "https://esm.sh/@unocss/core@0.56.5";
import type { Theme } from "https://esm.sh/@unocss/preset-uno@0.56.5";

import { Plugin } from "$fresh/server.ts";
import { exists } from "$fresh/src/server/deps.ts";
import { Plugin, type PluginRenderStyleTag } from "$fresh/server.ts";
import {
dirname,
exists,
fromFileUrl,
join,
walk,
} from "$fresh/src/server/deps.ts";

type PreactOptions = typeof preactOptions & { __b?: (vnode: VNode) => void };

// inline reset from https://esm.sh/@unocss/reset@0.56.5/tailwind-compat.css
// Regular expression to support @unocss-skip-start/end comments in source code
const SKIP_START_COMMENT = "@unocss-skip-start";
const SKIP_END_COMMENT = "@unocss-skip-end";
const SKIP_COMMENT_RE = new RegExp(
`(\/\/\\s*?${SKIP_START_COMMENT}\\s*?|\\/\\*\\s*?${SKIP_START_COMMENT}\\s*?\\*\\/|<!--\\s*?${SKIP_START_COMMENT}\\s*?-->)[\\s\\S]*?(\/\/\\s*?${SKIP_END_COMMENT}\\s*?|\\/\\*\\s*?${SKIP_END_COMMENT}\\s*?\\*\\/|<!--\\s*?${SKIP_END_COMMENT}\\s*?-->)`,
"g",
);

// Inline reset from https://esm.sh/@unocss/reset@0.56.5/tailwind-compat.css
const unoResetCSS = `/* reset */
a,hr{color:inherit}progress,sub,sup{vertical-align:baseline}blockquote,body,dd,dl,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,menu,ol,p,pre,ul{margin:0}fieldset,legend,menu,ol,ul{padding:0}*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:var(--un-default-border-color,#e5e7eb)}html{line-height:1.5;-webkit-text-size-adjust:100%;text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}body{line-height:inherit}hr{height:0;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}menu,ol,ul{list-style:none}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}
`;

type UnoCssPluginOptions = {
runtime?: boolean;
/**
* Explicit UnoCSS config object. By default `uno.config.ts` file.
* Not supported for the client runtime in JIT mode.
*/
config?: UserConfig;
/**
* Enable AOT mode - run UnoCSS to extract styles during the build task.
* Enabled by default.
*/
aot?: boolean;
/**
* Enable SSR mode - run UnoCSS live to extract styles during server renders.
* Disabled by default.
*/
ssr?: boolean;
/**
* Enable CSR mode - Run the UnoCSS runtime on the client.
* It will generate styles live in response to DOM events.
* Disabled by default.
*/
csr?: boolean;
};

/**
Expand Down Expand Up @@ -54,35 +87,18 @@ export function installPreactHook(classes: Set<string>) {
};
}

/**
* UnoCSS plugin - automatically generates CSS utility classes
*
* @param [opts] Plugin options
* @param [opts.runtime] By default the UnoCSS runtime will run in the browser. Set to `false` to disable this.
* @param [opts.config] Explicit UnoCSS config object. By default `uno.config.ts` file. Not supported with the browser runtime.
*/
/** UnoCSS plugin - automatically generates CSS utility classes */
export default function unocss(
opts: UnoCssPluginOptions = {},
{ config, aot = true, ssr = false, csr = false }: UnoCssPluginOptions = {},
): Plugin {
// Include the browser runtime by default
const runtime = opts.runtime ?? true;

// A uno.config.ts file is required in the project directory if a config object is not provided,
// or to use the browser runtime
const configURL = new URL("./uno.config.ts", Deno.mainModule);

// Create a set that will be used to hold class names encountered during SSR
const classes = new Set<string>();

// Hook into Preact to add to the set of classes on each server-side render
installPreactHook(classes);

let uno: UnoGenerator;
if (opts.config !== undefined) {
uno = new UnoGenerator(opts.config);
} else {
// Load config from file if required
if (config === undefined) {
import(configURL.toString()).then((mod) => {
uno = new UnoGenerator(mod.default);
config = mod.default;
}).catch((error) => {
exists(configURL, { isFile: true, isReadable: true }).then(
(configFileExists) => {
Expand All @@ -96,27 +112,74 @@ export default function unocss(

return {
name: "unocss",
entrypoints: runtime
// Optional client runtime
entrypoints: csr
? {
"main": `
data:application/javascript,
import config from "${configURL}";
import init from "https://esm.sh/@unocss/runtime@0.55.1";
import init from "https://esm.sh/@unocss/runtime@0.56.5";
export default function() {
window.__unocss = config;
init();
}`,
}
: {},

async renderAsync(ctx) {
classes.clear();
await ctx.renderAsync();
const { css } = await uno.generate(classes);

return {
scripts: runtime ? [{ entrypoint: "main", state: {} }] : [],
styles: [{ cssText: `${unoResetCSS}\n${css}` }],
};
// Link to CSS file, if AOT mode is enabled
const cssLinks = aot ? [{ url: "/_frsh/uno.css" }] : [];

// Add entrypoint, if CSR mode is enabled
const scripts = csr ? [{ entrypoint: "main", state: {} }] : [];

// Generate inline styles, if SSR mode is enabled
const styles: PluginRenderStyleTag[] = [];
if (ssr) {
// Create a set that will be used to hold class names encountered during SSR
const classes = new Set<string>();

// Hook into Preact to add to the set of classes during the render
installPreactHook(classes);

// Render. Preact will populate the list of classes.
await ctx.renderAsync();

// Run UnoCSS over the classes to generate CSS
const uno = new UnoGenerator(config);
const { css } = await uno.generate(classes);
styles.push({ cssText: `${unoResetCSS}\n${css}` });
} else {
await ctx.renderAsync();
}

return { scripts, styles, cssLinks };
},

async buildStart({ build: { outDir } }) {
// Find source files
const projectDir = fromFileUrl(dirname(Deno.mainModule));
const sourceFiles = [];
for await (
const entry of walk(projectDir, {
includeDirs: false,
exts: [".tsx", ".jsx", ".html"],
})
) {
sourceFiles.push(entry.path);
}

// Read the source code into a single string
const sourceCode = await Promise.all(
sourceFiles.map((filename) => Deno.readTextFile(filename)),
).then((x) => x.join("\n").replace(SKIP_COMMENT_RE, ""));

// Extract UnoCSS classes from the source code and generate CSS
const uno = new UnoGenerator(config);
const { css } = await uno.generate(sourceCode);

// Write the generated CSS to a static file
await Deno.writeTextFile(join(outDir, "uno.css"), unoResetCSS + css);
},
};
}

0 comments on commit ec746d2

Please sign in to comment.