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

Support tailwind for routes and islands from plugins #2159

Open
deer opened this issue Dec 7, 2023 · 8 comments · May be fixed by #2266
Open

Support tailwind for routes and islands from plugins #2159

deer opened this issue Dec 7, 2023 · 8 comments · May be fixed by #2266

Comments

@deer
Copy link
Contributor

deer commented Dec 7, 2023

Currently the tailwind plugin doesn't styles routes and islands provided by plugins.

@stepbrobd
Copy link

Do you mind sharing your tailwind.config.ts?

It does styles routes and islands if those folders are included in content: content: [{components,islands,routes}/**/*.{ts,tsx}"]

@deer
Copy link
Contributor Author

deer commented Dec 9, 2023

This was mainly a note to myself and for the ability to link this issue when someone else reports the problem.

I'm well aware it styles routes/islands if listed, but the issue is that routes and islands provided by plugins don't get styled. By their very nature they don't exist on the filesystem.

So I've started exploring a few options. Two workarounds that unblock people right now:

  1. Vendoring the dependencies provides a workaround, in that the plugin source files then exist within the project. This is obviously a horrible user experience.
  2. Plugin authors (or motivated users) can provide a safelist containing all the classes.

So as an example I have fresh.config.ts

import { defineConfig } from "$fresh/server.ts";
import tailwind from "$fresh/plugins/tailwind.ts";

import { blogPlugin } from "../../src/plugin/blog.ts";
import localConfig from "./local.config.ts";

export default defineConfig({
  plugins: [tailwind(), blogPlugin(localConfig)],
});

and tailwind.config.ts

import { type Config } from "tailwindcss";

export default {
  content: [
    "{routes,islands,components}/**/*.{ts,tsx}",
  ],
  safelist: [
    "max-w-screen-md",
    "px-4",
    "etc etc"
  ],
} satisfies Config;

This would require some utility for plugin authors to easily scan their classes and generate the list. (Generating it by hand is error prone and annoying, as I've recently discovered.) Then users can just copy and paste the safelist into their config and the styles will get created (or more accurately: not removed).

And what to do moving forward? The only viable option I see is to enhance the plugin API and the existing tailwind plugin. I suspect we would need to do something like the following:

  1. Allow plugins to declare files that have tailwind classes.
  2. Enhance Fresh's tailwind plugin to do something with that list of files.

Because tailwind's content configuration does not support URL paths, it's not as simple as just merging the list of files from plugins with those the user has provided. Perhaps the simplest is to generate our own safelist and merge it with whatever the user has provided. (Hopefully the user has provided nothing, since the tailwind docs say: Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names.) The Fresh tailwind plugin would then basically be doing an automatic version of my second workaround mentioned previously.

The above is valid for devmode, where we have no build step. But if we're building something for deployment, then we could consider pulling in the list of files into _fresh and then referencing them there from the content array. This would simplify things, at least for this case.

I'll continue thinking about the problem, but I'm happy to receive feedback on the above or any other ideas.

@mcgear
Copy link
Contributor

mcgear commented Jan 8, 2024

i am seeing this issue. We use a set of atomic controls that are in sub libraries. These are not all islands, but rather are just reusable components in many cases (for layouts and common controls).

Right now, we've lost all styling on any of our subcomponents.

I tried adding something like this to the tailwind config

  content: [
    "{routes,islands,components}/**/*.{ts,tsx}",
    "https://raw.githubusercontent.com/fathym-deno/atomic-iot/integration/src/molecules/IoTDisplay.tsx",
  ],

But no luck with that. I'm working on creating a safelist for our packages for now, wondering what it might take to get this more plugged in.

@mcgear
Copy link
Contributor

mcgear commented Jan 8, 2024

I also just tried this:

  content: [
    "{routes,islands,components}/**/*.{ts,tsx}",
    // "C:\\Users\\{micha_8ygdgy8}\\AppData\\Local\\deno\\deps\\https\\deno.land\\**\\*",
    // "https://raw.githubusercontent.com/fathym-deno/atomic-iot/integration/src/molecules/IoTDisplay.tsx",
    "%LOCALAPPDATA%/deno/deps/https/deno.land/**/*",
  ],

Thinking that if i pointed it where everything is downloaded that might work, but no luck

@deer
Copy link
Contributor Author

deer commented Jan 9, 2024

@mcgear, is vendoring an option for you? That will put the plugin source files in your project directory, allowing tailwind to scan them.

@mcgear
Copy link
Contributor

mcgear commented Jan 9, 2024

Vendoring is a new concept to me, it felt like a simple stop gap solution would be better than switching to vendoring. We ended up taking a slightly different approach that is sort of a hybrid (i think) between vendoring and safelisting. I ended up creating a few helper methods that allow me to generate a file in each of the libraries that contains the meta.url and all of the tsx files. For instances where libraries export out the other libraries, the new helpers generate a config with all the components. Finally, in the fresh application, the exported components are combined into a .config (text) file and the tailwind config is updated to look into that file.

This way i don't have to safelist the tailwind classes (that was quite the endeavor), and the components being exported can be automated. Its a large file, but since tailwind setup is only incurred at startup, it seems to be working for me.

Source for the helpers can be found here:
https://github.com/fathym-deno/reference-architecture/blob/integration/src/utils/tailwind.helpers.ts

The dev pattern is as follows...

In the library that has tailwind classes that need to be added:

Updates to the deno.json (https://github.com/fathym-deno/atomic/blob/integration/deno.jsonc):

{
  "tasks": {
    "build:tailwind": "deno run -A tailwind-components.config.ts",
  },
  "imports": {
    "$std/": "https://deno.land/std@0.211.0/"
  },
}

And then in my src.deps.ts file (https://github.com/fathym-deno/atomic/blob/integration/src/src.deps.ts):

export * from "https://deno.land/x/fathym_common@v0.0.127/mod.ts";

Then we add a tailwind-components.config.ts file (https://github.com/fathym-deno/atomic/blob/integration/tailwind-components.config.ts):

import { constructTailwindComponentsConfig } from "./src/src.deps.ts";

await constructTailwindComponentsConfig(
  import.meta,
  [
    {
      Directory: "./src",
      Extensions: [".tsx"],
    },
  ],
);

Once this is done, we execute the following command as part of our build task (or on its own):

deno task build:tailwind

After that is run, a new tailwind.components.ts file is generated:

export default [
	{
		Root: import.meta.resolve("./"),
		Components: [
			"./src/atoms/Action.tsx",
			"./src/atoms/forms/Input.tsx",
			"./src/atoms/forms/SlideToggle.tsx",
			"./src/molecules/ActionGroup.tsx",
			"./src/molecules/Display.tsx",
			"./src/molecules/LineItem.tsx",
			"./src/molecules/MenuButton.tsx",
			"./src/molecules/ResponsiveSet.tsx",
			"./src/molecules/Tabs.tsx",
			"./src/organisms/Features.tsx",
			"./src/organisms/Footer.tsx",
			"./src/organisms/Header.tsx",
			"./src/organisms/Hero.tsx",
			"./src/organisms/SignIn.tsx",
			"./src/organisms/SignUp.tsx",
			"./src/organisms/StepsFeatures.tsx",
			"./src/templates/BasicLayout.tsx",
		],
	},
];

In the case where there is a library that combines sub libraries there are a couple of additional changes...

In the src.deps.ts file, we import and export the tailwind-components.ts file from the child library (https://github.com/fathym-deno/fathym-atomic/blob/integration/src/src.deps.ts):

import FATC from "https://deno.land/x/fathym_atomic@v0.0.100/tailwind.components.ts";
export const FathymAtomicTailwindComponents = FATC;

And then an update to the tailwind-components.config.ts file (https://github.com/fathym-deno/fathym-atomic/blob/integration/tailwind-components.config.ts):

import {
  constructTailwindComponentsConfig,
  FathymAtomicTailwindComponents,
} from "./src/src.deps.ts";

await constructTailwindComponentsConfig(
  import.meta,
  [
    {
      Directory: "./src",
      Extensions: [".tsx"],
    },
  ],
  [
    ...FathymAtomicTailwindComponents,
  ],
);

Then, when the build:tailwind task is run, the tailwind.components.ts file is generated with the child library too:

export default [
	{
		Root: import.meta.resolve("./"),
		Components: [
			"./src/atoms/Logo.tsx",
			"./src/molecules/azure/connect.form.tsx",
			"./src/molecules/azure/existing.form.tsx",
			"./src/molecules/eac/calz.form.tsx",
			"./src/molecules/eac/connect.form.tsx",
			"./src/molecules/eac/create.form.tsx",
			"./src/organisms/FathymHeader.tsx",
			"./src/organisms/feed/BuildFeedCard.tsx",
		],
	},
	{
		Root: 'https://deno.land/x/fathym_atomic@v0.0.100/',
		Components: [
			"./src/atoms/Action.tsx",
			"./src/atoms/forms/Input.tsx",
			"./src/atoms/forms/SlideToggle.tsx",
			"./src/molecules/ActionGroup.tsx",
			"./src/molecules/Display.tsx",
			"./src/molecules/LineItem.tsx",
			"./src/molecules/MenuButton.tsx",
			"./src/molecules/ResponsiveSet.tsx",
			"./src/molecules/Tabs.tsx",
			"./src/organisms/Features.tsx",
			"./src/organisms/Footer.tsx",
			"./src/organisms/Header.tsx",
			"./src/organisms/Hero.tsx",
			"./src/organisms/SignIn.tsx",
			"./src/organisms/SignUp.tsx",
			"./src/organisms/StepsFeatures.tsx",
			"./src/templates/BasicLayout.tsx",
		],
	},
];

Finally, in the application that needs the tailwind styles, there are a few updates in the deno.json imports to make the tailwind-components.ts file importable (https://github.com/o-biotech/biotech-manager-web/blob/integration/deno.jsonc):

"@fathym/atomic/": "https://deno.land/x/fathym_open_biotech_atomic@v0.0.307-integration/",

Then in the tailwind.config.ts file you leverage another helper to generate the compound components file (./build/tailwind-components.config) in a build directory (https://github.com/o-biotech/biotech-manager-web/blob/integration/tailwind.config.ts):

import { buildTailwindComponentsConfigs } from "@fathym/common";
import BiotechAtomicTailwindComponents from "@fathym/atomic/tailwind.components.ts";

const tailwindComponents = [
  ...BiotechAtomicTailwindComponents,
];

await buildTailwindComponentsConfigs(tailwindComponents);

export default {
  content: [
    "{routes,islands,components}/**/*.{ts,tsx}",
    "build/tailwind-components.config",
  ],
} satisfies Config;

With all this in place, tailwind is correctly generating what we need to see things working.

We have several libraries that needed this, so wanted something that would work for us in the short term.

Working on an issue with our docker container build (https://github.com/o-biotech/biotech-manager-web/actions/runs/7458873509/job/20293710568), but it is working locally.

@mcgear
Copy link
Contributor

mcgear commented Jan 11, 2024

I did try vendoring, but with one of the octokit libraries it fails:

deno vendor --unstable main.ts 
error: Module not found "https://esm.sh/v135/@octokit/webhooks@12.0.8/dist-types/generated/webhook-names.ts".
    at https://esm.sh/v135/@octokit/webhooks@12.0.8/dist-types/types.d.ts:4:40

@deer
Copy link
Contributor Author

deer commented Jan 19, 2024

We can use deno_graph to extract dependencies of plugins (i.e. all the .tsx files a plugin uses). Then I'm sure there's some "easy" way to get the content of the file.

How does this help? I spent a few hours looking into modifying tailwind to support "remote content". But during my investigation, I stumbled upon this jewel:

    content: [
      {
        raw: html` <div class="md:w-full"></div> `,
      },
    ],

So this is a way of putting raw file content directly into tailwind -- it doesn't matter whether this content is local or not. Once I saw this, I realized I can just close the tailwind project (at least for now) and no longer make changes there. Instead we can simply modify compiler.ts here (or right after that block...) to inject the raw content of the plugin dependencies. Then all of this "remote content" will get scanned by tailwind, as if it were local.

I have bits and pieces of this working right now. I'll wire everything up later today and create a PR. Let's see what @marvinhagemeister has to say about the approach.

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 a pull request may close this issue.

3 participants