diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index 9731c81d6927..000000000000
--- a/.babelrc
+++ /dev/null
@@ -1,53 +0,0 @@
-{
- "presets": [
- "@babel/preset-env",
- "@babel/preset-react",
- "@babel/preset-flow"
- ],
- "plugins": [
- "babel-plugin-emotion",
- "babel-plugin-macros",
- "@babel/plugin-proposal-class-properties",
- "@babel/plugin-proposal-export-default-from",
- [
- "@babel/plugin-transform-runtime",
- {
- "regenerator": true
- }
- ]
- ],
- "env": {
- "test": {
- "plugins": ["babel-plugin-require-context-hook"]
- }
- },
- "overrides": [
- {
- "test": "./examples/vue-kitchen-sink",
- "presets": [
- "@babel/preset-env",
- "babel-preset-vue"
- ]
- },
- {
- "test": [
- "./lib/core/src/server",
- "./lib/node-logger",
- "./lib/codemod",
- "./addons/storyshots",
- "./addons/storysource/src/loader",
- "./app/**/src/server/**"
- ],
- "presets": [
- [
- "@babel/preset-env",
- {
- "targets": {
- "node": "8.11"
- }
- }
- ]
- ]
- }
- ]
-}
diff --git a/.babelrc.js b/.babelrc.js
new file mode 100644
index 000000000000..8db0608b032f
--- /dev/null
+++ b/.babelrc.js
@@ -0,0 +1,46 @@
+module.exports = {
+ presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-flow'],
+ plugins: [
+ 'babel-plugin-emotion',
+ 'babel-plugin-macros',
+ '@babel/plugin-proposal-class-properties',
+ '@babel/plugin-proposal-export-default-from',
+ [
+ '@babel/plugin-transform-runtime',
+ {
+ regenerator: true,
+ },
+ ],
+ ],
+ env: {
+ test: {
+ plugins: ['babel-plugin-require-context-hook'],
+ },
+ },
+ overrides: [
+ {
+ test: './examples/vue-kitchen-sink',
+ presets: ['@babel/preset-env', 'babel-preset-vue'],
+ },
+ {
+ test: [
+ './lib/core/src/server',
+ './lib/node-logger',
+ './lib/codemod',
+ './addons/storyshots',
+ './addons/storysource/src/loader',
+ './app/**/src/server/**',
+ ],
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ targets: {
+ node: '8.11',
+ },
+ },
+ ],
+ ],
+ },
+ ],
+};
diff --git a/app/angular/src/server/angular-cli_config.js b/app/angular/src/server/angular-cli_config.js
index 049167745352..c5b25b4710f1 100644
--- a/app/angular/src/server/angular-cli_config.js
+++ b/app/angular/src/server/angular-cli_config.js
@@ -103,12 +103,10 @@ export function applyAngularCliWebpackConfig(baseConfig, cliWebpackConfigOptions
const rulesExcludingStyles = filterOutStylingRules(baseConfig);
// cliStyleConfig.entry adds global style files to the webpack context
- const entry = {
+ const entry = [
...baseConfig.entry,
- iframe: []
- .concat(baseConfig.entry.iframe)
- .concat(Object.values(cliStyleConfig.entry).reduce((acc, item) => acc.concat(item), [])),
- };
+ ...Object.values(cliStyleConfig.entry).reduce((acc, item) => acc.concat(item), []),
+ ];
const module = {
...baseConfig.module,
diff --git a/app/angular/src/server/framework-preset-angular.js b/app/angular/src/server/framework-preset-angular.js
index 882e212caecb..714d9fb5a20f 100644
--- a/app/angular/src/server/framework-preset-angular.js
+++ b/app/angular/src/server/framework-preset-angular.js
@@ -38,13 +38,13 @@ export function webpack(config, { configDir }) {
},
resolve: {
...config.resolve,
- extensions: [...config.resolve.extensions, '.ts', '.tsx'],
+ extensions: ['.ts', '.tsx', ...config.resolve.extensions],
},
plugins: [
...config.plugins,
// See https://github.com/angular/angular/issues/11580#issuecomment-401127742
new ContextReplacementPlugin(
- /@angular(\\|\/)core(\\|\/)fesm5/,
+ /@angular(\\|\/)core(\\|\/)(fesm5|bundles)/,
path.resolve(__dirname, '..')
),
createForkTsCheckerInstance(tsLoaderOptions),
diff --git a/app/react-native/src/server/config/webpack.config.prod.js b/app/react-native/src/server/config/webpack.config.prod.js
index 09720b78ff2b..77da36c090b2 100644
--- a/app/react-native/src/server/config/webpack.config.prod.js
+++ b/app/react-native/src/server/config/webpack.config.prod.js
@@ -3,7 +3,7 @@ import webpack from 'webpack';
import Dotenv from 'dotenv-webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
-import { getManagerHeadHtml } from '@storybook/core/dist/server/utils';
+import { getManagerHeadHtml } from '@storybook/core/dist/server/utils/template';
import { version } from '../../../package.json';
import { includePaths, excludePaths, loadEnv } from './utils';
diff --git a/app/riot/src/server/framework-preset-riot.js b/app/riot/src/server/framework-preset-riot.js
index d7a5786502da..820e02e015d2 100644
--- a/app/riot/src/server/framework-preset-riot.js
+++ b/app/riot/src/server/framework-preset-riot.js
@@ -15,5 +15,12 @@ export function webpack(config) {
},
],
},
+ resolve: {
+ ...config.resolve,
+ alias: {
+ ...config.resolve.alias,
+ 'riot-compiler': 'riot-compiler/dist/es6.compiler',
+ },
+ },
};
}
diff --git a/examples/angular-cli/package.json b/examples/angular-cli/package.json
index 1bf055909cee..906cf911eea9 100644
--- a/examples/angular-cli/package.json
+++ b/examples/angular-cli/package.json
@@ -5,7 +5,7 @@
"license": "MIT",
"scripts": {
"build": "ng build",
- "build-storybook": "npm run storybook:prebuild && build-storybook -s src",
+ "build-storybook": "npm run storybook:prebuild && build-storybook -s src/assets",
"e2e": "ng e2e",
"ng": "ng",
"start": "ng serve",
diff --git a/examples/angular-cli/src/favicon.ico b/examples/angular-cli/src/assets/favicon.ico
similarity index 100%
rename from examples/angular-cli/src/favicon.ico
rename to examples/angular-cli/src/assets/favicon.ico
diff --git a/examples/cra-kitchen-sink/public/index.html b/examples/cra-kitchen-sink/public/index.html
deleted file mode 100644
index aab5e3b00ce4..000000000000
--- a/examples/cra-kitchen-sink/public/index.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
- React App
-
-
-
-
-
-
diff --git a/examples/svelte-kitchen-sink/public/index.html b/examples/svelte-kitchen-sink/public/index.html
deleted file mode 100644
index 605ba2f12207..000000000000
--- a/examples/svelte-kitchen-sink/public/index.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
- Svelte App
-
-
-
-
-
-
diff --git a/examples/vue-kitchen-sink/public/index.html b/examples/vue-kitchen-sink/public/index.html
deleted file mode 100644
index aab5e3b00ce4..000000000000
--- a/examples/vue-kitchen-sink/public/index.html
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
- React App
-
-
-
-
-
-
diff --git a/lib/core/package.json b/lib/core/package.json
index 35423deff122..f5f607bd3a9c 100644
--- a/lib/core/package.json
+++ b/lib/core/package.json
@@ -30,6 +30,7 @@
"@emotion/core": "^0.13.1",
"@emotion/provider": "^0.11.2",
"@emotion/styled": "^0.10.6",
+ "@ndelangen/html-webpack-harddisk-plugin": "^0.2.0",
"@storybook/addons": "4.1.0-alpha.1",
"@storybook/channel-postmessage": "4.1.0-alpha.1",
"@storybook/client-logger": "4.1.0-alpha.1",
@@ -43,6 +44,7 @@
"boxen": "^2.0.0",
"case-sensitive-paths-webpack-plugin": "^2.1.2",
"chalk": "^2.4.1",
+ "child-process-promise": "^2.2.1",
"cli-table3": "0.5.1",
"commander": "^2.19.0",
"common-tags": "^1.8.0",
@@ -75,6 +77,7 @@
"semver": "^5.6.0",
"serve-favicon": "^2.5.0",
"shelljs": "^0.8.2",
+ "spawn-promise": "^0.1.8",
"style-loader": "^0.23.1",
"svg-url-loader": "^2.3.2",
"url-loader": "^1.1.2",
diff --git a/lib/core/server.js b/lib/core/server.js
index b851b3b0ba2c..95d61363f855 100644
--- a/lib/core/server.js
+++ b/lib/core/server.js
@@ -1,5 +1,5 @@
-const defaultWebpackConfig = require('./dist/server/config/webpack.config.default.js');
-const serverUtils = require('./dist/server/utils');
+const defaultWebpackConfig = require('./dist/server/preview/base-webpack.config');
+const serverUtils = require('./dist/server/utils/template');
const buildStatic = require('./dist/server/build-static');
const buildDev = require('./dist/server/build-dev');
diff --git a/lib/core/src/server/build-dev.js b/lib/core/src/server/build-dev.js
index 25f3d2abf3b0..daddfc5bed2a 100644
--- a/lib/core/src/server/build-dev.js
+++ b/lib/core/src/server/build-dev.js
@@ -14,7 +14,7 @@ import semver from 'semver';
import { stripIndents } from 'common-tags';
import Table from 'cli-table3';
-import storybook, { webpackValid } from './middleware';
+import storybook, { webpackValid } from './dev-server';
import { getDevCli } from './cli';
const defaultFavIcon = require.resolve('./public/favicon.ico');
diff --git a/lib/core/src/server/build-static.js b/lib/core/src/server/build-static.js
index 3176fd2ae9a7..573a21b0202f 100644
--- a/lib/core/src/server/build-static.js
+++ b/lib/core/src/server/build-static.js
@@ -1,15 +1,20 @@
-import webpack from 'webpack';
-import path from 'path';
import fs from 'fs';
+import path from 'path';
+import webpack from 'webpack';
import shelljs from 'shelljs';
+import childProcess from 'child-process-promise';
+
import { logger } from '@storybook/node-logger';
+
import { getProdCli } from './cli';
import loadConfig from './config';
+import { loadEnv } from './config/utils';
const defaultFavIcon = require.resolve('./public/favicon.ico');
export async function buildStaticStandalone(options) {
- const { outputDir, staticDir, watch } = options;
+ const { outputDir, staticDir, watch, configDir, packageJson } = options;
+ const environment = loadEnv();
// create output directory if not exists
shelljs.mkdir('-p', path.resolve(outputDir));
@@ -17,16 +22,38 @@ export async function buildStaticStandalone(options) {
shelljs.rm('-rf', path.resolve(outputDir, 'static'));
shelljs.cp(defaultFavIcon, outputDir);
+ logger.info('building manager..');
+ const managerStartTime = process.hrtime();
+ await childProcess
+ .exec(
+ `node ${path.join(__dirname, 'manager/webpack.js')} dir=${configDir} out=${path.resolve(
+ outputDir
+ )}`,
+ {
+ env: {
+ NODE_ENV: 'production',
+ ...environment,
+ },
+ }
+ )
+ .then(() => {
+ const managerTotalTime = process.hrtime(managerStartTime);
+ logger.trace({ message: 'manager built', time: managerTotalTime });
+ });
+
// Build the webpack configuration using the `baseConfig`
// custom `.babelrc` file and `webpack.config.js` files
// NOTE changes to env should be done before calling `getBaseConfig`
const config = await loadConfig({
configType: 'PRODUCTION',
- corePresets: [require.resolve('./core-preset-prod.js')],
+ outputDir,
+ packageJson,
+ corePresets: [require.resolve('./preview/preview-preset.js')],
+ overridePresets: [require.resolve('./preview/custom-webpack-preset.js')],
...options,
});
- config.output.path = path.resolve(outputDir);
+ // config.output.path = path.resolve(outputDir);
// copy all static files
if (staticDir) {
@@ -42,6 +69,8 @@ export async function buildStaticStandalone(options) {
// compile all resources with webpack and write them to the disk.
return new Promise((resolve, reject) => {
+ const previewStartTime = process.hrtime();
+
const webpackCb = (err, stats) => {
if (err || stats.hasErrors()) {
logger.error('Failed to build the storybook');
@@ -52,11 +81,14 @@ export async function buildStaticStandalone(options) {
process.exitCode = 1;
return reject(err);
}
- logger.info('Building storybook completed.');
+
+ const previewTotalTime = process.hrtime(previewStartTime);
+ logger.trace({ message: 'preview built', time: previewTotalTime });
+
return resolve(stats);
};
- logger.info('Building storybook ...');
+ logger.info('building preview..');
const compiler = webpack(config);
if (watch) {
@@ -73,6 +105,7 @@ export async function buildStatic({ packageJson, ...loadOptions }) {
await buildStaticStandalone({
...cliOptions,
...loadOptions,
+ packageJson,
configDir: cliOptions.configDir || './.storybook',
outputDir: cliOptions.outputDir || './storybook-static',
});
diff --git a/lib/core/src/server/common/babel-cache-preset.js b/lib/core/src/server/common/babel-cache-preset.js
new file mode 100644
index 000000000000..a5b798468132
--- /dev/null
+++ b/lib/core/src/server/common/babel-cache-preset.js
@@ -0,0 +1,11 @@
+import findCacheDir from 'find-cache-dir';
+
+const extend = babelConfig => ({
+ // This is a feature of `babel-loader` for webpack (not Babel itself).
+ // It enables a cache directory for faster-rebuilds
+ // `find-cache-dir` will create the cache directory under the node_modules directory.
+ cacheDirectory: findCacheDir({ name: 'storybook' }),
+ ...babelConfig,
+});
+
+export { extend as babel, extend as managerBabel };
diff --git a/lib/core/src/server/common/babel.js b/lib/core/src/server/common/babel.js
new file mode 100644
index 000000000000..c0ed09f6e925
--- /dev/null
+++ b/lib/core/src/server/common/babel.js
@@ -0,0 +1,32 @@
+function createProdPresets() {
+ return [
+ [
+ require.resolve('babel-preset-minify'),
+ {
+ builtIns: false,
+ mangle: false,
+ },
+ ],
+ ];
+}
+
+export default ({ configType }) => {
+ const isProd = configType === 'PRODUCTION';
+ const prodPresets = isProd ? createProdPresets() : [];
+
+ return {
+ presets: [require.resolve('@babel/preset-env'), ...prodPresets],
+ plugins: [
+ require.resolve('babel-plugin-macros'),
+ require.resolve('@babel/plugin-transform-regenerator'),
+ require.resolve('@babel/plugin-proposal-class-properties'),
+ [
+ require.resolve('@babel/plugin-transform-runtime'),
+ {
+ helpers: true,
+ regenerator: true,
+ },
+ ],
+ ],
+ };
+};
diff --git a/lib/core/src/server/common/custom-presets.js b/lib/core/src/server/common/custom-presets.js
new file mode 100644
index 000000000000..aeabe5b9cb5a
--- /dev/null
+++ b/lib/core/src/server/common/custom-presets.js
@@ -0,0 +1,17 @@
+import path from 'path';
+import { logger } from '@storybook/node-logger';
+import serverRequire from '../utils/server-require';
+
+export default function loadCustomPresets({ configDir }) {
+ const presets = serverRequire(path.resolve(configDir, 'presets'));
+
+ if (presets) {
+ logger.warn(
+ '"Custom presets" is an experimental and undocumented feature that will be changed or deprecated soon. Use it on your own risk.'
+ );
+
+ return presets;
+ }
+
+ return [];
+}
diff --git a/lib/core/src/server/config/polyfills.js b/lib/core/src/server/common/polyfills.js
similarity index 100%
rename from lib/core/src/server/config/polyfills.js
rename to lib/core/src/server/common/polyfills.js
diff --git a/lib/core/src/server/config.js b/lib/core/src/server/config.js
index 83ac5604ad19..2c67e1fffcf4 100644
--- a/lib/core/src/server/config.js
+++ b/lib/core/src/server/config.js
@@ -1,54 +1,25 @@
-import path from 'path';
-import { logger } from '@storybook/node-logger';
import loadPresets from './presets';
-import serverRequire from './serverRequire';
+import loadCustomPresets from './common/custom-presets';
-function wrapCorePresets(presets) {
- return {
- babel: async (config, args) => presets.apply('babel', config, args),
- webpack: async (config, args) => presets.apply('webpack', config, args),
- preview: async (config, args) => presets.apply('preview', config, args),
- manager: async (config, args) => presets.apply('manager', config, args),
- };
-}
-
-function customPreset({ configDir }) {
- const presets = serverRequire(path.resolve(configDir, 'presets'));
-
- if (presets) {
- logger.warn(
- '"Custom presets" is an experimental and undocumented feature that will be changed or deprecated soon. Use it on your own risk.'
- );
-
- return presets;
- }
-
- return [];
-}
-
-async function getWebpackConfig(options, presets) {
- const babelOptions = await presets.babel({}, options);
-
- const entries = {
- iframe: await presets.preview([], options),
- manager: await presets.manager([], options),
- };
+async function getPreviewWebpackConfig(options, presets) {
+ const babelOptions = await presets.apply('babel', {}, options);
+ const entries = await presets.apply('entries', [], options);
- return presets.webpack({}, { ...options, babelOptions, entries });
+ return presets.apply('webpack', {}, { ...options, babelOptions, entries });
}
export default async options => {
- const { corePresets = [], frameworkPresets = [], ...restOptions } = options;
+ const { corePresets = [], frameworkPresets = [], overridePresets = [], ...restOptions } = options;
const presetsConfig = [
...corePresets,
- require.resolve('./core-preset-babel-cache.js'),
+ require.resolve('./common/babel-cache-preset.js'),
...frameworkPresets,
- ...customPreset(options),
- require.resolve('./core-preset-webpack-custom.js'),
+ ...loadCustomPresets(options),
+ ...overridePresets,
];
const presets = loadPresets(presetsConfig);
- return getWebpackConfig({ ...restOptions, presets }, wrapCorePresets(presets));
+ return getPreviewWebpackConfig({ ...restOptions, presets }, presets);
};
diff --git a/lib/core/src/server/config/babel.dev.js b/lib/core/src/server/config/babel.dev.js
deleted file mode 100644
index 0b6e5dbd19f6..000000000000
--- a/lib/core/src/server/config/babel.dev.js
+++ /dev/null
@@ -1,15 +0,0 @@
-export default {
- presets: [[require.resolve('@babel/preset-env')]],
- plugins: [
- require.resolve('babel-plugin-macros'),
- require.resolve('@babel/plugin-transform-regenerator'),
- require.resolve('@babel/plugin-proposal-class-properties'),
- [
- require.resolve('@babel/plugin-transform-runtime'),
- {
- helpers: true,
- regenerator: true,
- },
- ],
- ],
-};
diff --git a/lib/core/src/server/config/babel.prod.js b/lib/core/src/server/config/babel.prod.js
deleted file mode 100644
index 8bc12a641e12..000000000000
--- a/lib/core/src/server/config/babel.prod.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import baseConfig from './babel.dev';
-
-export default {
- ...baseConfig,
- presets: [
- ...baseConfig.presets,
- [
- require.resolve('babel-preset-minify'),
- {
- builtIns: false,
- mangle: false,
- },
- ],
- ],
-};
diff --git a/lib/core/src/server/config/entries.js b/lib/core/src/server/config/entries.js
deleted file mode 100644
index 262e0eb39b3b..000000000000
--- a/lib/core/src/server/config/entries.js
+++ /dev/null
@@ -1,29 +0,0 @@
-export async function createPreviewEntry(options) {
- const { configDir, presets } = options;
- const preview = [require.resolve('./polyfills'), require.resolve('./globals')];
-
- const configs = await presets.apply('config', [], options);
-
- if (!configs || !configs.length) {
- throw new Error(`=> Create a storybook config file in "${configDir}/config.{ext}".`);
- }
-
- preview.push(...configs);
-
- return preview;
-}
-
-export async function createManagerEntry(options) {
- const { presets } = options;
- const manager = [require.resolve('./polyfills')];
-
- const addons = await presets.apply('addons', [], options);
-
- if (addons && addons.length) {
- manager.push(...addons);
- }
-
- manager.push(require.resolve('../../client/manager'));
-
- return manager;
-}
diff --git a/lib/core/src/server/config/utils.js b/lib/core/src/server/config/utils.js
index bc91c80b47e7..402305b875ec 100644
--- a/lib/core/src/server/config/utils.js
+++ b/lib/core/src/server/config/utils.js
@@ -2,10 +2,8 @@ import path from 'path';
import { getEnvironment } from 'lazy-universal-dotenv';
export const includePaths = [path.resolve('./')];
-
-export const excludePaths = [path.resolve('node_modules')];
-
export const nodeModulesPaths = path.resolve('./node_modules');
+export const excludePaths = [nodeModulesPaths];
const nodePathsToArray = nodePath =>
nodePath
@@ -21,7 +19,7 @@ export function loadEnv(options = {}) {
NODE_ENV: process.env.NODE_ENV || defaultNodeEnv,
NODE_PATH: process.env.NODE_PATH || '',
// This is to support CRA's public folder feature.
- // In production we set this to dot(.) to allow the browser to access these assests
+ // In production we set this to dot(.) to allow the browser to access these assets
// even when deployed inside a subpath. (like in GitHub pages)
// In development this is just empty as we always serves from the root.
PUBLIC_URL: options.production ? '.' : '',
diff --git a/lib/core/src/server/config/webpack.config.dev.js b/lib/core/src/server/config/webpack.config.dev.js
deleted file mode 100644
index e090c92fbf47..000000000000
--- a/lib/core/src/server/config/webpack.config.dev.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import path from 'path';
-import webpack from 'webpack';
-import Dotenv from 'dotenv-webpack';
-import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
-import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
-import HtmlWebpackPlugin from 'html-webpack-plugin';
-
-import {
- includePaths,
- excludePaths,
- nodeModulesPaths,
- loadEnv,
- getBabelRuntimePath,
-} from './utils';
-import { getPreviewHeadHtml, getManagerHeadHtml, getPreviewBodyHtml } from '../utils';
-import { version } from '../../../package.json';
-
-export default ({ configDir, quiet, babelOptions, entries }) => {
- const { raw, stringified } = loadEnv();
- const entriesMeta = {
- iframe: {
- headHtmlSnippet: getPreviewHeadHtml(configDir, process.env),
- bodyHtmlSnippet: getPreviewBodyHtml(),
- },
- manager: {
- headHtmlSnippet: getManagerHeadHtml(configDir, process.env),
- },
- };
-
- return {
- mode: 'development',
- devtool: 'cheap-module-source-map',
- entry: entries,
- output: {
- path: path.join(__dirname, 'dist'),
- filename: 'static/[name].bundle.js',
- // Here we set the publicPath to ''.
- // This allows us to deploy storybook into subpaths like GitHub pages.
- // This works with css and image loaders too.
- // This is working for storybook since, we don't use pushState urls and
- // relative URLs works always.
- publicPath: '',
- },
- plugins: [
- ...Object.keys(entries).map(
- e =>
- new HtmlWebpackPlugin({
- filename: `${e === 'manager' ? 'index' : e}.html`,
- excludeChunks: Object.keys(entries).filter(i => i !== e),
- chunksSortMode: 'none',
- alwaysWriteToDisk: true,
- inject: false,
- templateParameters: (compilation, files, options) => ({
- compilation,
- files,
- options,
- version,
- ...entriesMeta[e],
- }),
- template: require.resolve(`../templates/index.ejs`),
- })
- ),
- new webpack.DefinePlugin({ 'process.env': stringified }),
- new webpack.HotModuleReplacementPlugin(),
- new CaseSensitivePathsPlugin(),
- new WatchMissingNodeModulesPlugin(nodeModulesPaths),
- quiet ? null : new webpack.ProgressPlugin(),
- new Dotenv({ silent: true }),
- ].filter(Boolean),
- module: {
- rules: [
- {
- test: /\.(mjs|jsx?)$/,
- use: [
- {
- loader: 'babel-loader',
- options: babelOptions,
- },
- ],
- include: includePaths,
- exclude: excludePaths,
- },
- {
- test: /\.md$/,
- use: [
- {
- loader: require.resolve('raw-loader'),
- },
- ],
- },
- ],
- },
- resolve: {
- // Since we ship with json-loader always, it's better to move extensions to here
- // from the default config.
- extensions: ['.mjs', '.js', '.jsx', '.json'],
- // Add support to NODE_PATH. With this we could avoid relative path imports.
- // Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253
- modules: ['node_modules'].concat(raw.NODE_PATH || []),
- alias: {
- '@babel/runtime': getBabelRuntimePath(),
- },
- },
- performance: {
- hints: false,
- },
- };
-};
diff --git a/lib/core/src/server/config/webpack.config.prod.js b/lib/core/src/server/config/webpack.config.prod.js
deleted file mode 100644
index 58a3c08619cb..000000000000
--- a/lib/core/src/server/config/webpack.config.prod.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import webpack from 'webpack';
-import Dotenv from 'dotenv-webpack';
-import HtmlWebpackPlugin from 'html-webpack-plugin';
-
-import { version } from '../../../package.json';
-import { includePaths, excludePaths, loadEnv, getBabelRuntimePath } from './utils';
-import { getPreviewHeadHtml, getManagerHeadHtml, getPreviewBodyHtml } from '../utils';
-
-export default ({ configDir, babelOptions, entries }) => {
- const { stringified, raw } = loadEnv({ production: true });
-
- const entriesMeta = {
- iframe: {
- headHtmlSnippet: getPreviewHeadHtml(configDir, process.env),
- bodyHtmlSnippet: getPreviewBodyHtml(),
- },
- manager: {
- headHtmlSnippet: getManagerHeadHtml(configDir, process.env),
- },
- };
-
- return {
- mode: 'production',
- bail: true,
- devtool: '#cheap-module-source-map',
- entry: entries,
- output: {
- filename: 'static/[name].[chunkhash].bundle.js',
- // Here we set the publicPath to ''.
- // This allows us to deploy storybook into subpaths like GitHub pages.
- // This works with css and image loaders too.
- // This is working for storybook since, we don't use pushState urls and
- // relative URLs works always.
- publicPath: '',
- },
- plugins: [
- ...Object.keys(entries).map(
- e =>
- new HtmlWebpackPlugin({
- filename: `${e === 'manager' ? 'index' : e}.html`,
- excludeChunks: Object.keys(entries).filter(i => i !== e),
- chunksSortMode: 'none',
- alwaysWriteToDisk: true,
- inject: false,
- templateParameters: (compilation, files, options) => ({
- compilation,
- files,
- options,
- version,
- ...entriesMeta[e],
- }),
- template: require.resolve(`../templates/index.ejs`),
- })
- ),
- new webpack.DefinePlugin({ 'process.env': stringified }),
- new Dotenv({ silent: true }),
- ],
- module: {
- rules: [
- {
- test: /\.(mjs|jsx?)$/,
- use: [
- {
- loader: 'babel-loader',
- options: babelOptions,
- },
- ],
- include: includePaths,
- exclude: excludePaths,
- },
- {
- test: /\.md$/,
- use: [
- {
- loader: require.resolve('raw-loader'),
- },
- ],
- },
- ],
- },
- resolve: {
- // Since we ship with json-loader always, it's better to move extensions to here
- // from the default config.
- extensions: ['.mjs', '.js', '.jsx', '.json'],
- // Add support to NODE_PATH. With this we could avoid relative path imports.
- // Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253
- modules: ['node_modules'].concat(raw.NODE_PATH || []),
- alias: {
- '@babel/runtime': getBabelRuntimePath(),
- },
- },
- optimization: {
- // Automatically split vendor and commons for preview bundle
- // https://twitter.com/wSokra/status/969633336732905474
- splitChunks: {
- chunks: chunk => chunk.name !== 'manager',
- },
- // Keep the runtime chunk seperated to enable long term caching
- // https://twitter.com/wSokra/status/969679223278505985
- runtimeChunk: true,
- },
- };
-};
diff --git a/lib/core/src/server/core-preset-babel-cache.js b/lib/core/src/server/core-preset-babel-cache.js
deleted file mode 100644
index 4abb4082c8d4..000000000000
--- a/lib/core/src/server/core-preset-babel-cache.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import findCacheDir from 'find-cache-dir';
-
-export function babel(babelConfig) {
- return {
- // This is a feature of `babel-loader` for webpack (not Babel itself).
- // It enables a cache directory for faster-rebuilds
- // `find-cache-dir` will create the cache directory under the node_modules directory.
- cacheDirectory: findCacheDir({ name: 'react-storybook' }),
- ...babelConfig,
- };
-}
diff --git a/lib/core/src/server/core-preset-dev.js b/lib/core/src/server/core-preset-dev.js
deleted file mode 100644
index 420b0c4619d5..000000000000
--- a/lib/core/src/server/core-preset-dev.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import loadCustomBabelConfig from './loadCustomBabelConfig';
-import loadCustomAddons from './loadCustomAddonsFile';
-import loadCustomConfig from './loadCustomConfigFile';
-import createDevConfig from './config/webpack.config.dev';
-import defaultBabelConfig from './config/babel.dev';
-import { createManagerEntry, createPreviewEntry } from './config/entries';
-
-export async function webpack(_, options) {
- return createDevConfig(options);
-}
-
-export async function babel(_, options) {
- const { configDir, presets } = options;
-
- return loadCustomBabelConfig(configDir, () =>
- presets.apply('babelDefault', defaultBabelConfig, options)
- );
-}
-
-export async function manager(_, options) {
- return createManagerEntry(options);
-}
-
-export async function preview(_, options) {
- const entry = await createPreviewEntry(options);
-
- return [...entry, `${require.resolve('webpack-hot-middleware/client')}?reload=true`];
-}
-
-export async function addons(_, options) {
- return loadCustomAddons(options);
-}
-
-export async function config(_, options) {
- return loadCustomConfig(options);
-}
diff --git a/lib/core/src/server/core-preset-prod.js b/lib/core/src/server/core-preset-prod.js
deleted file mode 100644
index ac50f2f2d224..000000000000
--- a/lib/core/src/server/core-preset-prod.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import loadCustomBabelConfig from './loadCustomBabelConfig';
-import loadCustomAddons from './loadCustomAddonsFile';
-import loadCustomConfig from './loadCustomConfigFile';
-import createProdConfig from './config/webpack.config.prod';
-import defaultBabelConfig from './config/babel.prod';
-import { createManagerEntry, createPreviewEntry } from './config/entries';
-
-export async function webpack(_, options) {
- return createProdConfig(options);
-}
-
-export async function babel(_, options) {
- const { configDir, presets } = options;
-
- return loadCustomBabelConfig(configDir, () =>
- presets.apply('babelDefault', defaultBabelConfig, options)
- );
-}
-
-export async function manager(_, options) {
- return createManagerEntry(options);
-}
-
-export async function preview(_, options) {
- return createPreviewEntry(options);
-}
-
-export async function addons(_, options) {
- return loadCustomAddons(options);
-}
-
-export async function config(_, options) {
- return loadCustomConfig(options);
-}
diff --git a/lib/core/src/server/dev-server.js b/lib/core/src/server/dev-server.js
new file mode 100644
index 000000000000..a06b2a0db59a
--- /dev/null
+++ b/lib/core/src/server/dev-server.js
@@ -0,0 +1,108 @@
+import path from 'path';
+import { Router } from 'express';
+import webpack from 'webpack';
+import childProcess from 'child-process-promise';
+import { logger } from '@storybook/node-logger';
+
+import webpackDevMiddleware from 'webpack-dev-middleware';
+import webpackHotMiddleware from 'webpack-hot-middleware';
+
+import { getMiddleware } from './utils/middleware';
+import loadConfig from './config';
+
+import { loadEnv } from './config/utils';
+
+let webpackResolve = () => {};
+let webpackReject = () => {};
+
+export const webpackValid = new Promise((resolve, reject) => {
+ webpackResolve = resolve;
+ webpackReject = reject;
+});
+
+export default async function(options) {
+ const environment = loadEnv();
+ const configDir = path.resolve(options.configDir);
+ const outputDir = path.resolve(options.outputDir || path.join(__dirname, '..', 'public'));
+ const configType = 'DEVELOPMENT';
+
+ const managerStartTime = process.hrtime();
+
+ // TODO: can we pass here the JSON.stringify(option)
+ const managerPromise = childProcess
+ .exec(`node ${path.join(__dirname, 'manager/webpack.js')} dir=${configDir} out=${outputDir}`, {
+ env: {
+ ...environment,
+ NODE_ENV: 'production',
+ },
+ })
+ .then(a => {
+ const managerTotalTime = process.hrtime(managerStartTime);
+ logger.trace({ message: 'manager built', time: managerTotalTime });
+
+ return a;
+ });
+
+ const iframeConfig = await loadConfig({
+ configType,
+ outputDir,
+ corePresets: [require.resolve('./preview/preview-preset.js')],
+ overridePresets: [require.resolve('./preview/custom-webpack-preset.js')],
+ ...options,
+ });
+
+ const middlewareFn = getMiddleware(configDir);
+
+ // remove the leading '/'
+ let { publicPath } = iframeConfig.output;
+ if (publicPath[0] === '/') {
+ publicPath = publicPath.slice(1);
+ }
+
+ const iframeStartTime = process.hrtime();
+ const iframeCompiler = webpack(iframeConfig);
+ const devMiddlewareOptions = {
+ noInfo: true,
+ publicPath: iframeConfig.output.publicPath,
+ watchOptions: iframeConfig.watchOptions || {},
+ ...iframeConfig.devServer,
+ };
+
+ const router = new Router();
+ const webpackDevMiddlewareInstance = webpackDevMiddleware(iframeCompiler, devMiddlewareOptions);
+ router.use(webpackDevMiddlewareInstance);
+ router.use(webpackHotMiddleware(iframeCompiler));
+
+ // custom middleware
+ middlewareFn(router);
+
+ const iframePromise = new Promise((res, rej) => {
+ webpackDevMiddlewareInstance.waitUntilValid(stats => {
+ const iframeTotalTime = process.hrtime(iframeStartTime);
+ logger.trace({ message: 'iframe built', time: iframeTotalTime });
+
+ if (stats.hasErrors()) {
+ rej(stats);
+ } else {
+ res(stats);
+ }
+ });
+ });
+
+ Promise.all([managerPromise, iframePromise])
+ .then(([, iframeStats]) => {
+ router.get('/', (request, response) => {
+ response.set('Content-Type', 'text/html');
+ response.sendFile(path.join(`${outputDir}/index.html`));
+ });
+ router.get(/(.+\.js)$/, (request, response) => {
+ response.set('Content-Type', 'text/javascript ');
+ response.sendFile(path.join(`${outputDir}/${request.params[0]}`));
+ });
+
+ webpackResolve(iframeStats);
+ })
+ .catch(e => webpackReject(e));
+
+ return router;
+}
diff --git a/lib/core/src/server/manager/manager-config.js b/lib/core/src/server/manager/manager-config.js
new file mode 100644
index 000000000000..50e4b6510d7b
--- /dev/null
+++ b/lib/core/src/server/manager/manager-config.js
@@ -0,0 +1,23 @@
+import loadPresets from '../presets';
+import loadCustomPresets from '../common/custom-presets';
+
+async function getManagerWebpackConfig(options, presets) {
+ const entries = await presets.apply('managerEntries', [], options);
+
+ return presets.apply('managerWebpack', {}, { ...options, entries });
+}
+
+export default async options => {
+ const { corePresets = [], overridePresets = [], ...restOptions } = options;
+
+ const presetsConfig = [
+ ...corePresets,
+ require.resolve('../common/babel-cache-preset.js'),
+ ...loadCustomPresets(options),
+ ...overridePresets,
+ ];
+
+ const presets = loadPresets(presetsConfig);
+
+ return getManagerWebpackConfig({ ...restOptions, presets }, presets);
+};
diff --git a/lib/core/src/server/manager/manager-preset.js b/lib/core/src/server/manager/manager-preset.js
new file mode 100644
index 000000000000..91956f8b1531
--- /dev/null
+++ b/lib/core/src/server/manager/manager-preset.js
@@ -0,0 +1,25 @@
+import loadCustomAddons from '../utils/load-custom-addons-file';
+import createDevConfig from './manager-webpack.config';
+
+export async function managerWebpack(_, options) {
+ return createDevConfig(options);
+}
+
+export async function managerEntries(_, options) {
+ const { presets } = options;
+ const entries = [require.resolve('../common/polyfills')];
+
+ const installedAddons = await presets.apply('addons', [], options);
+
+ if (installedAddons && installedAddons.length) {
+ entries.push(...installedAddons);
+ }
+
+ entries.push(require.resolve('../../client/manager'));
+
+ return entries;
+}
+
+export async function addons(_, options) {
+ return loadCustomAddons(options);
+}
diff --git a/lib/core/src/server/manager/manager-webpack.config.js b/lib/core/src/server/manager/manager-webpack.config.js
new file mode 100644
index 000000000000..aef6d3e7584c
--- /dev/null
+++ b/lib/core/src/server/manager/manager-webpack.config.js
@@ -0,0 +1,82 @@
+import webpack from 'webpack';
+import Dotenv from 'dotenv-webpack';
+import HtmlWebpackPlugin from 'html-webpack-plugin';
+import HtmlWebpackHarddiskPlugin from '@ndelangen/html-webpack-harddisk-plugin';
+import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
+
+import { version } from '../../../package.json';
+import { getManagerHeadHtml } from '../utils/template';
+import { loadEnv, getBabelRuntimePath } from '../config/utils';
+
+export default ({ configDir, entries, outputDir }) => {
+ const { raw, stringified } = loadEnv();
+
+ const exclude = /node_modules/;
+
+ return {
+ name: 'manager',
+ mode: 'production',
+ bail: true,
+ devtool: 'none',
+ entry: entries,
+ output: {
+ path: outputDir,
+ filename: '[name].[chunkhash].bundle.js',
+ publicPath: '',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: `index.html`,
+ chunksSortMode: 'none',
+ alwaysWriteToDisk: true,
+ inject: false,
+ templateParameters: (compilation, files, options) => ({
+ compilation,
+ files,
+ options,
+ version,
+ headHtmlSnippet: getManagerHeadHtml(configDir, process.env),
+ }),
+ template: require.resolve(`../templates/index.ejs`),
+ }),
+ new HtmlWebpackHarddiskPlugin(),
+ new webpack.DefinePlugin({ 'process.env': stringified }),
+ new CaseSensitivePathsPlugin(),
+ new Dotenv({ silent: true }),
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.jsx?$/,
+ use: [
+ {
+ loader: 'babel-loader',
+ },
+ ],
+ exclude,
+ },
+ {
+ test: /\.md$/,
+ use: [
+ {
+ loader: require.resolve('raw-loader'),
+ },
+ ],
+ },
+ ],
+ },
+ resolve: {
+ extensions: ['.mjs', '.js', '.jsx', '.json'],
+ modules: ['node_modules'].concat(raw.NODE_PATH || []),
+ alias: {
+ '@babel/runtime': getBabelRuntimePath(),
+ },
+ },
+ optimization: {
+ splitChunks: {
+ chunks: 'all',
+ },
+ runtimeChunk: true,
+ },
+ };
+};
diff --git a/lib/core/src/server/manager/webpack.js b/lib/core/src/server/manager/webpack.js
new file mode 100644
index 000000000000..2fcd1fea4d3b
--- /dev/null
+++ b/lib/core/src/server/manager/webpack.js
@@ -0,0 +1,47 @@
+import console from 'console';
+import webpack from 'webpack';
+
+import loadManagerConfig from './manager-config';
+
+const { Console } = console;
+
+const configDir = process.argv.reduce(
+ (acc, i) => (i.includes('dir=') ? i.replace('dir=', '') : acc),
+ './.storybook'
+);
+const outputDir = process.argv.reduce(
+ (acc, i) => (i.includes('out=') ? i.replace('out=', '') : acc),
+ undefined
+);
+
+const bad = new Console(process.stderr);
+const good = new Console(process.stdout);
+
+loadManagerConfig({
+ outputDir,
+ configDir,
+ corePresets: [require.resolve('./manager-preset.js')],
+})
+ .then(config => webpack(config))
+ .then(
+ compiler =>
+ new Promise((res, rej) => {
+ try {
+ compiler.run((err, stats) => {
+ if (err || stats.hasErrors()) {
+ bad.log(JSON.stringify({ err, data: stats.toJson() }, null, 2));
+ rej(err);
+ } else {
+ res(`success! ${process.env.NODE_ENV}`);
+ }
+ });
+ } catch (e) {
+ rej(e);
+ }
+ })
+ )
+ .then(data => good.log(data))
+ .catch(err => {
+ bad.log(err);
+ process.exit(1);
+ });
diff --git a/lib/core/src/server/middleware.js b/lib/core/src/server/middleware.js
deleted file mode 100644
index dd91e765a59b..000000000000
--- a/lib/core/src/server/middleware.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import path from 'path';
-import { Router } from 'express';
-import webpack from 'webpack';
-import webpackDevMiddleware from 'webpack-dev-middleware';
-import webpackHotMiddleware from 'webpack-hot-middleware';
-import { getMiddleware } from './utils';
-import loadConfig from './config';
-
-let webpackResolve = () => {};
-let webpackReject = () => {};
-
-export const webpackValid = new Promise((resolve, reject) => {
- webpackResolve = resolve;
- webpackReject = reject;
-});
-
-export default async function(options) {
- const { configDir } = options;
-
- // Build the webpack configuration using the `getBaseConfig`
- // custom `.babelrc` file and `webpack.config.js` files
- const config = await loadConfig({
- configType: 'DEVELOPMENT',
- corePresets: [require.resolve('./core-preset-dev.js')],
- ...options,
- });
- const middlewareFn = getMiddleware(configDir);
-
- // remove the leading '/'
- let { publicPath } = config.output;
- if (publicPath[0] === '/') {
- publicPath = publicPath.slice(1);
- }
-
- const compiler = webpack(config);
- const devMiddlewareOptions = {
- noInfo: true,
- publicPath: config.output.publicPath,
- watchOptions: config.watchOptions || {},
- ...config.devServer,
- };
-
- const router = new Router();
- const webpackDevMiddlewareInstance = webpackDevMiddleware(compiler, devMiddlewareOptions);
- router.use(webpackDevMiddlewareInstance);
- router.use(webpackHotMiddleware(compiler));
-
- // custom middleware
- middlewareFn(router);
-
- webpackDevMiddlewareInstance.waitUntilValid(stats => {
- router.get('/', (req, res) => {
- res.set('Content-Type', 'text/html');
- res.sendFile(path.join(`${__dirname}/public/index.html`));
- });
-
- router.get('/iframe.html', (req, res) => {
- res.set('Content-Type', 'text/html');
- res.sendFile(path.join(`${__dirname}/public/iframe.html`));
- });
-
- if (stats.toJson().errors.length) {
- webpackReject(stats);
- } else {
- webpackResolve(stats);
- }
- });
-
- return router;
-}
diff --git a/lib/core/src/server/presets.js b/lib/core/src/server/presets.js
index 9e8489fb6b75..68666de79734 100644
--- a/lib/core/src/server/presets.js
+++ b/lib/core/src/server/presets.js
@@ -2,7 +2,7 @@ import { logger } from '@storybook/node-logger';
function interopRequireDefault(filePath) {
// eslint-disable-next-line global-require,import/no-dynamic-require
- const result = require(filePath);
+ const result = require(`${filePath}`);
const isES6DefaultExported =
typeof result === 'object' && result !== null && typeof result.default !== 'undefined';
@@ -27,6 +27,7 @@ function loadPreset(preset) {
};
} catch (e) {
logger.warn(` Failed to load preset: ${JSON.stringify(preset)}`);
+ logger.error(e);
return false;
}
}
@@ -38,7 +39,9 @@ function loadPresets(presets) {
logger.info('=> Loading presets');
- return presets.map(loadPreset).filter(preset => preset);
+ const result = presets.map(loadPreset).filter(preset => preset);
+
+ return result;
}
function applyPresets(presets, config, args = {}, extension) {
diff --git a/lib/core/src/server/config/webpack.config.default.js b/lib/core/src/server/preview/base-webpack.config.js
similarity index 93%
rename from lib/core/src/server/config/webpack.config.default.js
rename to lib/core/src/server/preview/base-webpack.config.js
index ff4e7a18f60f..80be3b1c5de5 100644
--- a/lib/core/src/server/config/webpack.config.default.js
+++ b/lib/core/src/server/preview/base-webpack.config.js
@@ -20,10 +20,10 @@ export function createDefaultWebpackConfig(storybookBaseConfig) {
{
loader: require.resolve('postcss-loader'),
options: {
- ident: 'postcss', // https://webpack.js.org/guides/migrating/#complex-options
+ ident: 'postcss',
postcss: {},
plugins: () => [
- require('postcss-flexbugs-fixes'), // eslint-disable-line
+ require('postcss-flexbugs-fixes'), // eslint-disable-line global-require
autoprefixer({
flexbox: 'no-2009',
}),
diff --git a/lib/core/src/server/core-preset-webpack-custom.js b/lib/core/src/server/preview/custom-webpack-preset.js
similarity index 57%
rename from lib/core/src/server/core-preset-webpack-custom.js
rename to lib/core/src/server/preview/custom-webpack-preset.js
index 22458e39ca54..467d268902e7 100644
--- a/lib/core/src/server/core-preset-webpack-custom.js
+++ b/lib/core/src/server/preview/custom-webpack-preset.js
@@ -1,40 +1,32 @@
import { logger } from '@storybook/node-logger';
-import loadCustomWebpackConfig from './loadCustomWebpackConfig';
-import mergeConfigs from './mergeConfigs';
-import { createDefaultWebpackConfig } from './config/webpack.config.default';
+import loadCustomWebpackConfig from '../utils/load-custom-webpack-config';
+import mergeConfigs from '../utils/merge-webpack-config';
+import { createDefaultWebpackConfig } from './base-webpack.config';
-function informAboutCustomConfig(defaultConfigName) {
+function logConfigName(defaultConfigName) {
if (!defaultConfigName) {
logger.info('=> Using default webpack setup.');
- return;
+ } else {
+ logger.info(`=> Using default webpack setup based on "${defaultConfigName}".`);
}
-
- logger.info(`=> Using default webpack setup based on "${defaultConfigName}".`);
-}
-
-function wrapPresets(presets) {
- return {
- webpackFinal: async (config, args) => presets.apply('webpackFinal', config, args),
- };
}
async function createFinalDefaultConfig(presets, config, options) {
const defaultConfig = createDefaultWebpackConfig(config);
- return presets.webpackFinal(defaultConfig, options);
+ return presets.apply('webpackFinal', defaultConfig, options);
}
export async function webpack(config, options) {
- const { configDir, configType, defaultConfigName } = options;
- const presets = wrapPresets(options.presets);
+ const { configDir, configType, defaultConfigName, presets } = options;
- const finalConfig = await presets.webpackFinal(config, options);
+ const finalConfig = await presets.apply('webpackFinal', config, options);
// Check whether user has a custom webpack config file and
// return the (extended) base configuration if it's not available.
const customConfig = loadCustomWebpackConfig(configDir);
if (customConfig === null) {
- informAboutCustomConfig(defaultConfigName);
+ logConfigName(defaultConfigName);
return createFinalDefaultConfig(presets, config, options);
}
diff --git a/lib/core/src/server/preview/entries.js b/lib/core/src/server/preview/entries.js
new file mode 100644
index 000000000000..124bd5b0a014
--- /dev/null
+++ b/lib/core/src/server/preview/entries.js
@@ -0,0 +1,14 @@
+export async function createPreviewEntry(options) {
+ const { configDir, presets } = options;
+ const entries = [require.resolve('../common/polyfills'), require.resolve('./globals')];
+
+ const configs = await presets.apply('config', [], options);
+
+ if (!configs || !configs.length) {
+ throw new Error(`=> Create a storybook config file in "${configDir}/config.{ext}".`);
+ }
+
+ entries.push(...configs);
+
+ return entries;
+}
diff --git a/lib/core/src/server/config/globals.js b/lib/core/src/server/preview/globals.js
similarity index 100%
rename from lib/core/src/server/config/globals.js
rename to lib/core/src/server/preview/globals.js
diff --git a/lib/core/src/server/preview/iframe-webpack.config.js b/lib/core/src/server/preview/iframe-webpack.config.js
new file mode 100644
index 000000000000..b1408691f834
--- /dev/null
+++ b/lib/core/src/server/preview/iframe-webpack.config.js
@@ -0,0 +1,103 @@
+import path from 'path';
+import webpack from 'webpack';
+import Dotenv from 'dotenv-webpack';
+import HtmlWebpackPlugin from 'html-webpack-plugin';
+import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
+import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin';
+
+import {
+ includePaths,
+ excludePaths,
+ nodeModulesPaths,
+ loadEnv,
+ getBabelRuntimePath,
+} from '../config/utils';
+import { getPreviewHeadHtml, getPreviewBodyHtml } from '../utils/template';
+
+export default ({
+ configDir,
+ babelOptions,
+ entries,
+ outputDir = path.join('.', 'public'),
+ quiet,
+ packageJson,
+ configType,
+}) => {
+ const { raw, stringified } = loadEnv({ production: true });
+ const isProd = configType === 'PRODUCTION';
+
+ return {
+ mode: isProd ? 'production' : 'development',
+ bail: isProd,
+ devtool: '#cheap-module-source-map',
+ entry: entries,
+ output: {
+ path: path.join(process.cwd(), outputDir),
+ filename: '[name].[hash].bundle.js',
+ publicPath: '',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: `iframe.html`,
+ chunksSortMode: 'none',
+ alwaysWriteToDisk: true,
+ inject: false,
+ templateParameters: (compilation, files, options) => ({
+ compilation,
+ files,
+ options,
+ version: packageJson.version,
+ headHtmlSnippet: getPreviewHeadHtml(configDir, process.env),
+ bodyHtmlSnippet: getPreviewBodyHtml(),
+ }),
+ template: require.resolve(`../templates/index.ejs`),
+ }),
+ new webpack.DefinePlugin({ 'process.env': stringified }),
+ isProd ? null : new WatchMissingNodeModulesPlugin(nodeModulesPaths),
+ isProd ? null : new webpack.HotModuleReplacementPlugin(),
+ new CaseSensitivePathsPlugin(),
+ quiet ? null : new webpack.ProgressPlugin(),
+ new Dotenv({ silent: true }),
+ ].filter(Boolean),
+ module: {
+ rules: [
+ {
+ test: /\.(mjs|jsx?)$/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: babelOptions,
+ },
+ ],
+ include: includePaths,
+ exclude: excludePaths,
+ },
+ {
+ test: /\.md$/,
+ use: [
+ {
+ loader: require.resolve('raw-loader'),
+ },
+ ],
+ },
+ ],
+ },
+ resolve: {
+ extensions: ['.mjs', '.js', '.jsx', '.json'],
+ modules: ['node_modules'].concat(raw.NODE_PATH || []),
+ mainFields: ['browser', 'main', 'module'],
+ alias: {
+ '@babel/runtime': getBabelRuntimePath(),
+ },
+ },
+ optimization: {
+ splitChunks: {
+ chunks: 'all',
+ },
+ runtimeChunk: true,
+ },
+ performance: {
+ hints: isProd ? 'warning' : false,
+ },
+ };
+};
diff --git a/lib/core/src/server/preview/preview-preset.js b/lib/core/src/server/preview/preview-preset.js
new file mode 100644
index 000000000000..d2c56e182579
--- /dev/null
+++ b/lib/core/src/server/preview/preview-preset.js
@@ -0,0 +1,30 @@
+import loadCustomBabelConfig from '../utils/load-custom-babel-config';
+import loadCustomConfig from '../utils/load-custom-config-file';
+import babelConfig from '../common/babel';
+
+import webpackConfig from './iframe-webpack.config';
+import { createPreviewEntry } from './entries';
+
+export const webpack = async (_, options) => webpackConfig(options);
+
+export const babel = async (_, options) => {
+ const { configDir, presets } = options;
+
+ return loadCustomBabelConfig(configDir, () =>
+ presets.apply('babelDefault', babelConfig(options), options)
+ );
+};
+
+export const entries = async (_, options) => {
+ let result = [];
+
+ result = result.concat(await createPreviewEntry(options));
+
+ if (options.configType === 'DEVELOPMENT') {
+ result = result.concat(`${require.resolve('webpack-hot-middleware/client')}?reload=true`);
+ }
+
+ return result;
+};
+
+export const config = async (_, options) => loadCustomConfig(options);
diff --git a/lib/core/src/server/__snapshots__/mergeConfigs.test.js.snap b/lib/core/src/server/utils/__snapshots__/merge-webpack-config.test.js.snap
similarity index 100%
rename from lib/core/src/server/__snapshots__/mergeConfigs.test.js.snap
rename to lib/core/src/server/utils/__snapshots__/merge-webpack-config.test.js.snap
diff --git a/lib/core/src/server/interpret-files.js b/lib/core/src/server/utils/interpret-files.js
similarity index 100%
rename from lib/core/src/server/interpret-files.js
rename to lib/core/src/server/utils/interpret-files.js
diff --git a/lib/core/src/server/interpret-files.test.js b/lib/core/src/server/utils/interpret-files.test.js
similarity index 100%
rename from lib/core/src/server/interpret-files.test.js
rename to lib/core/src/server/utils/interpret-files.test.js
diff --git a/lib/core/src/server/loadCustomAddonsFile.js b/lib/core/src/server/utils/load-custom-addons-file.js
similarity index 99%
rename from lib/core/src/server/loadCustomAddonsFile.js
rename to lib/core/src/server/utils/load-custom-addons-file.js
index 90211dd5a954..b17f37c9c5ba 100644
--- a/lib/core/src/server/loadCustomAddonsFile.js
+++ b/lib/core/src/server/utils/load-custom-addons-file.js
@@ -1,5 +1,6 @@
import path from 'path';
import { logger } from '@storybook/node-logger';
+
import { getInterpretedFile } from './interpret-files';
function loadCustomAddons({ configDir }) {
diff --git a/lib/core/src/server/loadCustomBabelConfig.js b/lib/core/src/server/utils/load-custom-babel-config.js
similarity index 99%
rename from lib/core/src/server/loadCustomBabelConfig.js
rename to lib/core/src/server/utils/load-custom-babel-config.js
index 417ebcd5591f..6ba6a94ce333 100644
--- a/lib/core/src/server/loadCustomBabelConfig.js
+++ b/lib/core/src/server/utils/load-custom-babel-config.js
@@ -1,8 +1,9 @@
import fs from 'fs';
import path from 'path';
import JSON5 from 'json5';
-import { sync as resolveSync } from 'resolve';
import { satisfies } from 'semver';
+import { sync as resolveSync } from 'resolve';
+
import { logger } from '@storybook/node-logger';
function removeReactHmre(presets) {
diff --git a/lib/core/src/server/loadCustomConfigFile.js b/lib/core/src/server/utils/load-custom-config-file.js
similarity index 100%
rename from lib/core/src/server/loadCustomConfigFile.js
rename to lib/core/src/server/utils/load-custom-config-file.js
diff --git a/lib/core/src/server/loadCustomWebpackConfig.js b/lib/core/src/server/utils/load-custom-webpack-config.js
similarity index 81%
rename from lib/core/src/server/loadCustomWebpackConfig.js
rename to lib/core/src/server/utils/load-custom-webpack-config.js
index 20c2ab8856ed..73dcc0aa9476 100644
--- a/lib/core/src/server/loadCustomWebpackConfig.js
+++ b/lib/core/src/server/utils/load-custom-webpack-config.js
@@ -1,5 +1,5 @@
import path from 'path';
-import serverRequire from './serverRequire';
+import serverRequire from './server-require';
const webpackConfigs = ['webpack.config', 'webpackfile'];
diff --git a/lib/core/src/server/mergeConfigs.js b/lib/core/src/server/utils/merge-webpack-config.js
similarity index 100%
rename from lib/core/src/server/mergeConfigs.js
rename to lib/core/src/server/utils/merge-webpack-config.js
diff --git a/lib/core/src/server/mergeConfigs.test.js b/lib/core/src/server/utils/merge-webpack-config.test.js
similarity index 96%
rename from lib/core/src/server/mergeConfigs.test.js
rename to lib/core/src/server/utils/merge-webpack-config.test.js
index da2962bcb8e6..4a5c78aaf952 100644
--- a/lib/core/src/server/mergeConfigs.test.js
+++ b/lib/core/src/server/utils/merge-webpack-config.test.js
@@ -1,4 +1,4 @@
-import mergeConfigs from './mergeConfigs';
+import mergeConfigs from './merge-webpack-config';
const config = {
devtool: 'source-maps',
diff --git a/lib/core/src/server/utils/middleware.js b/lib/core/src/server/utils/middleware.js
new file mode 100644
index 000000000000..74928bc7a5c8
--- /dev/null
+++ b/lib/core/src/server/utils/middleware.js
@@ -0,0 +1,14 @@
+import path from 'path';
+import fs from 'fs';
+
+export function getMiddleware(configDir) {
+ const middlewarePath = path.resolve(configDir, 'middleware.js');
+ if (fs.existsSync(middlewarePath)) {
+ let middlewareModule = require(middlewarePath); // eslint-disable-line
+ if (middlewareModule.__esModule) { // eslint-disable-line
+ middlewareModule = middlewareModule.default;
+ }
+ return middlewareModule;
+ }
+ return () => {};
+}
diff --git a/lib/core/src/server/serverRequire.js b/lib/core/src/server/utils/server-require.js
similarity index 100%
rename from lib/core/src/server/serverRequire.js
rename to lib/core/src/server/utils/server-require.js
diff --git a/lib/core/src/server/utils.js b/lib/core/src/server/utils/template.js
similarity index 54%
rename from lib/core/src/server/utils.js
rename to lib/core/src/server/utils/template.js
index 985cd4483969..7b2bc51be54f 100644
--- a/lib/core/src/server/utils.js
+++ b/lib/core/src/server/utils/template.js
@@ -1,27 +1,18 @@
import path from 'path';
import fs from 'fs';
-export function getMiddleware(configDir) {
- const middlewarePath = path.resolve(configDir, 'middleware.js');
- if (fs.existsSync(middlewarePath)) {
- let middlewareModule = require(middlewarePath); // eslint-disable-line
- if (middlewareModule.__esModule) { // eslint-disable-line
- middlewareModule = middlewareModule.default;
- }
- return middlewareModule;
- }
- return () => {};
-}
-
const interpolate = (string, data = {}) =>
Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string);
export function getPreviewBodyHtml() {
- return fs.readFileSync(path.resolve(__dirname, 'templates/base-preview-body.html'), 'utf8');
+ return fs.readFileSync(path.resolve(__dirname, '../templates/base-preview-body.html'), 'utf8');
}
export function getPreviewHeadHtml(configDirPath, interpolations) {
- const base = fs.readFileSync(path.resolve(__dirname, 'templates/base-preview-head.html'), 'utf8');
+ const base = fs.readFileSync(
+ path.resolve(__dirname, '../templates/base-preview-head.html'),
+ 'utf8'
+ );
const headHtmlPath = path.resolve(configDirPath, 'preview-head.html');
let result = base;
@@ -34,7 +25,10 @@ export function getPreviewHeadHtml(configDirPath, interpolations) {
}
export function getManagerHeadHtml(configDirPath, interpolations) {
- const base = fs.readFileSync(path.resolve(__dirname, 'templates/base-manager-head.html'), 'utf8');
+ const base = fs.readFileSync(
+ path.resolve(__dirname, '../templates/base-manager-head.html'),
+ 'utf8'
+ );
const scriptPath = path.resolve(configDirPath, 'manager-head.html');
let result = base;
diff --git a/lib/core/src/server/utils.test.js b/lib/core/src/server/utils/template.test.js
similarity index 82%
rename from lib/core/src/server/utils.test.js
rename to lib/core/src/server/utils/template.test.js
index e5e6ad6445c2..debc72a0fbe3 100644
--- a/lib/core/src/server/utils.test.js
+++ b/lib/core/src/server/utils/template.test.js
@@ -1,5 +1,5 @@
import mock from 'mock-fs';
-import { getPreviewHeadHtml } from './utils';
+import { getPreviewHeadHtml } from './template';
const HEAD_HTML_CONTENTS = '';
const BASE_HTML_CONTENTS = '';
@@ -8,7 +8,7 @@ describe('server.getPreviewHeadHtml', () => {
describe('when .storybook/preview-head.html does not exist', () => {
beforeEach(() => {
mock({
- [`${__dirname}/templates/base-preview-head.html`]: BASE_HTML_CONTENTS,
+ [`${__dirname}/../templates/base-preview-head.html`]: BASE_HTML_CONTENTS,
config: {},
});
});
@@ -26,7 +26,7 @@ describe('server.getPreviewHeadHtml', () => {
describe('when .storybook/preview-head.html exists', () => {
beforeEach(() => {
mock({
- [`${__dirname}/templates/base-preview-head.html`]: BASE_HTML_CONTENTS,
+ [`${__dirname}/../templates/base-preview-head.html`]: BASE_HTML_CONTENTS,
config: {
'preview-head.html': HEAD_HTML_CONTENTS,
},
diff --git a/lib/node-logger/package.json b/lib/node-logger/package.json
index b5d207f35e26..816506d14647 100644
--- a/lib/node-logger/package.json
+++ b/lib/node-logger/package.json
@@ -24,6 +24,8 @@
},
"dependencies": {
"@babel/runtime": "^7.1.2",
- "npmlog": "^4.1.2"
+ "chalk": "^2.4.1",
+ "npmlog": "^4.1.2",
+ "pretty-hrtime": "^1.0.3"
}
}
diff --git a/lib/node-logger/src/index.js b/lib/node-logger/src/index.js
index b1b5784eedcc..bd166aebf037 100644
--- a/lib/node-logger/src/index.js
+++ b/lib/node-logger/src/index.js
@@ -1,7 +1,20 @@
import npmLog from 'npmlog';
+import prettyTime from 'pretty-hrtime';
+import chalk from 'chalk';
+
+export const colors = {
+ pink: chalk.hex('F1618C'),
+ purple: chalk.hex('B57EE5'),
+ orange: chalk.hex('F3AD38'),
+ green: chalk.hex('A2E05E'),
+ blue: chalk.hex('6DABF5'),
+ red: chalk.hex('F16161'),
+ gray: chalk.gray,
+};
export const logger = {
info: message => npmLog.info('', message),
warn: message => npmLog.warn('', message),
error: message => npmLog.error('', message),
+ trace: ({ message, time }) => npmLog.info(`${message} (${colors.purple(prettyTime(time))})`),
};
diff --git a/package.json b/package.json
index 8f1d8226304b..8563f769d313 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,8 @@
"repo-dirty-check": "node ./scripts/repo-dirty-check",
"start": "yarn --cwd examples/official-storybook storybook",
"test": "node ./scripts/test.js",
- "test-latest-cra": "yarn --cwd lib/cli run test-latest-cra"
+ "test-latest-cra": "yarn --prefix --cwd lib/cli run test-latest-cra",
+ "test:cli": "npm --prefix lib/cli run test"
},
"devDependencies": {
"@angular/common": "^7.0.1",
diff --git a/yarn.lock b/yarn.lock
index 4877a9565d42..6759645ddb6a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1818,11 +1818,18 @@
call-me-maybe "^1.0.1"
glob-to-regexp "^0.3.0"
+"@ndelangen/html-webpack-harddisk-plugin@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@ndelangen/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-0.2.0.tgz#d2eb570597c83c1aa93d1f2fcb3d874a5855de07"
+ integrity sha512-55Mo2b5WtIT0l653y6ocu7C6QzznbasEnlixGzA26WK8Fj81wuEY3xf5N5bNAvDVfrwTLIPTXdEUGgPdrPLszw==
+ dependencies:
+ mkdirp "^0.5.1"
"@ngrx/store@^6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-6.1.2.tgz#20fb5ab4d79571b804a348093aa11a167fe2946f"
integrity sha512-W9MbXrwhIRmN1BlINF9BT+rHR046e1HNk7GqykcDJrK9wW74PJW3aE5iuPb2sTPipBMjPHsXzc73E4U/+OTAyw==
+
"@ngtools/webpack@7.0.3":
version "7.0.3"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-7.0.3.tgz#96bc0d94e9a8ac84eb34cf81c59fdd21bfbd18e3"
@@ -17502,6 +17509,11 @@ pretty-format@^4.2.1:
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-4.3.1.tgz#530be5c42b3c05b36414a7a2a4337aa80acd0e8d"
integrity sha1-UwvlxCs8BbNkFKeipDN6qArNDo0=
+pretty-hrtime@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
+ integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
+
pretty-ms@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-3.2.0.tgz#87a8feaf27fc18414d75441467d411d6e6098a25"
@@ -20638,6 +20650,13 @@ spawn-command@^0.0.2-1:
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=
+spawn-promise@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/spawn-promise/-/spawn-promise-0.1.8.tgz#a5bea98814c48f52cbe02720e7fe2d6fc3b5119a"
+ integrity sha512-pTkEOFxvYLq9SaI1d8bwepj0yD9Yyz65+4e979YZLv/L3oYPxZpDTabcm6e+KIZniGK9mQ+LGrwB5s1v2z67nQ==
+ dependencies:
+ co "^4.6.0"
+
spawn-sync@^1.0.11, spawn-sync@^1.0.15:
version "1.0.15"
resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"