nuxt / content

The file-based CMS for your Nuxt application, powered by Markdown and Vue components.
https://content.nuxt.com
MIT License
3.02k stars 615 forks source link

Support invalidating Vite Styling when content changes (Tailwind, Uno, etc) #1119

Open Atinux opened 2 years ago

Atinux commented 2 years ago

I think a video is better to understand:

https://user-images.githubusercontent.com/904724/166730285-d28417e5-5b91-4559-8b3f-b89f802d6a7d.mp4

Adding classes that are not generated by Tailwind at first does not work when changing

Example for Tailwind: https://stackblitz.com/edit/nuxt-starter-omgw5t?file=app.vue

I am also trying to make UnoCSS works but not sure what I did wrong (cc @antfu): https://stackblitz.com/edit/nuxt-starter-rbqpf3?file=nuxt.config.ts

antfu commented 2 years ago

Do we have a resource that describes how Nuxt Content handles the markdown compiling somewhere? Did a quick check, and it seems that the markdowns are not been passed to Vite's plugins pipeline. Which UnoCSS is relying on, and probably also how TW handles HMR.

By adding this to config:

 vite: {
    plugins: [
      {
        name: 'foo',
        transform(code, id) {
          if (!id.match('node_modules'))
            console.log(id)
        },
      },
    ],
  },

You will see the contents are not presented in the log.

Atinux commented 2 years ago

Indeed @antfu, the Markdown files are not handled through Vite at all, this is the power of Nuxt Content, the Markdown is part of runtime, like any other CMS.

The main thing I need is the ability to tell Vite to tell its plugins (Uno, Tailwind, etc) to "recompile" by reading their sources (content/** is included in them). Basically, it will be here that I would like to ping Vite to re-run the plugin and do a HMR: https://github.com/nuxt/content/blob/docs/marketing/src/module.ts#L421

Or it could be handled on client-side here: https://github.com/nuxt/content/blob/docs/marketing/src/runtime/composables/web-socket.ts#L25

Something like import.meta.hot.refreshStyles() could be possible? (https://vitejs.dev/guide/api-plugin.html#client-to-server)

antfu commented 2 years ago

Tailwind is probably reading the changes from the disk, in this case, you might need a custom extractor for it to understand MDC? Other than that, you might just need to invalidate the style. You can use configureServer hook from a Vite plugin, and send a ws event to vite client manually:

server.ws.send({
  type: 'update',
  updates: [{
    acceptedPath: id, // id is the CSS file contains `@tailwind`
    path: id,
    timestamp: +Date.now(),
    type: 'js-update',
  }],
})

For UnoCSS it's a bit different, we read and extract utils usage from the plugin's transform hook instead of disk for efficiency. It means in this case UnoCSS didn't know about the contents at all. You might need to use cross-plugin API.

Unless we fake the vite plugin hook calls, we might more or less need to have some specific logic for handling Uno or Tailwind (probably a few other integrations in the future).

harlan-zw commented 2 years ago

Nuxt content v1 also had this issue with Windi, I fixed it with this code.

Basically, hook into before the markdown is parsed, extract the classes from the markdown, generate the classes for just that file and insert them at the end of the .md within a style block.

Not an ideal solution by any means but it's simple and worked. Since the scope was just the HMR update I wasn't too worried, the build isn't effected. Not sure if the same hook is provided for v2.

Seems like we'll need a custom extractor either way

Atinux commented 2 years ago

Can we expose this hook @farnabaz ?

farnabaz commented 2 years ago

We can provide a similar hook. Not in module scope but inside nitro scope. Windi module could inject a nitro plugin to use this hook.

// windi-nitro-plugin.ts
export default defineNitroPlugin((nitroApp) => {
 nitroApp.hooks.hook('content:file:beforeParse', () => {
   ...
 })
})
harlan-zw commented 2 years ago

Sounds good, I'm guessing we would be able to inject this nitro plugin via the nitro:context hook?

farnabaz commented 2 years ago

'nitro:context' removed in favor of 'nitro:config'. You can use this hook to update nitro configs

harlan-zw commented 2 years ago

It's a bit hacky and there are sometimes some weird h3 and worker errors popping up, but nuxt-windicss now supports nuxt/content v2

Nitro plugin: https://github.com/windicss/nuxt-windicss/blob/main/packages/nuxt-windicss/src/runtime/server/class-extractor.mjs Setup: https://github.com/windicss/nuxt-windicss/blob/main/packages/nuxt-windicss/src/module.ts#L310

Triloworld commented 1 year ago

Right now is not working together - MDC and UnoCSS: Example from @Atinux but newest version: https://stackblitz.com/edit/nuxt-starter-9s9zsj?file=content%2Findex.md,app.vue,package.json

    "@nuxt/content": "npm:@nuxt/content-edge@latest",
    "@unocss/nuxt": "^0.45.21",
    "nuxt": "3.0.0-rc.9"

We have problem with tailwind by refresh loop. On UnoCSS is not updating at all on rc9.

anhzf commented 4 months ago

for those who looking current workaround for unocss, you can include your content dir to uno.config.ts

export default defineConfig({
  filesystem: ['content/**'],
  pipeline: {
    include: [
      // the file extensions are limited by default. In my case, I need to add .json extension
      /\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html|json)($|\?)/,
    ]
  }
})

references: https://unocss.dev/guide/extracting#extracting-from-filesystem