From 38b9dfabfb1b8691c1f7b378f9dfff05b99603f5 Mon Sep 17 00:00:00 2001 From: Cameron Little Date: Wed, 25 Jan 2017 13:43:35 -0800 Subject: [PATCH 1/2] Add support for multiple webpack chunks in iframe This allows an arbitrary number of files to be loaded into the iframe, not just the single `preview.bundle.js` file. Webpack plugins can output chunks in a separate file. In dev, since file names are predictable, I can add the js file into a head.html file. When building storybook statically, however, js files have a hash added to them, so this doesn't work. This change makes the static build parse all webpack chunks and load them in the iframe (excluding the manager script), instead of only including known files. --- app/react/src/server/iframe.html.js | 47 ++++++++++++++++++----------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/app/react/src/server/iframe.html.js b/app/react/src/server/iframe.html.js index 985130293994..f55190f401ce 100644 --- a/app/react/src/server/iframe.html.js +++ b/app/react/src/server/iframe.html.js @@ -11,31 +11,44 @@ import url from 'url'; const previewUrlsFromAssets = assets => { if (!assets) { return { - js: 'static/preview.bundle.js', + js: ['static/preview.bundle.js'], + css: [], }; } - if (typeof assets.preview === 'string') { - return { - js: assets.preview, - }; - } - - return { - js: assets.preview.find(filename => filename.match(/\.js$/)), - css: assets.preview.find(filename => filename.match(/\.css$/)), + const urls = { + js: [], + css: [], }; + + const re = /.+\.(\w+)$/; + Object.keys(assets) + // Don't load the manager script in the iframe + .filter(key => key !== 'manager') + .forEach(key => { + const asset = assets[key]; + if (typeof asset === 'string') { + urls[re.exec(asset)[1]].push(asset); + } else { + const assetUrl = asset.find(u => re.exec(u)[1] !== 'map'); + urls[re.exec(assetUrl)[1]].push(assetUrl); + } + }); + + return urls; }; export default function(data) { const { assets, headHtml, publicPath } = data; - const previewUrls = previewUrlsFromAssets(assets); + const urls = urlsFromAssets(assets); - let previewCssTag = ''; - if (previewUrls.css) { - previewCssTag = ``; - } + const cssTags = urls.css + .map(u => ``) + .join('\n'); + const scriptTags = urls.js + .map(u => ``) + .join('\n'); return ` @@ -50,12 +63,12 @@ export default function(data) { Storybook ${headHtml} - ${previewCssTag} + ${cssTags}
- + ${scriptTags} `; From 7c707f6de2ca392ee6b0e00ce9133059412d1591 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 7 Jun 2017 16:48:52 +0200 Subject: [PATCH 2/2] ADD webpack progressbar && show server ready message after webpack FIX 'urlsFromAssets' --- app/react/src/server/config/webpack.config.js | 1 + app/react/src/server/iframe.html.js | 2 +- app/react/src/server/index.js | 20 ++++++++--- app/react/src/server/middleware.js | 35 +++++++++++++++---- examples/cra-storybook/package.json | 10 +++--- 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/app/react/src/server/config/webpack.config.js b/app/react/src/server/config/webpack.config.js index 72de95c5e61e..24eacd0bf24b 100644 --- a/app/react/src/server/config/webpack.config.js +++ b/app/react/src/server/config/webpack.config.js @@ -26,6 +26,7 @@ export default function() { new webpack.HotModuleReplacementPlugin(), new CaseSensitivePathsPlugin(), new WatchMissingNodeModulesPlugin(nodeModulesPaths), + new webpack.ProgressPlugin(), ], module: { rules: [ diff --git a/app/react/src/server/iframe.html.js b/app/react/src/server/iframe.html.js index f55190f401ce..580b48b6dec7 100644 --- a/app/react/src/server/iframe.html.js +++ b/app/react/src/server/iframe.html.js @@ -8,7 +8,7 @@ import url from 'url'; // 'preview.0d2d3d845f78399fd6d5e859daa152a9.css', // 'static/preview.9adbb5ef965106be1cc3.bundle.js.map', // 'preview.0d2d3d845f78399fd6d5e859daa152a9.css.map' ] -const previewUrlsFromAssets = assets => { +const urlsFromAssets = assets => { if (!assets) { return { js: ['static/preview.bundle.js'], diff --git a/app/react/src/server/index.js b/app/react/src/server/index.js index d086222ebcbc..d23a75fb50f1 100755 --- a/app/react/src/server/index.js +++ b/app/react/src/server/index.js @@ -8,7 +8,7 @@ import path from 'path'; import fs from 'fs'; import chalk from 'chalk'; import shelljs from 'shelljs'; -import storybook from './middleware'; +import storybook, { webpackValid } from './middleware'; import packageJson from '../../package.json'; import { parseList, getEnvConfig } from './utils'; @@ -135,11 +135,23 @@ process.env.STORYBOOK_GIT_BRANCH = // `getBaseConfig` function which is called inside the middleware app.use(storybook(configDir)); +let serverResolve = () => {}; +let serverReject = () => {}; +const serverListening = new Promise((resolve, reject) => { + serverResolve = resolve; + serverReject = reject; +}); server.listen(...listenAddr, error => { if (error) { - throw error; + serverReject(error); } else { - const address = `http://${program.host || 'localhost'}:${program.port}/`; - logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); + serverResolve(); } }); + +Promise.all([webpackValid, serverListening]) + .then(() => { + const address = `http://${program.host || 'localhost'}:${program.port}/`; + logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); + }) + .catch(error => logger.error(error)); diff --git a/app/react/src/server/middleware.js b/app/react/src/server/middleware.js index e2ca8d2ace87..112740698259 100644 --- a/app/react/src/server/middleware.js +++ b/app/react/src/server/middleware.js @@ -8,6 +8,13 @@ import getIndexHtml from './index.html'; import getIframeHtml from './iframe.html'; import { getHeadHtml, getMiddleware } from './utils'; +let webpackResolve = () => {}; +let webpackReject = () => {}; +export const webpackValid = new Promise((resolve, reject) => { + webpackResolve = resolve; + webpackReject = reject; +}); + export default function(configDir) { // Build the webpack configuration using the `getBaseConfig` // custom `.babelrc` file and `webpack.config.js` files @@ -29,19 +36,33 @@ export default function(configDir) { }; const router = new Router(); - router.use(webpackDevMiddleware(compiler, devMiddlewareOptions)); + const webpackDevMiddlewareInstance = webpackDevMiddleware(compiler, devMiddlewareOptions); + router.use(webpackDevMiddlewareInstance); router.use(webpackHotMiddleware(compiler)); // custom middleware middlewareFn(router); - router.get('/', (req, res) => { - res.send(getIndexHtml({ publicPath })); - }); + webpackDevMiddlewareInstance.waitUntilValid(stats => { + const data = { + publicPath: config.output.publicPath, + assets: stats.toJson().assetsByChunkName, + }; + + router.get('/', (req, res) => { + res.send(getIndexHtml({ publicPath })); + }); + + router.get('/iframe.html', (req, res) => { + const headHtml = getHeadHtml(configDir); + res.send(getIframeHtml({ ...data, headHtml, publicPath })); + }); - router.get('/iframe.html', (req, res) => { - const headHtml = getHeadHtml(configDir); - res.send(getIframeHtml({ headHtml, publicPath })); + if (stats.toJson().errors.length) { + webpackReject(stats); + } else { + webpackResolve(stats); + } }); return router; diff --git a/examples/cra-storybook/package.json b/examples/cra-storybook/package.json index 3cd54c700771..29ba51e8cbf3 100644 --- a/examples/cra-storybook/package.json +++ b/examples/cra-storybook/package.json @@ -19,11 +19,11 @@ "uuid": "^3.0.1" }, "devDependencies": { - "@storybook/addon-actions": "3.0.0", - "@storybook/addon-links": "3.0.0", - "@storybook/addon-events": "3.0.1", - "@storybook/addons": "3.0.0", - "@storybook/react": "3.0.0", + "@storybook/addon-actions": "^3.0.0", + "@storybook/addon-links": "^3.0.0", + "@storybook/addon-events": "^3.0.0", + "@storybook/addons": "^3.0.0", + "@storybook/react": "^3.0.0", "react-scripts": "1.0.1" }, "private": true