Storybook: Keeping the path for the static directory in building

Created on 7 Mar 2017  ·  57Comments  ·  Source: storybookjs/storybook

In my root directory, I have a static folder names public which is served as /public/... path from my node server. That means the images and CSS files are served with that /public/ prefix.

But when I use StoryBook with the command: start-storybook -p 6006 -s ./public
The path with /public/ is no longer available.
So I changed the command to start-storybook -p 6006 -s ./ to serve the root directory, and everything is ok.

But when I build my storybook with the command build-storybook -s ./ the script will copy all files in the root directory to the storybook-static.
And if I change the command to build-storybook -s ./public, the path with /public/ prefix will not be available anymore.

Is there a way to specify the path for the static directory?

feature request has workaround help wanted

Most helpful comment

Do you think a solution like start-storybook -p 6006 -s "./public:/static" to serve static files from ./public on the /static path be implemented? It would lead to extremely flexible uses and fit the Docker's conventions of path mapping that lots of us are already familiar with.

All 57 comments

Hmm so this problem is about build-storybook that's something I'd need to look into how that works.

You could give custom middleware a try?, I'm not sure it will work, but it might.
https://github.com/storybooks/react-storybook/pull/435#issuecomment-264813688
https://github.com/storybooks/react-storybook/blob/master/src/server/middleware.js#L35

:+1: This is an issue - not for building storybook but just using for dev.

Here's how I got it to work with middleware:

const express = require('express');
const path = require('path');
const paths = require("../config/paths");

const expressMiddleWare = (router) => {
    console.log(path.join(__dirname), paths.appPublic);
    router.use('/public', express.static(paths.appPublic))
};

module.exports = expressMiddleWare;

I will try to get a PR for this but not sure if I have the time atm

Thank you for sharing your solution! ❤️

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 60 days. Thanks!

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

Hi! @ndelangen @hansthinhle @patrickgordon

Im facing the exact same issue. Was this ever solved?

Thanks!

hi @aviramga - my solution that I posted a few up resolved it for us. I never got around doing a PR to resolve it.

Might still do that actually now that my memory has been jogged :)

Hi @patrickgordon. Sadly my issue is only with build-storybook :/

I'm using it with a tool called Percy to find ui issues in my app.

Did you manage to find a solution for the build?

Looks like you can work around this using symlinks:

Add a directory with an arbitrary name (e.g. static-link, and put there a file called public (without any extension) with following contents:

../public

Then you should be able to achieve what you need with -s static-link

@Hypnosphi thanks for the tip! this doesnt seem to work though.
Can you please explain how it works?

All my static files are under a directory named "static" (not public).
I tried to ways that didn't work:

  1. Create an arbitrary directory with a file named "static" and ../static in it - cased build error when I ran build-storybook
  2. Same as a above but with a file named "public" - still don't see any images

Any advice?

Thanks

Which OS are you using?
https://en.wikipedia.org/wiki/Symbolic_link#Overview

What was the error?

Mac high sierra

@Hypnosphi but just to make sure I understand correctly, to adjust your example to my use case I need to reate a new directory e.g static-link in it put a file named static and in it write ../static

Correct?

OK looks like my instruction for creating symlink was wrong. The correct way is this:

mkdir static-link
ln -s static static-link/static

It will create a "file" in static-link directory that can be shared in git (it won't work on Windows though)

@Hypnosphi I will give it a shot. but does it mean I have to run it every time I ran build-storybook?

No, it should be persistent

@Hypnosphi nope, still not working. While building I get a log that says:
cp: no such file or directory: static-link/static

I followed your instructions above. So I have an empty directory called static-link with a symlink from static to static-link/static

What am I doing wrong?

Really appreciate you taking the time to help out :)

And you use -s static-link option, right?

@Hypnosphi you mean build-storybook -c .storybook -s static-link ?

Yes

I do

Sorry, I mistyped:

ln -s ../static static-link/static

@Hypnosphi
why ../static?

Am I correct that the static-link directory is empty?
Maybe you want to try to move to a private conversation and publish here our final results? I feel like we are very close and its just miscommunication :)

It's the relative path from link location to link target

@Hypnosphi I was able to make it work with using absolute path..... but I do need to make it work with relative path as I will want to implement the same logic in semaphore

@Hypnosphi found a workaround. Thank you so much for all your help, you're a life saver!!

any tip for resolving that?

Yes, just run the solution suggested above. Work with a symlink
ln -s /<absolute-path-your-static-directory> storybook-static-symlink/static

@aviramga is this method still working for you? I'm not having any luck using a symlink.

My folder structure

|- docs
  |- folders-with-images
|- sandbox (holds my storybook files)
|- src
  |- README.md with image paths `/docs/folder/image.png`

I can get this working when serving storybook start-storybook -c sandbox -s sandbox,docs -p 6006 using a middleware file:

const express = require('express');
const path = require('path');

module.exports = router => {
  router.use('/docs', express.static(path.join(__dirname, '..', 'docs')));
}

But adding a symlink using ln -s /docs sandbox/docs and running build-storybook -c sandbox -s sandbox,docs -o storybook still does not work.

The folders in docs are copied over but since the file path I need is /docs/folder/image.png the images 404.

Also managed to use a static-link to solve this:

package.json:

"scripts": {
  "storybook": "(mkdir ./src/static-link || true) && (ln -s ../static ./src/static-link/static || true) && start-storybook -p 6006 -s ./src/static-link"
}

I would advise against creating the symlink via the package.json script for compatibility reasons, in case not every developer uses the same OS.

As an alternative I suggest using the copy-webpack-plugin in the .storybook/webpack.config.js file, like this:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = config => {
  function resolve(dir) {
    return path.join(__dirname, '..', dir);
  }

  // Other configuration properties

  config.plugins.push(
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, 'static-foo'),
        to: '.'
      },
      {
        from: path.resolve(__dirname, '../static-bar'),
        to: './bar'
      }
    ])
  );

  return config;
}

This for example would mount the contents of the directory .storybook/static-foo to http://localhost:6006/ and the contents of static-bar to http://localhost:6006/bar/.

Also note the use of push to add to the plugins array to avoid overwriting other plugins, which could break you Storybook Webpack configuration.

For future reference, symlinks are perfectly fine to use. If you want/need to use them with relative paths, then you simply use the -r switch like so: ln -rs dir1 dir2

I personally think that if storybook sees fit to copy an entire static directory, this should be clearly stated in the documentation. Otherwise, this behavior should be switched out for one that creates the appropriate symlinks instead. I found out about the copying thing because it was crashing my server through excessively disk space usage!

So basically there is no official built in solution and all the "solutions" are just random crappy hacks :)

I would advise against creating the symlink via the package.json script for compatibility reasons, in case not every developer uses the same OS.

As an alternative I suggest using the copy-webpack-plugin in the .storybook/webpack.config.js file, like this:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = config => {
  function resolve(dir) {
    return path.join(__dirname, '..', dir);
  }

  // Other configuration properties

  config.plugins.push(
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, 'static-foo'),
        to: '.'
      },
      {
        from: path.resolve(__dirname, '../static-bar'),
        to: './bar'
      }
    ])
  );

  return config;
}

This for example would mount the contents of the directory .storybook/static-foo to http://localhost:6006/ and the contents of static-bar to http://localhost:6006/bar/.

Also note the use of push to add to the plugins array to avoid overwriting other plugins, which could break you Storybook Webpack configuration.

this doesn't work ;)

this is a working config:

.storybook/webpack.config.js
(assuming static folder is on the root folder of the project)

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = async ({ config }) => {

  function resolve(dir) {
    return path.join(__dirname, '..', dir);
  }

  // Other configuration properties

  config.plugins.push(
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../../static'),
        to: './static'
      }
    ])
  );

  return config;
}

@mtrabelsi doesn't work for me for some reasons...

Also, I guess you have a type since your .storybook folder stand on the one level deep from the root, not double deep. So, you need switch to:
from - from: path.resolve(__dirname, '../../static')
to - from: path.resolve(__dirname, '../static')

"copy-webpack-plugin": "^5.0.4"
All paths are right. Double checked.

Update
I've found out with @BradMcGonigle answer upper by creating the middleware.js file inside my .storybook/ folder:

const express = require('express');
const path = require('path');

module.exports = router => {
  router.use('/docs', express.static(path.join(__dirname, '..', 'docs')));
}

@BiosBoy to avoid any confusion here quickly an idea about my project structure ( only what you need to know ):

My_sweet_projet_ROOT_DIR/components/.storybook/webpack.config.js
My_sweet_projet_ROOT_DIR/static

I also use next.js as the main application where it consumes react components from components folder.

Here my setup ( you could reproduce it on your side - I trust you :D )

My_sweet_projet_ROOT_DIR/package.json

{
  "name": "sweet_like_butter",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@zeit/next-css": "^1.0.1",
    "next": "^8.1.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "styled-components": "^4.2.0"
  },
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "devDependencies": {
    "babel-plugin-styled-components": "^1.10.0"
  }
}

My_sweet_projet_ROOT_DIR/next.config.js

const withCSS = require('@zeit/next-css')
module.exports = withCSS()

I was able to get around a similar issue by using a base tag:

  1. create a manager-head.html file in your .storybook directory. https://storybook.js.org/docs/configurations/add-custom-head-tags/
  2. add
<head>
  <script>
    const hostname = window.location.hostname
    if (hostname !== "" && hostname !== "localhost") {
      const script = document.createElement('base')
      script.href = '/storybook-static/'
      document.getElementsByTagName('head')[0].appendChild(script)
    }
  </script>
</head>

(with script.href = to whatever your desired path is)

files in index.html are now retrieved with the specified prefix

Shouldn't this behavior be built into Storybook, where if you flag -s ./static then your paths will work the same way in Storybook as they would in production? Seems nonsensical that when I tell Storybook that my static directory is ./static, that my relative paths e.g. ./static/image.png have to instead be ./image.png or use some goofy symlink 🤔

@eckmLJE If you use the snippet that I posted above that should work without any additional webpack configuration or a symlink. We could look into outputting this automatically as part of the build process though.

I still facing the problem in 2020...
My case is very simple: my package.json has this script: "storybook": "start-storybook -s ./dist/img -p 8888"

So when I run npm run storybook, it displays info => Loading static files from: /home/vagrant/projects/MySuperProject/web/themes/ofb/ofb_ui/dist/img .

but when I try to access to a file in the dist/img directory from the browser with this URL, it does not work: http://localhost:8888/myImage.png...

So guys, is it a bug or am I doing something wrong ?

@ndelangen do you think we can fit this into 6.0 breaking changes? i think it would be a great fix, but a breaking one.

Seems like CopyWepbackPlugin's api has changed and you need to add a pattern key to @mtrabelsi solution like that:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = async ({ config }) => {
  config.plugins.push(
    new CopyWebpackPlugin({
      pattern: [
        {
          from: path.resolve(__dirname, '../../static'),
          to: './static'
        }
      ]
    })
  );

  return config;
}

I'll add my two cents related to this issue. Though I think it's closely related, my solution differs from @mtrabelsi in that I found that simply copying my assets into the ./static directory that is a result of the build-storybook command wasn't enough. Specifically if I am deploying the static app to a Tomcat server that serves the static app from http://example.com/docs, where docs is a directory located in the Tomcat webapps/ on the server.

Because of the subpath, none of the files outside of the /css directory in the /static directory were getting loaded. I am using relative paths for images and fonts (these were the static assets that were failing to load on the deployed version). So requests like https://example.com/img/path/to/my/image failed, when it should have been adding /static, ie. https://example.com/static/img/path/to/my/image My solutions, as I said, is very close to @mtrabelsi but I just drop the two directorys if fonts and img into the root of the output:

const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = async ({ config }) => {

  function resolve(dir) {
    return path.join(__dirname, '..', dir);
  }

  // Other configuration properties

  config.plugins.push(
    new CopyWebpackPlugin({
      pattern: [
        {
          from: path.resolve(__dirname, './_assets'), // My static font and images are located in the .storybook dir
          to: './' // Drop both the fonts/ and img/ directory into the root of the build output.
        }
      ]
    })
  );

  return config;
}

After this, the images and fonts were loaded appropriately.

@tmeasday suggests: why can't we make this a main.js option so that it appears in both start-storybook and build-storybook

@vcastro45 I've tested this for both start-storybook & build-storybook, and it seems to work (tested on the next branch).

Could you create a reproduction repo for me?

@shilman It's possible, but we'd have to hoist the preset setup up from
https://github.com/storybookjs/storybook/blob/9ea455a1746c489b7364448212663f2445af8a8b/lib/core/src/server/manager/manager-config.js#L101-L102

to before here:
https://github.com/storybookjs/storybook/blob/19c2420db80fcb3b89b34cdbbe03bf9010b0b3b2/lib/core/src/server/build-static.js#L190-L191

And then pass it down to all lower functions in the build / dev chain.
It's quite a refactor, not something I can do in 1 day.

It'd also be another moment we'd really want to migrate it all to TS. Since we'd be touching 50% of lib/core's files anyway.

@ndelangen OOF, let's not do that now then 🙈

Yeah we'll do it some day

@ndelangen I've documented the workaround per discussion with @tmeasday in #11370. Propose that we move this config to main.js and add an option to support this use case in 6.x as a feature not a breaking change.

Do you think a solution like start-storybook -p 6006 -s "./public:/static" to serve static files from ./public on the /static path be implemented? It would lead to extremely flexible uses and fit the Docker's conventions of path mapping that lots of us are already familiar with.

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

@nfroidure that is an interesting idea for sure!

@ndelangen I can give a try to it with a PR if you wish. Any chance it gets merged?

Well I have something here : https://github.com/storybookjs/storybook/pull/12222

Some tests are failing but I cannot see any link between those failures and my changes.

LMKWYT ;)

Thank you for this PR @nfroidure !

I'll look at it this week!

ZOMG!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.1.0-alpha.6 containing PR #12222 that references this issue. Upgrade today to try it out!

You can find this prerelease on the @next NPM tag.

Closing this issue. Please re-open if you think there's still more to do.

Is there an example of how to use this new feature?

Was this page helpful?
0 / 5 - 0 ratings