Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): add unocss plugin (WIP) #1303

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
32ad5ca
feat(plugins): add unocss plugin (WIP)
miguelrk Jun 15, 2023
b070bcd
fix(plugins/unocss): use presetUno instead of presetWind by default
miguelrk Jul 25, 2023
4e09931
chore(plugins): add comment to inline reset for unocss
miguelrk Aug 1, 2023
ca6461c
chore(plugins): update comment of inline reset for unocss
miguelrk Aug 1, 2023
9c38da6
Merge branch 'denoland:main' into main
miguelrk Aug 8, 2023
2bf9ce0
docs(plugins): add docs for unocss
miguelrk Aug 8, 2023
2a64f85
fix(plugins): remove unnecessary `?bundle&no-check` from `@unocss/pre…
miguelrk Aug 8, 2023
86c8227
docs(plugins): remove redundant text for unocss
miguelrk Aug 8, 2023
b397d6f
fix(plugins): remove defaults from unocss plugin
miguelrk Aug 8, 2023
c6e81c3
chore(plugins): add `fixture_unocss_hydrate` test
miguelrk Aug 9, 2023
2f06ee5
chore(plugins): bump `@unocss/reset` version
miguelrk Aug 9, 2023
a6f1075
chore(plugins): bump `@unocss/core` version
miguelrk Aug 9, 2023
656d28b
chore(plugins): bump `@unocss/core` and `@unocss/preset-uno` version
miguelrk Aug 9, 2023
e4a518c
chore(plugins): format via `deno fmt`
miguelrk Aug 9, 2023
27a773f
feat(plugins): add `antfu.unocss` to recommended vscode extensions
miguelrk Aug 9, 2023
c4c901b
chore(ci): try fixing broken types causing CI to fail
miguelrk Aug 9, 2023
0d4133c
chore(plugins): order imports
miguelrk Aug 9, 2023
49b5c86
chore(plugins): use import map instead of url imports for unocss
miguelrk Aug 9, 2023
a7243b1
feat(plugins): add client runtime script to unocss plugin by default
miguelrk Aug 10, 2023
19a92c1
chore(plugins): unify imports form `@unocss/core`
miguelrk Aug 10, 2023
25fcfc6
Re-export UserConfig type from UnoCSS plugin
adamgreg Aug 17, 2023
5f50efe
Import @unocss/preset-uno from full URL in test
adamgreg Aug 17, 2023
2fab2d2
Update UnoCSS in plugin to 0.55.1
adamgreg Aug 17, 2023
954180b
In the UnoCSS plugin pass the config to runtime
adamgreg Aug 20, 2023
6be1dad
Merge pull request #1 from adamgreg/main
miguelrk Aug 20, 2023
0d880b7
chore(plugins): format via `deno fmt`
miguelrk Aug 20, 2023
4f5695c
Merge branch 'main' into main
miguelrk Aug 20, 2023
1d2a5e1
Update plugins/unocss.ts
miguelrk Aug 23, 2023
220e1a5
chore(plugins): update querySelector to style[data-unocss-runtime-lay…
miguelrk Aug 23, 2023
bfd2bfe
Merge branch 'main' of https://github.com/miguelrk/fresh
miguelrk Aug 23, 2023
1b00653
Update docs/latest/examples/using-unocss.md
miguelrk Aug 23, 2023
ce4f64d
UnoCSS plugin use uno.config.ts
adamgreg Sep 13, 2023
6c46559
UnoCSS plugin: Add defineConfig() helper function
adamgreg Sep 13, 2023
d9388d2
Merge pull request #2 from adamgreg/config_file
miguelrk Sep 13, 2023
09c8116
Merge branch 'denoland:main' into main
miguelrk Sep 13, 2023
e22f03d
Make UnoCSS plugin function synchronous
adamgreg Sep 13, 2023
09555e8
Merge pull request #3 from adamgreg/config_file
miguelrk Sep 14, 2023
ade4b9d
Merge branch 'main' into main
miguelrk Sep 14, 2023
800574d
UnoCSS Plugin: Use Preact hook for SSR
adamgreg Oct 4, 2023
69f9aca
Merge pull request #4 from adamgreg/main
miguelrk Oct 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"recommendations": [
"denoland.vscode-deno",
"sastan.twind-intellisense"
"sastan.twind-intellisense",
"antfu.unocss"
]
}
2 changes: 1 addition & 1 deletion docs/latest/examples/using-twind-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default {
};
```

(Note: the `as Preset` cast is required to fix a typing issue with twind.)
Note: the `as Preset` cast is required to fix a typing issue with twind.

To see what other presets exist, you can go to the
[twind docs](https://twind.style/presets).
44 changes: 44 additions & 0 deletions docs/latest/examples/using-unocss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
description: |
One can use UnoCSS, an instant on-demand atomic CSS engine
---

The template generates a Twind v0 project by default. If you want to use UnoCSS
miguelrk marked this conversation as resolved.
Show resolved Hide resolved
you can update the `main.ts` as follows:

```ts
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />

import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";

import unocssPlugin from "$fresh/plugins/unocss.ts";
import unocssConfig from "./uno.config.ts";

await start(manifest, { plugins: [unocssPlugin(unocssConfig)] });
```

The unocss config object at `uno.config.ts` can be customized to your liking.
Refer to the [unocss docs](https://unocss.dev/guide/config-file) for more
information. If no config is provided, the default config is used, which
defaults to the following:

```ts
import { defineConfig } from "$fresh/plugins/unocss.ts";
import presetUno from "https://esm.sh/@unocss/[email protected]";

export default defineConfig({
presets: [presetUno()],
selfURL: import.meta.url,
});
```

Note: you could also inline the config object in `main.ts` instead of using a
separate `uno.config.ts` file.

To see what other presets exist, you can go to the
[unocss docs](https://unocss.dev/presets/).
2 changes: 2 additions & 0 deletions docs/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const toc: RawTableOfContents = {
"link:latest",
],
["using-twind-v1", "Using Twind v1", "link:latest"],
["using-unocss", "Using UnoCSS", "link:latest"],
["init-the-server", "Initializing the server", "link:latest"],
[
"using-fresh-canary-version",
Expand Down Expand Up @@ -152,6 +153,7 @@ const toc: RawTableOfContents = {
["writing-tests", "Writing tests"],
["changing-the-src-dir", "Changing the source directory"],
["using-twind-v1", "Using Twind v1"],
["using-unocss", "Using UnoCSS"],
["init-the-server", "Initializing the server"],
["using-fresh-canary-version", "Using Fresh canary version"],
["dealing-with-cors", "Dealing with CORS"],
Expand Down
122 changes: 122 additions & 0 deletions plugins/unocss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { JSX, options as preactOptions, VNode } from "preact";

import {
UnoGenerator,
type UserConfig,
} from "https://esm.sh/@unocss/[email protected]";
import type { Theme } from "https://esm.sh/@unocss/[email protected]";

import { Plugin } from "$fresh/server.ts";
import { exists } from "$fresh/src/server/deps.ts";

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

// inline reset from https://esm.sh/@unocss/[email protected]/tailwind.css
const unoResetCSS = `/* reset */
miguelrk marked this conversation as resolved.
Show resolved Hide resolved
*,:before,:after{box-sizing:border-box;border:0 solid}html{-webkit-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;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;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-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-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}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}
`;

type UnoCssPluginOptions = {
runtime?: boolean;
config?: UserConfig;
};

/**
* Helper function for typing of config objects
*/
export function defineConfig<T extends object = Theme>(config: UserConfig<T>) {
return config;
}

/**
* Installs a hook in Preact to extract classes during server-side renders
* @param classes - Set of class strings, which will be mutated by this function.
*/
export function installPreactHook(classes: Set<string>) {
// Hook into options._b which is called whenever a new comparison
// starts in Preact.
const originalHook = (preactOptions as PreactOptions).__b;
(preactOptions as PreactOptions).__b = (
// deno-lint-ignore no-explicit-any
vnode: VNode<JSX.DOMAttributes<any>>,
) => {
if (typeof vnode.type === "string" && typeof vnode.props === "object") {
const { props } = vnode;
if (props.class) {
props.class.split(" ").forEach((cls) => classes.add(cls));
}
if (props.className) {
props.className.split(" ").forEach((cls) => classes.add(cls));
}
}

originalHook?.(vnode);
};
}

/**
* 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.
*/
export default function unocss(
opts: 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 {
import(configURL.toString()).then((mod) => {
uno = new UnoGenerator(mod.default);
}).catch((error) => {
exists(configURL, { isFile: true, isReadable: true }).then(
(configFileExists) => {
throw configFileExists ? error : new Error(
"uno.config.ts not found in the project directory! Please create it or pass a config object to the UnoCSS plugin",
);
},
);
});
}

return {
name: "unocss",
entrypoints: runtime
? {
"main": `
data:application/javascript,
import config from "${configURL}";
import init from "https://esm.sh/@unocss/[email protected]";
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}` }],
};
},
};
}
1 change: 1 addition & 0 deletions src/server/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
toFileUrl,
} from "https://deno.land/[email protected]/path/mod.ts";
export { walk } from "https://deno.land/[email protected]/fs/walk.ts";
export { exists } from "https://deno.land/[email protected]/fs/exists.ts";
export * as colors from "https://deno.land/[email protected]/fmt/colors.ts";
export {
type Handler as ServeHandler,
Expand Down
2 changes: 1 addition & 1 deletion tests/fixture_twind_hydrate/islands/InsertCssrules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export default function InsertCssrules() {
}}
disabled={insertedStyles.value === "" ? false : true}
>
Add `text-green-600` to Cureent Number Class
Add `text-green-600` to Current Number Class
</button>
</div>
);
Expand Down
18 changes: 18 additions & 0 deletions tests/fixture_unocss_hydrate/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"lock": false,
"tasks": {
"start": "deno run -A --watch=static/,routes/ dev.ts"
},
"imports": {
"$fresh/": "../../",
"preact": "https://esm.sh/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
"preact-render-to-string": "https://esm.sh/*[email protected]",
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}
5 changes: 5 additions & 0 deletions tests/fixture_unocss_hydrate/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env -S deno run -A --watch=static/,routes/

import dev from "$fresh/dev.ts";

await dev(import.meta.url, "./main.ts");
26 changes: 26 additions & 0 deletions tests/fixture_unocss_hydrate/fresh.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// DO NOT EDIT. This file is generated by Fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.

import * as $0 from "./routes/check-duplication.tsx";
import * as $1 from "./routes/insert-cssrules.tsx";
import * as $2 from "./routes/static.tsx";
import * as $3 from "./routes/unused.tsx";
import * as $$0 from "./islands/CheckDuplication.tsx";
import * as $$1 from "./islands/InsertCssrules.tsx";

const manifest = {
routes: {
"./routes/check-duplication.tsx": $0,
"./routes/insert-cssrules.tsx": $1,
"./routes/static.tsx": $2,
"./routes/unused.tsx": $3,
},
islands: {
"./islands/CheckDuplication.tsx": $$0,
"./islands/InsertCssrules.tsx": $$1,
},
baseUrl: import.meta.url,
};

export default manifest;
59 changes: 59 additions & 0 deletions tests/fixture_unocss_hydrate/islands/CheckDuplication.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// https://github.com/denoland/fresh/pull/1050
import { useEffect } from "preact/hooks";
import { cmpCssRules } from "../utils/utils.ts";
import { useSignal } from "@preact/signals";

/**
* Returns a cssrulelist of styleElement matching the selector.
*/
function getCssrules(selector: string) {
const elem = document.querySelector(selector) as HTMLStyleElement;
return elem?.sheet?.cssRules;
}

export default function CheckDuplication() {
const cssRulesFRSHUNOCSS = useSignal<undefined | CSSRuleList>(undefined);
const cssRulesClaimed = useSignal<undefined | CSSRuleList>(undefined);

// Init
useEffect(() => {
// get <style>
cssRulesFRSHUNOCSS.value = getCssrules("style");

// get <style>
cssRulesClaimed.value = getCssrules(":not(style)");
});

return (
<div class="p-2">
{/* At least one class is required in the islands for hydrate to work. */}
<h2>Check duplicated cssrules</h2>

{/* Status of duplicates */}
{(() => {
if (cssRulesFRSHUNOCSS.value != null && cssRulesClaimed.value != null) {
return (
<div>
<p>Error :</p>
<p id="numDuplicates">
{`${
cmpCssRules(
cssRulesFRSHUNOCSS.value,
cssRulesClaimed.value,
)
}`}
</p>
<p>cssrules are duplicated</p>
</div>
);
} else if (
cssRulesFRSHUNOCSS.value != null && cssRulesClaimed.value == null
) {
return <p id="okNoDuplicates">Ok : No duplicates</p>;
} else {
return <p id="errorNoExistsRules">Error : Cssrules does not exist</p>;
}
})()}
</div>
);
}
Loading
Loading