Poimen / stencil-tailwind-plugin

Plugin for using tailwindcss with StencilJS
https://www.npmjs.com/package/stencil-tailwind-plugin
MIT License
51 stars 5 forks source link

Missing classes with Stencil Hydrate #29

Open mayerraphael opened 1 year ago

mayerraphael commented 1 year ago

As this plugin is a rollup plugin (no pluginType: 'css'), this is run AFTER the stencil hydrate plugin is run.

The problem:

Stencil hydrate adds comments to the css classes so the scoped classes can later be rewritten to work hydrated component again.

Now with this plugin, all tailwind inline-classes and utility classes, which generate more classes (:hover, lg:, md: and so on) are added AFTER the comments for the hydrate package have been created. Resulting in broken styles with SSR.

We have created our own plugin thanks to your source code (and searching the tailwindcss source) which runs as a css plugin, fully creating all css classes BEFORE any Stencil plugin runs.

One drawback is that we need to load the tsx file manually (like tailwindcss does with changedContent) and don't have any _sourceTsx already set (has same content as sourceCss), as the plugin is run only on css files.

Thanks for all your hard work.

Poimen commented 1 year ago

oooooo, hydrate - I thought I had fixed that 🙈 đŸ¤Ļ‍♂ī¸ Come to think of it, our bigger project did show some interesting artefacts when we turned hydrate on, but in the end we reverted back to static generation so didn't need hydrate so never investigated them further.

Do you know if this happens on the example repo, or have a example to play with?

Also, the code drop here ... is this your plugin or an example of a/the fix?

mayerraphael commented 1 year ago

I removed the code from the original post, you can find our adjusted plugin here: https://gist.github.com/mayerraphael/bb3122ece5900671b5cb85dbe46a1e8e

I will check your example repo later.

mayerraphael commented 1 year ago

It looks like the tailwindHMR plugin is required to correctly generate the Hydrate classes. We only had the tailwindCss plugin set.

Poimen commented 1 year ago

Interesting ... the HMR plugin add dependencies and writes the css to stencil buffers. I'm guessing the secondary buffer write is probably what does it seeing that the HMR only deals with the CSS files and updates stencils css files.

Should probably make that more explicit if that is the cause - like an actual plugin/implementation that maps better to hydrate.

mayerraphael commented 1 year ago

Yes the problem is that only the css transform plugins run before Stencils Hydrate worker plugin.

This also means that every component MUST have a stylesheet, as other components are not processed.

You can check this behavior in your example.

  1. Add dist-hydrate-script to your output targets in the stencil.config.ts
outputTargets: [
    {
      type: 'dist',
      esmLoaderPath: '../loader',
    },
    {
      type: 'dist-hydrate-script',
    },
    ...
}
  1. Remove tailwindHMR() from plugins
  2. run npm run build
  3. Check the generated hydrate/index.js

The last css classes of FooterNav (footerNavCss)

.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}

With tailwindHMR()

/*!@.duration-300*/.duration-300.sc-footer-nav{transition-duration:.3s}/*!@.ease-in-out*/.ease-in-out.sc-footer-nav{transition-timing-function:cubic-bezier(.4,0,.2,1)}@media (min-width:1024px){.lg\\:flex-row{flex-direction:row}.lg\\:gap-4{gap:1rem}.lg\\:rounded-md{border-radius:.375rem}}

You can see that

  1. The classes without tailwindHMR did not get scoped (sc-footer-nav suffix missing)
  2. Classes which are generated by tailwind are missing (lg:, hover:, , etc)
  3. The scoped-comments are missing, so Stencil hydration cannot convert those classes back again.

I guess the best way would be to do the required part (as we do in our plugin) in the css transform plugin phase. Because most worker plugins run directyl afterwards.

From StencilJS source code, as far as i understand it: image

Poimen commented 1 year ago

Thanks for the context - great find 👍

One of those lovely side-effects 🛩ī¸

The TW plugin runs in beforePlugins because it needs the raw sources to process the tsx source. The other plugin entry points don't give the tsx source, only the css source (it's been a while so would need to check this properly again). At the start I didn't have a mapping between the inline styles and the associated css, but had to build something like that for the all the other funny stuff going on.

But thanks for the updates

Poimen commented 1 year ago

Yeah, just checked again - as soon as you use a pluginType of anything, you only get css files:

pluginType: 'sadsad'

The files passed in:

dep: C:/git/stencil-tailwind-plugin-example/src/components/hero-section/hero-section.css
dep: C:/git/stencil-tailwind-plugin-example/src/components/demo-section/demo-section.css
dep: C:/git/stencil-tailwind-plugin-example/src/components/demo-card/demo-card.css
dep: C:/git/stencil-tailwind-plugin-example/src/components/footer-nav/footer-nav.css
dep: C:/git/stencil-tailwind-plugin-example/src/components/main-app/main-app.css
dep: C:/git/stencil-tailwind-plugin-example/src/components/top-nav-bar/top-nav-bar.css

I remember been disappointed that you couldn't get tsx files ... but still if the HMR "works", just need to check if you run it twice if you start getting duplications or not ... then could just have a "hydrate" plugin, but with the same underlying workings 🤔

mayerraphael commented 1 year ago

I had the same discovery as you. Stencil only checks if a pluginType is set. If so, it adds the plugin as a userPlugin , which are css transformation plugins... Very disappointing.

I checked the tailwindcss sourcecode while developing our plugin variant and they load the content (`--contents flag or from your tailwind configuration) manually. See: https://github.com/tailwindlabs/tailwindcss/blob/a92932f4cc3c071334cfaa2b4a782419bdfe83e5/src/cli/build/plugin.js#L179

So i did the same with our plugin. This works for our best practice; does not work if css and tsx filenames differ.

const tsxFilename = filename.replace('.css', '.tsx');
let changedContent: any[] = [];

const exists = !!(await fs.stat(tsxFilename).catch(() => false));
if (exists) {
  changedContent = [{ content: await fs.readFile(path.resolve(tsxFilename), 'utf8'), extension: 'tsx' }];
} else {
  ...
}
const tailwindPlugin = makeTailwindPlugin(conf.tailwindConf, changedContent);

If using the same logic as tailwindcss (fastglob) is possible, it should also work generically with the tailwind configuration the user provided.

This would mean no tsx source is required by Stencil. It can all be done in the css path like the tailwindcss cli does it.

Poimen commented 1 year ago

Unfortunately there are also cases where the style components are conditional, which means a component can have more than 1 stylesheet associated and the inline styles need to be added to each one.

Poimen commented 1 year ago

Going to re-open this open to track - the HMR plugin works but the amount of css that is injected is actually quite high, which is "workable" for dev but for a proper component, probably not so great.