denoland / fresh

The next-gen web framework.

Home Page:https://fresh.deno.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support Tailwind v3

vwkd opened this issue · comments

commented

It would be great if fresh could support Tailwind v3. Currently, the twind plugin only supports Tailwind v2.

Tailwind v3 supports among other things custom properties, which are needed for example to use pseudo elements with after:content-['']. Also built-in colors without configuration is nice.

I believe this would essentially require updating to the 1.0 release of twind (which is still in beta) and adding in @twind/preset-tailwind.

commented

I managed to get the 1.0 version of twind + its tailwind preset working with fresh on a personal project.
This might prove useful to someone of you for whom lack of Tailwind 3 support is a dealbreaker. I basically just rewrote the twind plugin with an updated version, using the API's provided by twind 1.0, see sanban/tree/plugins.

@psimk I would like to fork and try the same, but it can't seem to locate sheet in your project. Did I missed something?

commented

@vicary

I am not sure exactly what is missing, could you open an issue in my project? that way we won't pollute this one 😄

Twind 1.0(.1) is now released!

The documentation is now at: twind.style

Here is the official migration guide: twind.style/migration

Now that twind 1 is officially out, it would be great if Fresh twind plugin were updated to support it.

after a lazy replacement of

    "twind": "https://esm.sh/twind@0.16.17",
    "twind/": "https://esm.sh/twind@0.16.17/",

by

    "twind": "https://esm.sh/twind@1.0.2-canary-b1acc2e",
    "twind/": "https://esm.sh/twind@1.0.2-canary-b1acc2e/",

The ESM gateway crash.
see
unexpected...

/* esm.sh - error */
throw new Error("[esm.sh] " + "checkESM: open /tmp/esm-build-49b477dff79a43e0aeab929ce2871cc9ac71af25-f3160f04/node_modules/twind/sheets.mjs: no such file or directory");
export default null;

So it appears to be an import issue. Twind-next hasn't been published to NPM yet, so the latest version we can pull is 0.16.18.

tw-in-js/twind#384

It's not actually an import issue. Twind switched their module name from twind/core to @twind/core. The following imports work:

import { defineConfig } from "https://esm.sh/@twind/core@1.0.3"
import presetTailwind from "https://esm.sh/@twind/preset-tailwind@1.0.1";

If anyone is looking to use twind 1.0+ the following works(at least for me). I'm tempted to open a PR but it's so much less code than the existing plugin that I worry I'm missing behavior that I'm not taking advantage of.

EDIT: The code below doesn't work as expected. I've opened a pr though that should so reference that instead

twindPlugin.ts

import { JSX, options as preactOptions, VNode } from "preact";
import { setup as twindSetup, TwindConfig, Sheet, tw, virtual, stringify } from "https://esm.sh/@twind/core@1.0.3";
import { Plugin } from "$fresh/server.ts";

export const STYLE_ELEMENT_ID = "__FRSH_TWIND";

export interface Options {
  config: TwindConfig;
  selfURL: string;
}

declare module "preact" {
  namespace JSX {
    interface DOMAttributes<Target extends EventTarget> {
      class?: string;
      className?: string;
    }
  }
}

export function setup(options: Options, sheet: Sheet) {
  twindSetup(options.config, sheet);

  const originalHook = preactOptions.vnode;
  // deno-lint-ignore no-explicit-any
  preactOptions.vnode = (vnode: VNode<JSX.DOMAttributes<any>>) => {
    if (typeof vnode.type === "string" && typeof vnode.props === "object") {
      const { props } = vnode;
      const classes: string[] = [];
      if (props.class) {
        classes.push(tw(props.class));
        props.class = undefined;
      }
      if (props.className) {
        classes.push(tw(props.className));
      }
      if (classes.length) {
        props.class = classes.join(" ");
      }
    }

    originalHook?.(vnode);
  };
}

export function plugin(options: Options): Plugin {
  const sheet = virtual();
  setup(options, sheet);
  const main = `data:application/javascript,import hydrate from "${
    new URL("./twind/main.ts", import.meta.url).href
  }";
import options from "${options.selfURL}";
export default function(state) { hydrate(options, state); }`;
  return {
    name: "twind",
    entrypoints: { "main": main },
    render(ctx) {
      const res = ctx.render();
      const  cssText = stringify(sheet.target);
      const scripts = [];
      if (res.requiresHydration) scripts.push({ entrypoint: "main", state: [] });
      return {
        scripts,
        styles: [{ cssText, id: STYLE_ELEMENT_ID }],
      };
    },
  };
}

twind.config.js

import { defineConfig } from "https://esm.sh/@twind/core@1.0.3"
import presetTailwind from "https://esm.sh/@twind/preset-tailwind@1.0.1";

export default defineConfig({
  presets: [presetTailwind()],
});

main.ts

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

import twindConfig from "./twind.config.ts";
import {plugin as twindPlugin2} from './twindPlugin.ts';

await start(manifest, { plugins: [
  twindPlugin2({
    config: twindConfig, 
    selfURL: import.meta.url
  }),
]});

@trescenzi Do you mean sheets are still there, we only need to import it from the new core module instead?

@trescenzi if you have published a twind/core@1.0.3 version on Deno deploy, it's easy to compare the effect on a website.

Simply compare css sheets data size with the default twind integration with your version.

I give is a try I change my mains.ts as:

import { start, Plugin } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
// https://twind.dev/handbook/configuration.html

const plugins: Plugin[] = [];

// twindV1 Plugin
// import twindConfig from "./twindV1.config.ts";
// import twindPluginV1 from "$fresh/plugins/twind.ts";
// plugins.push(twindPluginV1(twindConfig))

// twindV2 Plugin
import twindPluginV2 from './twindPlugin.ts';
import twindConfig from "./twindV2.config.ts";
plugins.push(twindPluginV2({
    config: twindConfig,
    selfURL: import.meta.url
}));

await start(manifest, { plugins });

and pathed twindPlugin.ts to use default export, so V1 and V2 looks the same

@vicary sheets are still there however the behavior is slightly different. It appears that there is very little that is required for this to work as there's now simply a stringify function which if you pass a sheet to it it'll pull everything out you need.

@UrielCh I've got two versions of the Fresh Hacker News demo setup in deno deploy. It appears as the new version is smaller which would be nice. I'll look to clean things up and open a pr. Although I'm still not 100% sure it's not missing some edge case I'm unaware of:

Edit After playing with the v1 version it looks like it's not correctly functioning with islands/hydration which is kinda what I'd expected.

yep, not working in islands, I rolled back to v1.

The code in the pr that's linked should work in islands. If there's an issue though if you could share it on the pr, maybe with the island it's not working with, and I can look into what's still wrong.

@trescenzi, we have mostly 12h of time shift.
I may only try your last version only tomorrow morning.

It's late but I test it, island works better.

more tests tomorrow.

for now you should change in twindv1.ts
export function plugin(options: Options): Plugin {
to
export default function plugin(options: Options): Plugin {

the old twind plugins is exported as a default not as plugin

Oh awesome thanks for taking a look. I'll make that change.

I'm using the code from the PR locally. It looks like the styles are not recomputed when islands update their classes. e.g.
class={`${active ? 'text-red-500' : 'text-blue-500'`} will never update.

I don't use fresh but I did recently get twind 1.0.6 working in deno with React 18. Sharing my example here because I thought it might be helpful for other people's efforts to make twind 1.0.6 work with fresh/preact.

https://deno-twind-with-react.deno.dev/
https://github.com/KyleJune/deno-twind-with-react

I was looking to release the code in my pr as a separate plug-in and came across another version of this code called freshwind. Assuming it doesn't have the issues with the code in my pr it's probably the better solution to this problem.

I'm using the code from the PR locally. It looks like the styles are not recomputed when islands update their classes. e.g.

class={`${active ? 'text-red-500' : 'text-blue-500'`} will never update.

Sweet thank you for this. I'll look into it tomorrow.

I'm still using your fork, I also tried custom animation, and everything is still working fine.

esm.sh v100 or v101 contains breaking change that make @twind/core non functional.

I use these import maps to make it work today:

{
    "@twind/core": "https://esm.sh/v99/@twind/core@1.0.3",
    "@twind/preset-tailwind": "https://esm.sh/v99/@twind/preset-tailwind@1.0.1",
}

v99 is forced.

What's the error that you are getting? I use v100 in my project with no issue, but I'm using the latest @twind/core version. Also I recommend always pinning your version of esm.sh to avoid updates to it breaking your build.

I think that the error was caused by V101, only with old @twind/core versions.

Following the steps outlined in this repository made it work for me:
https://github.com/y3km21/fresh-twindv1-plugin