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) #1966

Closed
wants to merge 38 commits into from
Closed

Conversation

adamgreg
Copy link
Contributor

@adamgreg adamgreg commented Oct 20, 2023

Adds a plugin that can use UnoCSS to generate CSS at different times:

  • During AOT builds (enabled by default)
  • During server-side renders (disabled by default)
  • During client-side rendering (disabled by default)

UnoCSS transformers are not currently supported.

Rebases and succeeds #1303

@adamgreg adamgreg force-pushed the unocss branch 2 times, most recently from ec746d2 to ed55e85 Compare November 2, 2023 16:32
@adamgreg adamgreg marked this pull request as ready for review November 2, 2023 17:56
@adamgreg
Copy link
Contributor Author

adamgreg commented Nov 2, 2023

@marvinhagemeister @mrk could I have your opinions on a few questions please?

  • I think perhaps when the server is running in dev mode, SSR (and maybe CSR?) operation of the plugin should be automatically enabled, even if only AOT is selected in the config.
  • On the client side, would it be better to hook into Preact instead of using the runtime script that UnoCSS provides? It looks like you're trying out that approach with twind at the moment. I suppose that limits the operation to islands, which might be preferable?
  • UnoCSS transformers are not currently supported (and could be deferred), but I can see a few options:
    1. The transformers could modify bodyHtml (I already have code for this, and see that's being tried on the twind-ssr branch)
    2. Does the new pre-compiling of JSX provide an opportunity to apply transformers? (I don't know anything about the details, sorry)
    3. The main use of transformers that I can see, is to support variant groups. We could avoid the need for transformation altogether by using an extractor that uses commas instead of spaces (e.g. hover:(bg-red,c-blue) instead of hover:(bg-red c-blue)).

@kpwebb
Copy link

kpwebb commented Nov 6, 2023

@adamgreg I was investigating making this change myself, and fwiw, i just went through the unocss steps in your PR docs and am loving how it works thus far!

I ran into a couple things during deno deployment:

  1. the docs should probably include the changes required to fresh.config.ts to include the uno plugin. I just updated mine to match the main.ts file:
import { defineConfig } from "$fresh/server.ts";
import unocssPlugin from "$fresh/plugins/unocss.ts";
import unocssConfig from "./uno.config.ts";

export default defineConfig({
  plugins: [unocssPlugin(unocssConfig)],
});
  1. Even with the config updated it doesn't look like deno deploy is generating the uno.css file when the build step runs (it appears locally in _fresh/static/uno.css, but I get a 404 on uno.css on the server. fwiw, I changed my uno.config.ts to ssr=true and the build worked.

@adamgreg
Copy link
Contributor Author

adamgreg commented Nov 7, 2023

@adamgreg I was investigating making this change myself, and fwiw, i just went through the unocss steps in your PR docs and am loving how it works thus far!

I ran into a couple things during deno deployment:

1. the docs should probably include the changes required to `fresh.config.ts` to include the uno plugin. I just updated mine  to match the `main.ts` file:
import { defineConfig } from "$fresh/server.ts";
import unocssPlugin from "$fresh/plugins/unocss.ts";
import unocssConfig from "./uno.config.ts";

export default defineConfig({
  plugins: [unocssPlugin(unocssConfig)],
});
2. Even with the config updated it doesn't look like deno deploy is generating the uno.css file when the build step runs (it appears locally in `_fresh/static/uno.css`, but I get a 404 on `uno.css` on the server. fwiw, I changed my `uno.config.ts` to `ssr=true` and the build worked.

Thanks! Much of the credit to @miguelrk.

About the arguments to the plugin: I think you've identified that the docs are out of date! I'll update them (EDIT: done). If the Uno config is in a uno.config.ts module, the plugin should pick it up automatically, no need to pass it as an argument.

I haven't tried Deno Deploy yet. Does it work when you run locally? Do you have the github action set up to perform the build step on Deploy? https://fresh.deno.dev/docs/concepts/ahead-of-time-builds#deploying-an-optimized-fresh-project

@kpwebb
Copy link

kpwebb commented Nov 7, 2023

@adamgreg ah thanks for the clarification! i was missing the switch to the GH action build step for AOT. I just added the deploy.yml and switched the project back to AOT (and disabled SSR) and it now works on Deno Deploy!

@kpwebb
Copy link

kpwebb commented Nov 30, 2023

I ran into a problem with AoT deployment using the UnoCSS PR with the UnoCSS iconify preset.

It appears that the UnoCSS integration defaults to CDN load of icon data sets during instance startup, causing a multi-second wait for Deno cold starts (or occasional failure due to network problems). I dug into the iconify code a bit, and found that the Node loader is explicitly disabled on Deno:

https://github.com/unocss/unocss/blob/859cae6e3735c15b8738065d94c3550d9177490f/packages/preset-icons/src/index.ts#L28

The UnoCSS docs also mention a dynamic import option using the preset config. I attempted this, and it appeared to work locally, but it did not work on an AoT Deno Deploy instance (no errors, but also no icons). Here's the config I tried:

    collections : {
      ph: () => import('https://esm.sh/@iconify-json/[email protected]').then(i => i.default)
    },

After making this change the Deno Deploy logs showed the ph icon set was downloaded at build time, but perhaps still aren't getting bundled into the app correctly?

Downloaded https://esm.sh/@iconify-json/[email protected]

I'm not sure how to debug this on the deployed instance, but I suspect that an upstream change to Uno might be required to support icons on Deno.

@adamgreg
Copy link
Contributor Author

Hi @kpwebb, that's interesting - the Iconify preset would crash the last time I tried it, because it did not include that isNode check.

When the plugin runs in AOT mode it should build all of the CSS (including icons) at build time.
It shouldn't have to bundle the icon set, because UnoCSS won't be running at runtime. What I would expect to see if the SVG embedded in the generated uno.css file. I haven't tried myself, but you could try checking that file after a local build.

@kpwebb
Copy link

kpwebb commented Nov 30, 2023

@adamgreg oh fascinating! So they are getting included in the uno.css on local build (and only the icons I'm using the app).

What should be happening in a AOT build in terms of caching? Is each static paged stored or does it also generate the uno.css and then merge that for each page render?

It would be really useful to be able to inspect the Deno isolate bundle and see what's actually getting stored -- is that possible?

@kpwebb
Copy link

kpwebb commented Nov 30, 2023

I just dug into the local build in ./_fresh and can't see anywhere that it stores the the ability to make a dynamic request. But given the logs generated by the Deno Deploy builder that's happening each time the isolate is spun up (see attached error with an errant 404 from esm.sh, I also occasionally get 500s from ems for this content). This makes me suspect that something very different is happening on Deploy than is happening in my local build, but I don't know how to debug the Deploy instance.

Screenshot from 2023-11-29 20-52-12

@adamgreg
Copy link
Contributor Author

adamgreg commented Dec 2, 2023

hi @kpwebb , I haven't tried UnoCSS icons myself, but what I've read an interesting blog post (https://antfu.me/posts/icons-in-pure-css) which implies they should end up embedded within the CSS file generated by CSS. I would expect the AOT mode of the Fresh plugin to result in the used icon classes (with embedded SVG data) in the uno.css file. AFAIK the main AOT build of Fresh just pre-bundles the islands and doesn't otherwise affect server-side renders.

I'm afraid I haven't tried Deno Deploy. If the plugin is instantiated with the default settings (like unocssPlugin({aot: true, ssr: false, csr: false})) then it shouldn't be running UnoCSS after the build. That 404ed fetch seems to be originating from your imports, and is odd since the URL seems accessible. It's a wild guess, but I don't suppose Deploy might have tweaked DNS to handle esm.sh requests from their own local deployment that lacks JSONs? Maybe try asking for help in the deploy channel in Deno's Discord?

plugins/unocss.ts Outdated Show resolved Hide resolved
plugins/unocss.ts Outdated Show resolved Hide resolved
@kpwebb
Copy link

kpwebb commented Dec 3, 2023

@adamgreg thanks for that very thoughtful response! I've asked around on the Deploy channel but haven't found a way to debug this reliably. The AOT build did include the icons in the static.css, but the server still appeared to make an additional request when deployed.

I decided after yesterday's 1.6 release to table this, and just use the new Tailwind integration. Iconify appears to work correctly via Tailwind so I suspect the issue is in that Tailwind/Uno Iconify integration point.

I'll follow up if I can find anything new, and but will probably wait until the Uno PR is fully merged into a Fresh release before I bug the Deno folks for more info about deployment.

miguelrk and others added 19 commits December 19, 2023 17:25
-  Define a Config interface for the plugin, which extends UnoCSS UserConfig with a selfURL property
- Add a `defineConfig()` function for help with types, and make it easier to adapt existing UnoCSS config modules for use with the plugin
- Move the runtime script inline
Use `init()` function of `@unocss/runtime` instead of manually injecting the script tag to avoid fetching it (redundantly) in the client, and instead ship runtime with `plugin-unocss.main.ts`.

Co-authored-by: 木杉 <[email protected]>
- Import config from uno.config.ts if no config object is explicitly provided
- Always use uno.config.ts as the import source for the browser runtime config
- This avoids the complexity of selfURL, allowing the plugin to use standard Uno config files
It is the same as found in the unocss package, but that can not currently be safely imported in Deno (due to Node-specific code in the icons preset).
Use tailwind-compat.css instead of tailwind.css as the source. This resolves a specificity problem affecting button backgrounds: unocss/unocss#2127
- Add separate SSR, CSR & AOT config options
- Bump UnoCSS version to 0.56.5
- Use middleware to maintain "AOT" behaviour in dev mode
- Use new configResolved() plugin hook to resolve UnoCSS config file
- Reduce effort during renders in SSR mode
@adamgreg
Copy link
Contributor Author

adamgreg commented Sep 5, 2024

Let's close this PR. When Fresh 2 is released, I think we can revive it as a JSR package.

@adamgreg adamgreg closed this Sep 5, 2024
@rmortes
Copy link

rmortes commented Nov 12, 2024

Hi! Is this package being worked on right now? I've implemented unocss (badly) in my current codebase, but I'd be interested in contributing to a package to make it work correctly. Please let me know if/how I can help!

@adamgreg
Copy link
Contributor Author

Hi @rmortes , I haven't made a JSR package yet.. I'm still waiting to adapt my codebase once Fresh 2 is eventually released.

I'm still using Fresh 1, with the UnoCSS plugin implemented as a single file in my codebase, and I would say it works well. I've attached it to this comment - feel free to use it. I had to zip it, since GitHub wouldn't accept the .ts file itself directly.

unocss_plugin.zip

If you add that to your project, along with a normal uno.config.ts file, and add the plugin to your Fresh config, it should just work.

@rmortes
Copy link

rmortes commented Nov 12, 2024

Hi @adamgreg! Thanks for the code, it's working great so far! Maybe Github wanted you to upload it as a Gist instead of a file? Anyways, I copied it to my codebase and as far as I can tell everything is working as intended.

I added a transformer to my unocss config (transformerCompileClass) but it doesn't work. Which I guess makes sense, since there's no code transformation step, I think? Do you know where could be an implementation of the transformation step so that I could try and implement it in Fresh? Thanks!

@adamgreg
Copy link
Contributor Author

I can't think of an efficient way to use transformers, sorry

@rmortes
Copy link

rmortes commented Nov 13, 2024

I'm the same hahaha. I'll just keep chipping at it then, many thanks for the code again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants