vite_ruby icon indicating copy to clipboard operation
vite_ruby copied to clipboard

Allow `rollupOptions` set by other plugins to be respected

Open blake-simpson opened this issue 1 year ago • 2 comments

Description 📖

Storybook (builder-vite) adds an iframe.html entry into the rollupOptions when building a static build. I found that vite-plugin-ruby is overriding these options meaning that the Storybook build fails.

Background 📜

Storybook builds are failing for projects using vite-plugin-ruby or vite-plugin-rails.

Build/Rollup options without the spread:

{
  emptyOutDir: false,
  sourcemap: true,
  target: [ 'chrome108', 'edge108', 'firefox109', 'ios15', 'safari15' ],
  outDir: '<path-to-project>/storybook-static',
  rollupOptions: {
    external: [ './sb-preview/runtime.js', /\.\/sb-common-assets\/.*\.woff2/ ],
    input: {
      'entrypoints/application.ts': '<path-to-project>/app/frontend/entrypoints/application.ts',
      'entrypoints/vue.ts': '<path-to-project>/app/frontend/entrypoints/vue.ts'
    },
    output: {
      assetFileNames: [Function (anonymous)],
      entryFileNames: [Function (anonymous)]
    }
  },
  assetsDir: 'assets',
  manifest: true
}

The Fix 🔨

Here I am allowing any existing rollup options + inputs to be spread into the config before the plugin returns it.

Rollup options with the spread:

build: {
  emptyOutDir: false,
  sourcemap: true,
  target: [ 'chrome108', 'edge108', 'firefox109', 'ios15', 'safari15' ],
  outDir: '<path-to-project>/storybook-static',
  rollupOptions: {
    external: [ './sb-preview/runtime.js', /\.\/sb-common-assets\/.*\.woff2/ ],
    input: {
      '<path-to-project>/node_modules/.pnpm/@[email protected][email protected]_@[email protected]_@babel_mehfrma2w74ofinzeaaidxqt6y/node_modules/@storybook/builder-vite/input/iframe.html': '<path-to-project>/node_modules/.pnpm/@[email protected][email protected]_@[email protected]_@babel_mehfrma2w74ofinzeaaidxqt6y/node_modules/@storybook/builder-vite/input/iframe.html',
      'entrypoints/application.ts': '<path-to-project>/app/frontend/entrypoints/application.ts',
      'entrypoints/vue.ts': '<path-to-project>/app/frontend/entrypoints/vue.ts'
    },
    output: {
      assetFileNames: [Function (anonymous)],
      entryFileNames: [Function (anonymous)]
    }
  },
  assetsDir: 'assets',
  manifest: true
},

Notes

I appreciate this may not be the correct fix for the problem. If you would like a different style / approach to solve this please let me know!

blake-simpson avatar Aug 06 '24 12:08 blake-simpson

Woah! Well done figuring this one out. I've been struggling with the storybook static builds for years on my vite-ruby projects and could not understand what was going on!

eriknygren avatar Aug 15 '24 10:08 eriknygren

@ElMassimo any chance you could have a look? I appreciate all your hard work and know that you have a lot on your plate with your suite of OSS projects so don't mean to rush you/come across as ungrateful.

This would be awesome for all of us vite_ruby using teams who use storybook as documentation for our front end. ❤️ Cheers!

eriknygren avatar Aug 28 '24 16:08 eriknygren

Also just run into this issue and couldn't understand why the iframe was missing until I came across this discussion.

Is there a way to patch this manually until a proper fix is in place? I tried using the patched Gem:

gem 'vite_rails', github: 'blake-simpson/vite_ruby', branch: 'patch-1'

But the iframe is still missing from builds...

Thanks

iamdriz avatar Sep 03 '24 14:09 iamdriz

@ElMassimo I've seen that this is merged now, but still not getting the iframe in the Storybook output after updating to the latest version of this gem... do I need to update something else as well?

iamdriz avatar Sep 05 '24 08:09 iamdriz

@ElMassimo I've seen that this is merged now, but still not getting the iframe in the Storybook output after updating to the latest version of this gem... do I need to update something else as well?

Same @iamdriz, maybe it makes most sense to continue the conversation here. We might have had a different issue than @blake-simpson that's not vite_ruby related.

eriknygren avatar Sep 05 '24 11:09 eriknygren

@eriknygren Yeah I'm seeing the same issue as mentioned in the other conversation with the iframe.html being added into /public/vite folder instead of storybook-static. The comments seem to still suggest that vite_ruby is the culprit.

iamdriz avatar Sep 20 '24 10:09 iamdriz

@iamdriz seeing the same. @blake-simpson have you got this working on your end now?

eriknygren avatar Sep 25 '24 14:09 eriknygren

Hi all, sorry for the late reply, I was away on a longer vacation.

First of all thank you @ElMassimo for merging + releasing this!

To the thread above, I also have the same issue. I can't quite pin down why but it seems all of the rollup options for vite-plugin-ruby ensure that the assets are built into the ./vite directory instead of of the usual static one. I think it may be related to making the Ruby on Rails asset pipeline work? I think changing this may break this library entirely though, so I've been working with the following hack:

storybook build --force-build-preview --preview-url=vite/iframe.html && \ 
  cp -R storybook-static/sb-preview storybook-static/vite && \
  cp -R storybook-static/sb-common-assets storybook-static/vite && \
  cp -R storybook-static/sb-addons storybook-static/vite &&  \
  cp storybook-static/index.json storybook-static/vite/index.json

It's ugly but the best I can figure out for now. I'd be happy to hear if someone has a better way or if @ElMassimo knows a way to make the files build into . instead of ./vite when using Storybook?

This is however working for me locally but I'm having issues with GitHub pages. If anyone has anymore input please let me know.

cc/ @eriknygren @iamdriz

blake-simpson avatar Sep 30 '24 15:09 blake-simpson

Thanks @blake-simpson for getting back to us. So I don't really understand the nuances of this, my iframe.html ends up in the vite-dev folder for example (and others have reported it ending up in the vite folder), even though I'm building with both NODE_ENV and RAILS_ENV set to production 🤷

And even when I copy files over manually, loading a page from the static build results in no components, it doesn't even look like it's trying to fetch them.

I feel pretty stuck on this and don't know what to suggest. Maybe another approach is needed, maintaining a completely separate vite config for storybook for example. Would love to hear if anyone does find a solution, but personally I've sort of given up at the moment (althought I would love to have this working).

eriknygren avatar Oct 08 '24 08:10 eriknygren

a way to make the files build into . instead of ./vite when using Storybook

Have you tried configuring publicOutputDir to .?

Maybe you could pass the VITE_RUBY_PUBLIC_OUTPUT_DIR="." environment variable when building Storybook. I haven't used Storybook, so I'm not familiar on whether it requires a separate command to build, or if it's integrated in the default Vite build.

ElMassimo avatar Oct 08 '24 12:10 ElMassimo

@ElMassimo Thank you that helps with the VITE_RUBY_PUBLIC_OUTPUT_DIR var. I now have to move a lot less files around.

Interestingly though for me, in this case, the ifame.html no longer builds into the . dir (<rails-root>/storybook-static/) but instead into ../public (<rails-root>/public/) I'm not sure why that is happening but personally I am copying that file over + the assets (also in ../public) after build. Just an FYI to others who are on this chain.

blake-simpson avatar Oct 09 '24 13:10 blake-simpson

I'm returning with some good news, just got this working for me! 🎉

Running the storybook build command with these output dir vars have made sure everything ends up where expected. So in my package.json I build storybook with

"build-storybook": "VITE_RUBY_PUBLIC_OUTPUT_DIR='.' VITE_RUBY_PUBLIC_DIR='./storybook-static' storybook build"

In my storybook config (.storybook/main.js), to resolve imports to my components, I copy that over from the main vite config with:

import path from 'path';

const config = {
  ...
  staticDirs: ['../public'],
  async viteFinal(storybookConfig) {
    const { mergeConfig, loadConfigFromFile } = await import('vite');
    const { config: mainAppConfig } = await loadConfigFromFile(
      path.resolve(__dirname, '../vite.config.ts'),
    );
    const pickedConfigFieldsFromMainApp = { resolve: mainAppConfig.resolve };
    const mergedConfig = mergeConfig(storybookConfig, pickedConfigFieldsFromMainApp);
    return mergedConfig;
  },
  ...
};
export default config;

Without needing to copy anything over, runtime.js from storybook seems to be in the right place, and so does iframe.html

Hopefully this unblocks everyone else too! cc @blake-simpson @iamdriz

Edit: On a second review: I think the .storybook/main.js part is not needed. I hadn't realise it was clever enough to merge things in from your vite.config.ts to start with.

eriknygren avatar Nov 05 '24 17:11 eriknygren

I ended up having to solve this too and none of the other solutions seemed to work. I'm unsure if something changed since the previous solutions were posted here. Importantly, what I noticed was that any time vite-plugin-ruby was included, the iframe.html was just not being generated what so ever. Not in storybook-static/ nor in any subdirectory in public/. I'm not sure why that is, but to resolve it I ensured that the vite-plugin-ruby was not included in the plugins for Storybook.

To note, we're using vite-plugin-rails and adding it like so

in vite.config.ts (isn't isn't everything in our file, just enough to show how we're adding a couple plugins)

import path from "node:path";
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import Vue from "@vitejs/plugin-vue";
import ViteRails from "vite-plugin-rails";


export default defineConfig(({ mode }) => ({
  define: {
    "process.env.NODE_ENV": JSON.stringify(mode),
  },
  plugins: [
    Vue(),
    ViteRails(),
  ],
})

in .storybook/main.ts

import type { StorybookConfig } from "@storybook/vue3-vite";

const config: StorybookConfig = {
  ...
  viteFinal(config) {
    if (config.plugins) {
      const newPlugins = config.plugins.filter((plugin) => {
        // We're looking for the vite-plugin-ruby and its pals. They appear to come in
        // as an array
        if (Array.isArray(plugin)) {
          // Confirm vite-plugin-ruby is in this array
          const rubyExists = plugin.some(obj => {
            if (obj && 'name' in obj) {
              return obj.name.startsWith('vite-plugin-ruby')
            } else {
              return false
            }
          })

          // If vite-plugin-ruby was found, we want to filter this array of plugins out of our Storybook build
          return !rubyExists;
        } else {
          return true
        }
      })

      config.plugins = newPlugins;
    }

    return config
  }
};

export default config;

The viteFinal can be made more concise and comments removed, but this verbose version helps to explain what is going on. Hope this helps if anyone else runs into the same issue!

jer-k avatar Sep 04 '25 20:09 jer-k