diff --git a/HISTORY.md b/HISTORY.md index 423b8ca19d34..2aa2f50d0984 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,6 @@ ## v.Next -- Deprecated `{ linkTo, action }` as built-in addons: . From 3.0 use them as you would [any other addon](https://storybook.js.org/docs/react-storybook/addons/using-addons/). +- Deprecated `{ linkTo, action }` as built-in addons: . From 3.0 use them as you would [any other addon](https://storybook.js.org/addons/using-addons/). Before: diff --git a/addons/graphql/src/preview.js b/addons/graphql/src/preview.js index 2dc1c2cf9300..5d18a1180981 100644 --- a/addons/graphql/src/preview.js +++ b/addons/graphql/src/preview.js @@ -21,7 +21,7 @@ function getDefautlFetcher(url) { function reIndentQuery(query) { const lines = query.split('\n'); const spaces = lines[lines.length - 1].length - 1; - return lines.map((l, i) => (i === 0 ? l : l.slice(spaces)).join('\n')); + return lines.map((l, i) => (i === 0 ? l : l.slice(spaces))).join('\n'); } export function setupGraphiQL(config) { diff --git a/app/react-native/src/server/addons.js b/app/react-native/src/server/addons.js index 05ee4df423da..7a3afc8b7ae1 100644 --- a/app/react-native/src/server/addons.js +++ b/app/react-native/src/server/addons.js @@ -4,5 +4,5 @@ import '@storybook/addon-links/register'; deprecate( () => {}, - '@storybook/react-native/addons is deprecated. See https://storybook.js.org/docs/react-storybook/addons/using-addons/' + '@storybook/react-native/addons is deprecated. See https://storybook.js.org/addons/using-addons/' )(); diff --git a/app/react/README.md b/app/react/README.md index 573ea112c831..edf8681ef468 100644 --- a/app/react/README.md +++ b/app/react/README.md @@ -27,8 +27,8 @@ For more information visit: [storybook.js.org](https://storybook.js.org) * * * -Storybook also comes with a lot of [addons](https://storybook.js.org/docs/react-storybook/addons/introduction) and a great API to customize as you wish. -You can also build a [static version](https://storybook.js.org/docs/react-storybook/basics/exporting-storybook) of your storybook and deploy it anywhere you want. +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. Here are some featured storybooks that you can reference to see how Storybook works: @@ -41,6 +41,6 @@ If you are using Typescript, make sure you have the type definitions installed v ## Docs -- [Basics](https://storybook.js.org/docs/react-storybook/basics/introduction) -- [Configurations](https://storybook.js.org/docs/react-storybook/configurations/default-config) -- [Addons](https://storybook.js.org/docs/react-storybook/addons/introduction) +- [Basics](https://storybook.js.org/basics/introduction) +- [Configurations](https://storybook.js.org/configurations/default-config) +- [Addons](https://storybook.js.org/addons/introduction) diff --git a/app/react/addons.js b/app/react/addons.js index 3a4c7938924c..71b3fb1f83e5 100644 --- a/app/react/addons.js +++ b/app/react/addons.js @@ -4,5 +4,5 @@ import '@storybook/addon-links/register'; deprecate( () => {}, - '@storybook/react/addons is deprecated. See https://storybook.js.org/docs/react-storybook/addons/using-addons/' + '@storybook/react/addons is deprecated. See https://storybook.js.org/addons/using-addons/' )(); diff --git a/app/react/demo/src/stories/Welcome.js b/app/react/demo/src/stories/Welcome.js index 6da890717aaf..9e944c427e65 100644 --- a/app/react/demo/src/stories/Welcome.js +++ b/app/react/demo/src/stories/Welcome.js @@ -99,7 +99,7 @@ export default class Welcome extends React.Component { {' '} Writing Stories 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 240b2be5774e..580b48b6dec7 100644 --- a/app/react/src/server/iframe.html.js +++ b/app/react/src/server/iframe.html.js @@ -8,37 +8,47 @@ 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', + 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 ` @@ -53,12 +63,12 @@ export default function(data) { Storybook ${headHtml} - ${previewCssTag} + ${cssTags}
- + ${scriptTags} `; diff --git a/app/react/src/server/index.html.js b/app/react/src/server/index.html.js index fa7b791d8413..375f03acb84c 100644 --- a/app/react/src/server/index.html.js +++ b/app/react/src/server/index.html.js @@ -28,7 +28,7 @@ const managerUrlsFromAssets = assets => { }; export default function(data) { - const { assets, publicPath } = data; + const { assets, publicPath, headHtml } = data; const managerUrls = managerUrlsFromAssets(assets); @@ -70,6 +70,7 @@ export default function(data) { background-color: #eee } + ${headHtml}
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..d15201c82a28 100644 --- a/app/react/src/server/middleware.js +++ b/app/react/src/server/middleware.js @@ -6,7 +6,14 @@ import getBaseConfig from './config/webpack.config'; import loadConfig from './config'; import getIndexHtml from './index.html'; import getIframeHtml from './iframe.html'; -import { getHeadHtml, getMiddleware } from './utils'; +import { getHeadHtml, getManagerHeadHtml, 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` @@ -29,19 +36,34 @@ 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) => { + const headHtml = getManagerHeadHtml(configDir) + res.send(getIndexHtml({ publicPath, headHtml })); + }); + + 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/app/react/src/server/utils.js b/app/react/src/server/utils.js index 3961915ba6f7..70f20e268f49 100644 --- a/app/react/src/server/utils.js +++ b/app/react/src/server/utils.js @@ -1,20 +1,40 @@ import path from 'path'; import fs from 'fs'; +import deprecate from 'util-deprecate'; + +const fallbackHeadUsage = deprecate( + () => {}, + 'Usage of head.html has been deprecated. Please rename head.html to preview-head.html' +); export function parseList(str) { return str.split(','); } export function getHeadHtml(configDirPath) { - const headHtmlPath = path.resolve(configDirPath, 'head.html'); + const headHtmlPath = path.resolve(configDirPath, 'preview-head.html'); + const fallbackHtmlPath = path.resolve(configDirPath, 'head.html'); let headHtml = ''; if (fs.existsSync(headHtmlPath)) { headHtml = fs.readFileSync(headHtmlPath, 'utf8'); + } else if (fs.existsSync(fallbackHtmlPath)) { + headHtml = fs.readFileSync(fallbackHtmlPath, 'utf8'); + fallbackHeadUsage(); } return headHtml; } +export function getManagerHeadHtml(configDirPath) { + const scriptPath = path.resolve(configDirPath, 'manager-head.html'); + let scriptHtml = ''; + if (fs.existsSync(scriptPath)) { + scriptHtml = fs.readFileSync(scriptPath, 'utf8'); + } + + return scriptHtml; +} + export function getEnvConfig(program, configEnv) { Object.keys(configEnv).forEach(fieldName => { const envVarName = configEnv[fieldName]; diff --git a/docs/package.json b/docs/package.json index 33b61bbde769..5f88b728135e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -55,7 +55,8 @@ "sitemap": "^1.12.0", "typography": "^0.15.8", "typography-plugin-code": "^0.15.9", - "underscore.string": "^3.2.2" + "underscore.string": "^3.2.2", + "webpack": "^1.15.0" }, "private": true } diff --git a/docs/pages/_users.yml b/docs/pages/_users.yml index a8937c1d1417..d022be7c4ba9 100644 --- a/docs/pages/_users.yml +++ b/docs/pages/_users.yml @@ -26,7 +26,8 @@ squarespace: coursera: logo: ./logos/coursera.svg title: Coursera - site: https://coursera.org + description: Coursera UI component library + site: https://building.coursera.org/ui/ # buffer: # logo: ./logos/buffer.svg # title: Buffer Components diff --git a/docs/pages/configurations/add-custom-head-tags/index.md b/docs/pages/configurations/add-custom-head-tags/index.md index b7bb8943c8ca..6cae4180c759 100644 --- a/docs/pages/configurations/add-custom-head-tags/index.md +++ b/docs/pages/configurations/add-custom-head-tags/index.md @@ -5,7 +5,7 @@ title: 'Add Custom Head Tags' Sometimes, you may need to add different tags to the HTML head. This is useful for adding web fonts or some external scripts. -You can do this very easily. Simply create a file called `head.html` inside the Storybook config directory and add tags like this: +You can do this very easily. Simply create a file called `preview-head.html` inside the Storybook config directory and add tags like this: ```html @@ -17,3 +17,14 @@ That's it. Storybook will inject these tags. > **Important** > > Storybook will inject these tags to the iframe where your components are rendered. So, these won’t be loaded into the main Storybook UI. + +## Add Tags or Scripts to the Main UI. + +Additionally, you may need to add different scripts or tags to the main Storybook UI. This might arise when your custom Webpack configuration outputs or requires additional chunks. + +Create a file called `manager-head.html` inside of the Storybook config directory and add any tags you require. + +> **Important** +> +> Your scripts will run before Storybook's React UI. Also be aware, that this is an uncommon scenario and could potentially break Storybook's UI. So use with caution. + diff --git a/docs/pages/examples/_examples.yml b/docs/pages/examples/_examples.yml index bdc48abaed53..7e3eb179f62b 100644 --- a/docs/pages/examples/_examples.yml +++ b/docs/pages/examples/_examples.yml @@ -25,6 +25,11 @@ algolia: description: Lightning-fast, hyper-configurable search. source: https://github.com/algolia/react-instantsearch/ demo: https://community.algolia.com/react-instantsearch/storybook/ +coursera: + thumbnail: ./thumbnails/coursera-ui.png + title: Coursera + description: Coursera UI component library + demo: https://building.coursera.org/ui/ necolas: thumbnail: ./thumbnails/reactnativeweb.jpg title: React Native Web diff --git a/docs/pages/examples/thumbnails/coursera-ui.png b/docs/pages/examples/thumbnails/coursera-ui.png new file mode 100644 index 000000000000..73006f17d7bc Binary files /dev/null and b/docs/pages/examples/thumbnails/coursera-ui.png differ 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 diff --git a/examples/cra-storybook/src/stories/Welcome.js b/examples/cra-storybook/src/stories/Welcome.js index 65384f40d233..0679fdbeee3b 100644 --- a/examples/cra-storybook/src/stories/Welcome.js +++ b/examples/cra-storybook/src/stories/Welcome.js @@ -94,7 +94,7 @@ export default class Welcome extends React.Component { {' '}
diff --git a/examples/react-native-vanilla/jest.config.js b/examples/react-native-vanilla/jest.config.js new file mode 100644 index 000000000000..377a172fabae --- /dev/null +++ b/examples/react-native-vanilla/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'react-native', + globals: { + __DEV__: true, + }, +}; diff --git a/examples/react-native-vanilla/package.json b/examples/react-native-vanilla/package.json index 977023dade40..643f02744594 100644 --- a/examples/react-native-vanilla/package.json +++ b/examples/react-native-vanilla/package.json @@ -26,8 +26,5 @@ "@storybook/react-native": "file:../../app/react-native", "@storybook/ui": "file:../../lib/ui", "react-dom": "^15.5.4" - }, - "jest": { - "preset": "react-native" } } diff --git a/examples/react-native-vanilla/storybook/stories/index.js b/examples/react-native-vanilla/storybook/stories/index.js index 63f1a6266717..1f0e1cb917a5 100644 --- a/examples/react-native-vanilla/storybook/stories/index.js +++ b/examples/react-native-vanilla/storybook/stories/index.js @@ -13,13 +13,13 @@ storiesOf('Welcome', module).add('to Storybook', () => {getStory()}) - .add('with text', () => ( + .add('with text', () => - )) - .add('with some emoji', () => ( + ) + .add('with some emoji', () => - )); + ); diff --git a/examples/test-cra/src/__snapshots__/storyshots.test.js.snap b/examples/test-cra/src/__snapshots__/storyshots.test.js.snap index 5fe5ee3ca2bb..ba78f9638b61 100644 --- a/examples/test-cra/src/__snapshots__/storyshots.test.js.snap +++ b/examples/test-cra/src/__snapshots__/storyshots.test.js.snap @@ -165,7 +165,7 @@ exports[`Storyshots Welcome to Storybook 1`] = ` Have a look at the diff --git a/jest.config.js b/jest.config.js index 18aed5696d06..12054792ad60 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,8 +6,15 @@ module.exports = { '/__mocks__/fileMock.js', '\\.(css|scss)$': '/__mocks__/styleMock.js', }, - roots: ['/addons', '/app', '/lib', '/examples'], - testPathIgnorePatterns: ['/node_modules/', 'examples/react-native-vanilla'], + roots: [ + '/addons', + '/app', + '/lib', + '/examples/cra-storybook', + '/examples/test-cra', + ], + testPathIgnorePatterns: ['/node_modules/'], + projects: ['./', './examples/react-native-vanilla'], collectCoverage: false, collectCoverageFrom: [ 'app/**/*.{js,jsx}', diff --git a/lib/cli/generators/REACT_SCRIPTS/template/src/stories/Welcome.js b/lib/cli/generators/REACT_SCRIPTS/template/src/stories/Welcome.js index 4271b97fb902..ad2e19ccbc6f 100644 --- a/lib/cli/generators/REACT_SCRIPTS/template/src/stories/Welcome.js +++ b/lib/cli/generators/REACT_SCRIPTS/template/src/stories/Welcome.js @@ -94,7 +94,7 @@ export default class Welcome extends React.Component { {' '} diff --git a/lib/cli/generators/WEBPACK_REACT/template/.storybook/webpack.config.js b/lib/cli/generators/WEBPACK_REACT/template/.storybook/webpack.config.js index 985f61741c4b..17cc9b3be62e 100644 --- a/lib/cli/generators/WEBPACK_REACT/template/.storybook/webpack.config.js +++ b/lib/cli/generators/WEBPACK_REACT/template/.storybook/webpack.config.js @@ -1,6 +1,6 @@ // you can use this file to add your custom webpack plugins, loaders and anything you like. // This is just the basic way to add additional webpack configurations. -// For more information refer the docs: https://storybook.js.org/docs/react-storybook/configurations/custom-webpack-config +// For more information refer the docs: https://storybook.js.org/configurations/custom-webpack-config // IMPORTANT // When you add this file, we won't add the default configurations which is similar diff --git a/package.json b/package.json index 7d617b02bfc3..483abd531e04 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,7 @@ "lint:js": "eslint . --cache --cache-location=.cache/eslint --ext .js,.jsx,.json", "lint:md": "remark .", "publish": "lerna publish", - "test": "npm run test:libs && npm run test:apps", - "test:apps": "npm run test:react-native-vanilla", - "test:libs": "jest", - "test:watch": "npm run test:libs -- --watch", - "test:react-native-vanilla": "cd examples/react-native-vanilla && jest" + "test": "jest --projects ./ ./examples/react-native-vanilla" }, "devDependencies": { "babel-cli": "^6.24.1",