Open deer opened 11 months ago
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}"]
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:
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:
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.
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.
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
@mcgear, is vendoring an option for you? That will put the plugin source files in your project directory, allowing tailwind to scan them.
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.
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
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.
Currently the tailwind plugin doesn't styles routes and islands provided by plugins.