storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.77k stars 9.34k forks source link

[Bug]: Storybook 7, `storybook build` fails with `Entry points must be inside Vite root directory` #21847

Open D3strukt0r opened 1 year ago

D3strukt0r commented 1 year ago

Describe the bug

At work I have tried to upgrade to Storybook 7. Altough I was able to fix the HMR issue that the storybook-server-channel url is fixed to localhost with a patch, I still am unable to build at all.

I am using vite with the following configuration for my application

import react from '@vitejs/plugin-react';
import browserslistToEsbuild from 'browserslist-to-esbuild';
import * as dotenv from 'dotenv';
import { defineConfig } from 'vite';
import { chunkSplitPlugin } from 'vite-plugin-chunk-split';
import symfonyPlugin from 'vite-plugin-symfony';
import viteRestart from 'vite-plugin-restart';
import fs from 'fs';
import i18nHotReload from './assets/vite-plugins/i18n-hot-reload';

dotenv.config();

export default defineConfig(({ command }) => ({
  plugins: [
    chunkSplitPlugin({
      customChunk: ({ file }) => {
        // Extract each node_modules package into a separate chunk in "js/npm/(user-)package".
        // Regex taken from: https://github.com/npm/validate-npm-package-name/blob/main/lib/index.js#L3
        const match = file.match(/node_modules\/(?:@([^\/]+?)[\/])?([^\/]+)/);
        if (match) {
          const name = match[1] ? `${match[1]}-${match[2]}` : match[2];
          return `npm/${name}`;
        }
        return null;
      },
    }),
    react(),
    symfonyPlugin({
      viteDevServerHostname: 'localhost',
    }),
    viteRestart({
      reload: ['templates/**/*'],
    }),
    i18nHotReload(),
  ],
  base: '/build/',
  build: {
    target: browserslistToEsbuild(),
    outDir: './public/build',
    rollupOptions: {
      input: {
        main: './assets/main.tsx',
      },
      output: {
        assetFileNames: (assetInfo) => {
          // Place asset in each corresponding folder "build/{img,font,etc.}/*".
          const info = assetInfo.name.split('.');
          let extType = info[info.length - 1];
          if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
            extType = 'img';
          } else if (/woff2?|otf|ttf|eot/.test(extType)) {
            extType = 'font';
          }
          return `${extType}/[name].[hash][extname]`;
        },
        chunkFileNames: 'js/[name].[hash].js',
        entryFileNames: 'js/[name].[hash].js',
      },
    },
    manifest: true,
  },
  server: {
    host: '0.0.0.0',
    port: 3000,
    strictPort: true,
    https: {
      // Get certificates if we are not building (CI has no certificates)
      key: command === 'build' ? null : fs.readFileSync('docker/run/certificates/key.pem'),
      cert: command === 'build' ? null : fs.readFileSync('docker/run/certificates/cert.pem'),
    },
    watch: {
      ignored: ['**/.idea/**', '**/tests/**', '**/var/**', '**/vendor/**'],
    },
    origin: `https://${process.env.APP_URL}:3000`,
  },
  // Needed for antd
  css: {
    preprocessorOptions: {
      less: {
        javascriptEnabled: true,
      },
    },
  },
}));

and here is my .storybook/main.ts

import type { StorybookConfig } from '@storybook/react-vite';
import * as dotenv from 'dotenv';
import {mergeConfig} from 'vite';

dotenv.config({path: '../.env'});

const config: StorybookConfig = {
  stories: [
    '../assets/**/*.mdx',
    '../assets/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: '@storybook/react-vite',
  docs: {
    autodocs: 'tag',
  },
  async viteFinal(config, options) {
    if (options.configType === 'PRODUCTION') {
      // TODO: While "rendering chunks", Vite will error out with "Entry points must be inside Vite root directory"
      return mergeConfig(config, {
        base: '/storybook/',
      });
    }
    return mergeConfig(config, {
      server: {
        watch: {
          ignored: ['**/.idea/**', '**/tests/**', '**/var/**', '**/vendor/**'],
        },
      },
    });
  },
};

export default config;

All my stories are in assets/stories/*. I moved them from src/ where the init script put them.

The full output of the build:

www-data@b4a1870105af:/app$ yarn build:storybook
yarn run v1.22.19
$ storybook build --output-dir ./public/storybook
@storybook/cli v7.0.0-rc.10

info => Cleaning outputDir: /public/storybook
info => Loading presets
info => Building manager..
info => Manager built (1.82 s)
info => Copying static files: /app/node_modules/@storybook/manager/static at /app/public/storybook/sb-common-assets
vite v4.2.1 building for production...

./sb-common-assets/fonts.css doesn't exist at build time, it will remain unchanged to be resolved at runtime
transforming (2334) node_modules/rc-virtual-list/es/Item.jsUse of eval in "node_modules/telejson/dist/index.mjs" is strongly discouraged as it poses security risks and may cause issues with minification.
Use of eval in "node_modules/telejson/dist/index.mjs" is strongly discouraged as it poses security risks and may cause issues with minification.
✓ 2341 modules transformed.
Generated an empty chunk: "npm/copy-to-clipboard".
Generated an empty chunk: "npm/html-parse-stringify".
Generated an empty chunk: "npm/json2mq".
Generated an empty chunk: "npm/moment".
Generated an empty chunk: "npm/shallowequal".
Generated an empty chunk: "npm/string-convert".
Generated an empty chunk: "npm/toggle-selection".
Generated an empty chunk: "npm/void-elements".
rendering chunks (181)...Entry points must be inside Vite root directory
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I also had a console log for the config param passed to viteFinal which looked like this

{
  plugins: [
    {
      name: 'storybook:react-docgen-plugin',
      enforce: 'pre',
      transform: [AsyncFunction: transform]
    },
    {
      name: 'vite-plugin-chunk-split',
      config: [Function: config],
      renderChunk: [Function: renderChunk]
    },
    [
      {
        name: 'vite:react-babel',
        enforce: 'pre',
        config: [Function: config],
        configResolved: [Function: configResolved],
        transform: [AsyncFunction: transform]
      },
      {
        name: 'vite:react-refresh',
        enforce: 'pre',
        config: [Function: config],
        resolveId: [Function: resolveId],
        load: [Function: load],
        transformIndexHtml: [Function: transformIndexHtml]
      },
      {
        name: 'vite:react-jsx',
        enforce: 'pre',
        config: [Function: config],
        resolveId: [Function: resolveId],
        load: [Function: load]
      }
    ],
    {
      name: 'symfony',
      enforce: 'post',
      config: [Function: config],
      configResolved: [Function: configResolved],
      configureServer: [Function: configureServer],
      renderChunk: [AsyncFunction: renderChunk],
      generateBundle: [Function: generateBundle]
    },
    {
      name: 'vite-plugin-restart:0',
      apply: 'serve',
      config: [Function: config],
      configResolved: [Function: configResolved],
      configureServer: [Function: configureServer]
    },
    {
      name: 'i18n-hot-reload',
      handleHotUpdate: [Function: handleHotUpdate]
    },
    {
      name: 'storybook:code-generator-plugin',
      enforce: 'pre',
      configureServer: [Function: configureServer],
      config: [Function: config],
      configResolved: [Function: configResolved],
      resolveId: [Function: resolveId],
      load: [AsyncFunction: load],
      transformIndexHtml: [AsyncFunction: transformIndexHtml]
    },
    {
      name: 'unplugin-csf',
      transformInclude: [Function: transformInclude],
      transform: [Function (anonymous)],
      vite: { enforce: 'pre' },
      enforce: 'pre'
    },
    {
      name: 'storybook:mdx-plugin',
      enforce: 'pre',
      transform: [AsyncFunction: transform]
    },
    {
      name: 'storybook:inject-export-order-plugin',
      enforce: 'post',
      transform: [AsyncFunction: transform]
    },
    {
      name: 'storybook:strip-hmr-boundary-plugin',
      enforce: 'post',
      transform: [AsyncFunction: transform]
    },
    {
      name: 'storybook:allow-storybook-dir',
      enforce: 'post',
      config: [Function: config]
    },
    {
      name: 'storybook:external-globals-plugin',
      enforce: 'post',
      config: [AsyncFunction: config],
      transform: [AsyncFunction: transform]
    },
    {
      name: 'vite:react-docgen-typescript',
      transform: [AsyncFunction: transform]
    }
  ],
  base: './',
  server: {
    host: '0.0.0.0',
    port: 3000,
    strictPort: true,
    https: { key: null, cert: null },
    watch: {
      ignored: [ '**/.idea/**', '**/tests/**', '**/var/**', '**/vendor/**' ]
    },
    origin: 'https://<my-custom-url>.test:3000'
  },
  css: { preprocessorOptions: { less: { javascriptEnabled: true } } },
  configFile: false,
  cacheDir: 'node_modules/.cache/.vite-storybook',
  root: '/app',
  resolve: {
    preserveSymlinks: false,
    alias: {
      assert: '/app/node_modules/browser-assert/lib/assert.js',
      '@storybook/react-dom-shim': '@storybook/react-dom-shim/dist/react-18'
    }
  },
  envPrefix: [ 'VITE_', 'STORYBOOK_' ],
  build: {
    outDir: '/app/public/storybook',
    emptyOutDir: false,
    sourcemap: true,
    rollupOptions: { external: [ './sb-preview/runtime.mjs' ] }
  }
}

I don't know if i have misconfigured something or whether there is actually an issue, which is why I'm here.

To Reproduce

Can't quite explain simply ran npx sb@next init --builder=vite and added my custom config

System

Environment Info:

  System:
    OS: Linux 5.10 Debian GNU/Linux 10 (buster) 10 (buster)
    CPU: (2) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
  Binaries:
    Node: 18.15.0 - /usr/bin/node
    Yarn: 1.22.19 - /usr/bin/yarn
    npm: 9.5.0 - /usr/bin/npm
  npmPackages:
    @storybook/addon-essentials: ^7.0.0-rc.10 => 7.0.0-rc.10
    @storybook/addon-interactions: ^7.0.0-rc.10 => 7.0.0-rc.10
    @storybook/addon-links: ^7.0.0-rc.10 => 7.0.0-rc.10
    @storybook/blocks: ^7.0.0-rc.10 => 7.0.0-rc.10
    @storybook/react: ^7.0.0-rc.10 => 7.0.0-rc.10
    @storybook/react-vite: ^7.0.0-rc.10 => 7.0.0-rc.10
    @storybook/testing-library: ^0.0.14-next.1 => 0.0.14-next.1

Additional context

No response

JorisAerts commented 1 year ago

I would suspect it has something to do with specifying base: "/storybook/".

D3strukt0r commented 1 year ago

@JorisAerts i commented out that line, and still get the same error. Also i do still need it, since it will be accessible from myurl.com/storybook/index.html.

I can even comment out the entire viteFinal function, and still get the error.

My app (inside container) is at /app and all the storybook output must go in /app/public/storybook whereas /app/public is the public root, which is why i said i need the base set to /storybook

D3strukt0r commented 1 year ago

I have figured out so far that it was symfonyPlugin (Source code) which caused the issue. If I exclude it in viteFinal, it goes through.

import {defineConfig} from 'vite';
import symfonyPlugin from 'vite-plugin-symfony';

export default defineConfig(({command}) => ({
  plugins: [
    symfonyPlugin({
      viteDevServerHostname: 'localhost',
    }),
  ],
}));
import {mergeConfig} from 'vite';
import symfonyPlugin from 'vite-plugin-symfony';

const config = {
  async viteFinal(config, options) {
    // While "rendering chunks", Vite will error out with "Entry points must be inside Vite root directory"
    if (options.configType === 'PRODUCTION') {
      for (let i = 0; i < config.plugins.length; i++) {
        if (config.plugins[i].name === symfonyPlugin.name) {
          config.plugins.splice(i, 1);
        }
      }
    }
    return mergeConfig(config, {});
  },
};
export default config;

I don't what causes this, going through the source code, would be too much for my current knowledge.

D3strukt0r commented 9 months ago

Version 6.3.0+ needs adjustments to this workaround for anyone looking help in here. The symfony plugin exports 2 plugins now, so we just need to extract the name of the one we need which is "symfony-entrypoints".

import type { StorybookConfig } from '@storybook/react-vite';
import { mergeConfig } from 'vite';
import symfonyPlugin from 'vite-plugin-symfony';

const config: StorybookConfig = {
  [...]
  async viteFinal(config, options) {
    // While "rendering chunks", Vite will error out with "Entry points must be inside Vite root directory"
    if (options.configType === 'PRODUCTION' && config.plugins) {
      for (let i = 0; i < config.plugins.length; i++) {
        const pluginOrArrayOf = config.plugins[i];
        if (Array.isArray(pluginOrArrayOf)) {
          for (let j = 0; j < pluginOrArrayOf.length; j++) {
            // @ts-ignore TODO: Name exists, fix type?
            if (pluginOrArrayOf[j]?.name === symfonyPlugin()[0].name) {
              config.plugins.splice(i, 1);
            }
          }
        }
      }
    }
    return mergeConfig(config, {
      [...]
    });
  },
};

Nothing to be taken care of by Storybook team, should be handled by the SymfonyPlugin creator