diff --git a/app/angular/package.json b/app/angular/package.json index a71c0b95574a..a0a670a08eca 100644 --- a/app/angular/package.json +++ b/app/angular/package.json @@ -43,8 +43,6 @@ "babel-preset-stage-0": "^6.24.1", "babel-runtime": "^6.23.0", "case-sensitive-paths-webpack-plugin": "^2.0.0", - "chalk": "^2.1.0", - "commander": "^2.14.0", "common-tags": "^1.7.2", "configstore": "^3.1.0", "core-js": "^2.4.1", @@ -76,17 +74,12 @@ "request": "^2.81.0", "rxjs": "^5.4.2", "sass-loader": "^6.0.6", - "serve-favicon": "^2.4.3", - "shelljs": "^0.8.1", "style-loader": "^0.20.1", "ts-loader": "^2.2.2", "uglifyjs-webpack-plugin": "^1.1.7", "url-loader": "^0.6.2", "util-deprecate": "^1.0.2", - "uuid": "^3.2.1", "webpack": "^2.5.1 || ^3.0.0", - "webpack-dev-middleware": "^1.10.2", - "webpack-hot-middleware": "^2.18.0", "zone.js": "^0.8.20" }, "devDependencies": { diff --git a/app/angular/src/server/build.js b/app/angular/src/server/build.js index cf7c810fbf34..804f258a0ba4 100755 --- a/app/angular/src/server/build.js +++ b/app/angular/src/server/build.js @@ -1,93 +1,12 @@ -import '@storybook/core/env'; - -import webpack from 'webpack'; -import program from 'commander'; +import { buildStatic } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import { logger } from '@storybook/node-logger'; import packageJson from '../../package.json'; import getBaseConfig from './config/webpack.config.prod'; import loadConfig from './config'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - -program - .version(packageJson.version) - .option('-s, --static-dir ', 'Directory where to load static files from', parseList) - .option('-o, --output-dir [dir-name]', 'Directory where to store built files') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option('-w, --watch', 'Enable watch mode') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}\n`)); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - staticDir: 'SBCONFIG_STATIC_DIR', - outputDir: 'SBCONFIG_OUTPUT_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', +buildStatic({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -const configDir = program.configDir || './.storybook'; -const outputDir = program.outputDir || './storybook-static'; - -// create output directory if not exists -shelljs.mkdir('-p', path.resolve(outputDir)); -// clear the static dir -shelljs.rm('-rf', path.resolve(outputDir, 'static')); -shelljs.cp(path.resolve(__dirname, 'public/favicon.ico'), outputDir); - -// 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 = loadConfig('PRODUCTION', getBaseConfig(configDir), configDir); -config.output.path = path.resolve(outputDir); - -// copy all static files -if (program.staticDir) { - program.staticDir.forEach(dir => { - if (!fs.existsSync(dir)) { - logger.error(`Error: no such directory to load static files: ${dir}`); - process.exit(-1); - } - logger.info(`=> Copying static files from: ${dir}`); - shelljs.cp('-r', `${dir}/*`, outputDir); - }); -} - -// compile all resources with webpack and write them to the disk. -logger.info('Building storybook ...'); -const webpackCb = (err, stats) => { - if (err || stats.hasErrors()) { - logger.error('Failed to build the storybook'); - // eslint-disable-next-line no-unused-expressions - err && logger.error(err.message); - // eslint-disable-next-line no-unused-expressions - stats && stats.hasErrors() && stats.toJson().errors.forEach(e => logger.error(e)); - process.exitCode = 1; - } - logger.info('Building storybook completed.'); -}; -const compiler = webpack(config); -if (program.watch) { - compiler.watch({}, webpackCb); -} else { - compiler.run(webpackCb); -} diff --git a/app/angular/src/server/config/webpack.config.js b/app/angular/src/server/config/webpack.config.js index 23794df5cc49..922c89d59429 100644 --- a/app/angular/src/server/config/webpack.config.js +++ b/app/angular/src/server/config/webpack.config.js @@ -4,8 +4,8 @@ import Dotenv from 'dotenv-webpack'; import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; +import { WatchMissingNodeModulesPlugin } from '@storybook/core/server'; import { managerPath } from '@storybook/core/client'; -import WatchMissingNodeModulesPlugin from './WatchMissingNodeModulesPlugin'; import { includePaths, excludePaths, nodeModulesPaths, loadEnv, nodePaths } from './utils'; import babelLoaderConfig from './babel'; diff --git a/app/angular/src/server/index.js b/app/angular/src/server/index.js index c0c5ff601dee..69df1fe59e54 100755 --- a/app/angular/src/server/index.js +++ b/app/angular/src/server/index.js @@ -1,165 +1,12 @@ -import '@storybook/core/env'; - -import express from 'express'; -import https from 'https'; -import favicon from 'serve-favicon'; -import program from 'commander'; +import { buildDev } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import { logger } from '@storybook/node-logger'; -import storybook, { webpackValid } from './middleware'; import packageJson from '../../package.json'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -program - .version(packageJson.version) - .option('-p, --port [number]', 'Port to run Storybook (Required)', parseInt) - .option('-h, --host [string]', 'Host to run Storybook') - .option('-s, --static-dir ', 'Directory where to load static files from') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option( - '--https', - 'Serve Storybook over HTTPS. Note: You must provide your own certificate information.' - ) - .option( - '--ssl-ca ', - 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', - parseList - ) - .option('--ssl-cert ', 'Provide an SSL certificate. (Required with --https)') - .option('--ssl-key ', 'Provide an SSL key. (Required with --https)') - .option('--smoke-test', 'Exit after successful start') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}`) + chalk.reset('\n')); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} - -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - port: 'SBCONFIG_PORT', - host: 'SBCONFIG_HOSTNAME', - staticDir: 'SBCONFIG_STATIC_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', -}); - -if (!program.port) { - logger.error('Error: port to run Storybook is required!\n'); - program.help(); - process.exit(-1); -} - -// Used with `app.listen` below -const listenAddr = [program.port]; - -if (program.host) { - listenAddr.push(program.host); -} - -const app = express(); -let server = app; - -if (program.https) { - if (!program.sslCert) { - logger.error('Error: --ssl-cert is required with --https'); - process.exit(-1); - } - if (!program.sslKey) { - logger.error('Error: --ssl-key is required with --https'); - process.exit(-1); - } - - const sslOptions = { - ca: (program.sslCa || []).map(ca => fs.readFileSync(ca, 'utf-8')), - cert: fs.readFileSync(program.sslCert, 'utf-8'), - key: fs.readFileSync(program.sslKey, 'utf-8'), - }; - - server = https.createServer(sslOptions, app); -} - -let hasCustomFavicon = false; - -if (program.staticDir) { - program.staticDir = parseList(program.staticDir); - program.staticDir.forEach(dir => { - const staticPath = path.resolve(dir); - if (!fs.existsSync(staticPath)) { - logger.error(`Error: no such directory to load static files: ${staticPath}`); - process.exit(-1); - } - logger.info(`=> Loading static files from: ${staticPath} .`); - app.use(express.static(staticPath, { index: false })); - - const faviconPath = path.resolve(staticPath, 'favicon.ico'); - if (fs.existsSync(faviconPath)) { - hasCustomFavicon = true; - app.use(favicon(faviconPath)); - } - }); -} - -if (!hasCustomFavicon) { - app.use(favicon(path.resolve(__dirname, 'public/favicon.ico'))); -} - -const configDir = program.configDir || './.storybook'; - -// The repository info is sent to the storybook while running on -// development mode so it'll be easier for tools to integrate. -const exec = cmd => shelljs.exec(cmd, { silent: true }).stdout.trim(); -process.env.STORYBOOK_GIT_ORIGIN = - process.env.STORYBOOK_GIT_ORIGIN || exec('git remote get-url origin'); -process.env.STORYBOOK_GIT_BRANCH = - process.env.STORYBOOK_GIT_BRANCH || exec('git symbolic-ref HEAD --short'); - -// NOTE changes to env should be done before calling `getBaseConfig` -// `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) { - serverReject(error); - } else { - serverResolve(); - } +import getBaseConfig from './config/webpack.config'; +import loadConfig from './config'; + +buildDev({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -Promise.all([webpackValid, serverListening]) - .then(() => { - const address = `http://${program.host || 'localhost'}:${program.port}/`; - logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); - if (program.smokeTest) { - process.exit(0); - } - }) - .catch(error => { - if (error instanceof Error) { - logger.error(error); - } - if (program.smokeTest) { - process.exit(1); - } - }); diff --git a/app/angular/src/server/utils.js b/app/angular/src/server/utils.js index 71396437a03d..913f4c3219ea 100644 --- a/app/angular/src/server/utils.js +++ b/app/angular/src/server/utils.js @@ -5,10 +5,6 @@ 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 getPreviewHeadHtml(configDirPath) { const headHtmlPath = path.resolve(configDirPath, 'preview-head.html'); const fallbackHtmlPath = path.resolve(configDirPath, 'head.html'); @@ -32,25 +28,3 @@ export function getManagerHeadHtml(configDirPath) { return scriptHtml; } - -export function getEnvConfig(program, configEnv) { - Object.keys(configEnv).forEach(fieldName => { - const envVarName = configEnv[fieldName]; - const envVarValue = process.env[envVarName]; - if (envVarValue) { - program[fieldName] = envVarValue; // eslint-disable-line - } - }); -} - -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/app/polymer/package.json b/app/polymer/package.json index 645d3656f663..d4aa352f73d3 100644 --- a/app/polymer/package.json +++ b/app/polymer/package.json @@ -45,8 +45,6 @@ "babel-preset-stage-0": "^6.24.1", "babel-runtime": "^6.26.0", "case-sensitive-paths-webpack-plugin": "^2.1.1", - "chalk": "^2.3.0", - "commander": "^2.14.0", "common-tags": "^1.4.0", "configstore": "^3.1.1", "copy-webpack-plugin": "^4.2.0", @@ -73,15 +71,11 @@ "react-modal": "^3.1.12", "redux": "^3.7.2", "request": "^2.83.0", - "serve-favicon": "^2.4.5", - "shelljs": "^0.8.1", "style-loader": "^0.20.1", + "uglifyjs-webpack-plugin": "^1.1.7", "url-loader": "^0.6.2", "util-deprecate": "^1.0.2", - "uuid": "^3.2.1", - "webpack": "^3.6.0", - "webpack-dev-middleware": "^1.12.0", - "webpack-hot-middleware": "^2.20.0" + "webpack": "^3.6.0" }, "devDependencies": { "babel-cli": "^6.26.0", diff --git a/app/polymer/src/server/build.js b/app/polymer/src/server/build.js index 3f05ed3b4b76..804f258a0ba4 100755 --- a/app/polymer/src/server/build.js +++ b/app/polymer/src/server/build.js @@ -1,93 +1,12 @@ -import '@storybook/core/env'; - -import webpack from 'webpack'; -import program from 'commander'; +import { buildStatic } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import { logger } from '@storybook/node-logger'; import packageJson from '../../package.json'; import getBaseConfig from './config/webpack.config.prod'; import loadConfig from './config'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - -program - .version(packageJson.version) - .option('-s, --static-dir ', 'Directory where to load static files from', parseList) - .option('-o, --output-dir [dir-name]', 'Directory where to store built files') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option('-w, --watch', 'Enable watch mode') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}\n`)); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - staticDir: 'SBCONFIG_STATIC_DIR', - outputDir: 'SBCONFIG_OUTPUT_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', +buildStatic({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -const configDir = program.configDir || './.storybook'; -const outputDir = program.outputDir || './storybook-static'; - -// create output directory if not exists -shelljs.mkdir('-p', path.resolve(outputDir)); -// clear the static dir -shelljs.rm('-rf', path.resolve(outputDir, 'static')); -shelljs.cp(path.resolve(__dirname, 'public/favicon.ico'), outputDir); - -// 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 = loadConfig('PRODUCTION', getBaseConfig(), configDir); -config.output.path = path.resolve(outputDir); - -// copy all static files -if (program.staticDir) { - program.staticDir.forEach(dir => { - if (!fs.existsSync(dir)) { - logger.error(`Error: no such directory to load static files: ${dir}`); - process.exit(-1); - } - logger.log(`=> Copying static files from: ${dir}`); - shelljs.cp('-r', `${dir}/*`, outputDir); - }); -} - -// compile all resources with webpack and write them to the disk. -logger.info('Building storybook ...'); -const webpackCb = (err, stats) => { - if (err || stats.hasErrors()) { - logger.error('Failed to build the storybook'); - // eslint-disable-next-line no-unused-expressions - err && logger.error(err.message); - // eslint-disable-next-line no-unused-expressions - stats && stats.hasErrors() && stats.toJson().errors.forEach(e => logger.error(e)); - process.exitCode = 1; - } - logger.info('Building storybook completed.'); -}; -const compiler = webpack(config); -if (program.watch) { - compiler.watch({}, webpackCb); -} else { - compiler.run(webpackCb); -} diff --git a/app/polymer/src/server/config/WatchMissingNodeModulesPlugin.js b/app/polymer/src/server/config/WatchMissingNodeModulesPlugin.js deleted file mode 100644 index 962bbb97ff45..000000000000 --- a/app/polymer/src/server/config/WatchMissingNodeModulesPlugin.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -// @remove-on-eject-end - -// This Webpack plugin ensures `npm install ` forces a project rebuild. -// We’re not sure why this isn't Webpack's default behavior. -// See https://github.com/facebookincubator/create-react-app/issues/186. - -function WatchMissingNodeModulesPlugin(nodeModulesPath) { - this.nodeModulesPath = nodeModulesPath; -} - -WatchMissingNodeModulesPlugin.prototype.apply = function apply(compiler) { - compiler.plugin('emit', (compilation, callback) => { - const missingDeps = compilation.missingDependencies; - const { nodeModulesPath } = this; - - // If any missing files are expected to appear in node_modules... - if (missingDeps.some(file => file.indexOf(nodeModulesPath) !== -1)) { - // ...tell webpack to watch node_modules recursively until they appear. - compilation.contextDependencies.push(nodeModulesPath); - } - - callback(); - }); -}; - -module.exports = WatchMissingNodeModulesPlugin; diff --git a/app/polymer/src/server/config/utils.js b/app/polymer/src/server/config/utils.js index 0236481efd78..fc73c38a37f0 100644 --- a/app/polymer/src/server/config/utils.js +++ b/app/polymer/src/server/config/utils.js @@ -33,5 +33,3 @@ export function loadEnv(options = {}) { 'process.env': env, }; } - -export const getConfigDir = () => process.env.SBCONFIG_CONFIG_DIR || './.storybook'; diff --git a/app/polymer/src/server/config/webpack.config.js b/app/polymer/src/server/config/webpack.config.js index daf5560ddb84..925ce22dd1a8 100644 --- a/app/polymer/src/server/config/webpack.config.js +++ b/app/polymer/src/server/config/webpack.config.js @@ -5,22 +5,15 @@ import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; +import { WatchMissingNodeModulesPlugin } from '@storybook/core/server'; import { managerPath } from '@storybook/core/client'; -import WatchMissingNodeModulesPlugin from './WatchMissingNodeModulesPlugin'; -import { - getConfigDir, - includePaths, - excludePaths, - nodeModulesPaths, - loadEnv, - nodePaths, -} from './utils'; +import { includePaths, excludePaths, nodeModulesPaths, loadEnv, nodePaths } from './utils'; import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils'; import babelLoaderConfig from './babel'; import { version } from '../../../package.json'; -export default function() { +export default function(configDir) { const config = { devtool: 'cheap-module-source-map', entry: { @@ -42,7 +35,7 @@ export default function() { filename: 'index.html', chunks: ['manager'], data: { - managerHead: getManagerHeadHtml(getConfigDir()), + managerHead: getManagerHeadHtml(configDir), version, }, template: require.resolve('../index.html.ejs'), @@ -51,7 +44,7 @@ export default function() { filename: 'iframe.html', excludeChunks: ['manager'], data: { - previewHead: getPreviewHeadHtml(getConfigDir()), + previewHead: getPreviewHeadHtml(configDir), }, template: require.resolve('../iframe.html.ejs'), }), diff --git a/app/polymer/src/server/config/webpack.config.prod.js b/app/polymer/src/server/config/webpack.config.prod.js index 5c8e5bfd55c7..6ec48b6508cb 100644 --- a/app/polymer/src/server/config/webpack.config.prod.js +++ b/app/polymer/src/server/config/webpack.config.prod.js @@ -1,15 +1,16 @@ import webpack from 'webpack'; +import UglifyJsPlugin from 'uglifyjs-webpack-plugin'; import Dotenv from 'dotenv-webpack'; import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; import { managerPath } from '@storybook/core/client'; import babelLoaderConfig from './babel.prod'; -import { getConfigDir, includePaths, excludePaths, loadEnv, nodePaths } from './utils'; +import { includePaths, excludePaths, loadEnv, nodePaths } from './utils'; import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils'; import { version } from '../../../package.json'; -export default function() { +export default function(configDir) { const entries = { preview: [require.resolve('./polyfills'), require.resolve('./globals')], manager: [require.resolve('./polyfills'), managerPath], @@ -34,7 +35,7 @@ export default function() { filename: 'index.html', chunks: ['manager'], data: { - managerHead: getManagerHeadHtml(getConfigDir()), + managerHead: getManagerHeadHtml(configDir), version, }, template: require.resolve('../index.html.ejs'), @@ -43,7 +44,7 @@ export default function() { filename: 'iframe.html', excludeChunks: ['manager'], data: { - previewHead: getPreviewHeadHtml(getConfigDir()), + previewHead: getPreviewHeadHtml(configDir), }, template: require.resolve('../iframe.html.ejs'), }), @@ -52,15 +53,18 @@ export default function() { { from: require.resolve('@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js') }, ]), new webpack.DefinePlugin(loadEnv({ production: true })), - new webpack.optimize.UglifyJsPlugin({ - compress: { - screw_ie8: true, + new UglifyJsPlugin({ + parallel: true, + uglifyOptions: { + ie8: false, + mangle: false, warnings: false, - }, - mangle: false, - output: { - comments: false, - screw_ie8: true, + compress: { + keep_fnames: true, + }, + output: { + comments: false, + }, }, }), new Dotenv({ silent: true }), diff --git a/app/polymer/src/server/index.js b/app/polymer/src/server/index.js index f4e504bf0662..69df1fe59e54 100755 --- a/app/polymer/src/server/index.js +++ b/app/polymer/src/server/index.js @@ -1,168 +1,12 @@ -import '@storybook/core/env'; - -import express from 'express'; -import https from 'https'; -import favicon from 'serve-favicon'; -import program from 'commander'; +import { buildDev } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import { logger } from '@storybook/node-logger'; -import storybook, { webpackValid } from './middleware'; import packageJson from '../../package.json'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -program - .version(packageJson.version) - .option('-p, --port [number]', 'Port to run Storybook (Required)', str => parseInt(str, 10)) - .option('-h, --host [string]', 'Host to run Storybook') - .option('-s, --static-dir ', 'Directory where to load static files from') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option( - '--https', - 'Serve Storybook over HTTPS. Note: You must provide your own certificate information.' - ) - .option( - '--ssl-ca ', - 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', - parseList - ) - .option('--ssl-cert ', 'Provide an SSL certificate. (Required with --https)') - .option('--ssl-key ', 'Provide an SSL key. (Required with --https)') - .option('--smoke-test', 'Exit after successful start') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}`) + chalk.reset('\n')); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} - -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - port: 'SBCONFIG_PORT', - host: 'SBCONFIG_HOSTNAME', - staticDir: 'SBCONFIG_STATIC_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', -}); - -if (!program.port) { - logger.error('Error: port to run Storybook is required!\n'); - program.help(); - process.exit(-1); -} - -// Used with `app.listen` below -const listenAddr = [program.port]; - -if (program.host) { - listenAddr.push(program.host); -} - -const app = express(); -let server = app; - -if (program.https) { - if (!program.sslCert) { - logger.error('Error: --ssl-cert is required with --https'); - process.exit(-1); - } - if (!program.sslKey) { - logger.error('Error: --ssl-key is required with --https'); - process.exit(-1); - } - - const sslOptions = { - ca: (program.sslCa || []).map(ca => fs.readFileSync(ca, 'utf-8')), - cert: fs.readFileSync(program.sslCert, 'utf-8'), - key: fs.readFileSync(program.sslKey, 'utf-8'), - }; - - server = https.createServer(sslOptions, app); -} - -let hasCustomFavicon = false; - -if (program.staticDir) { - program.staticDir = parseList(program.staticDir); - program.staticDir.forEach(dir => { - const staticPath = path.resolve(dir); - if (!fs.existsSync(staticPath)) { - logger.error(`Error: no such directory to load static files: ${staticPath}`); - process.exit(-1); - } - logger.log(`=> Loading static files from: ${staticPath} .`); - app.use(express.static(staticPath, { index: false })); - - const faviconPath = path.resolve(staticPath, 'favicon.ico'); - if (fs.existsSync(faviconPath)) { - hasCustomFavicon = true; - app.use(favicon(faviconPath)); - } - }); -} - -if (!hasCustomFavicon) { - app.use(favicon(path.resolve(__dirname, 'public/favicon.ico'))); -} - -// Build the webpack configuration using the `baseConfig` -// custom `.babelrc` file and `webpack.config.js` files -const configDir = program.configDir || './.storybook'; - -// The repository info is sent to the storybook while running on -// development mode so it'll be easier for tools to integrate. -const exec = cmd => shelljs.exec(cmd, { silent: true }).stdout.trim(); -process.env.STORYBOOK_GIT_ORIGIN = - process.env.STORYBOOK_GIT_ORIGIN || exec('git remote get-url origin'); -process.env.STORYBOOK_GIT_BRANCH = - process.env.STORYBOOK_GIT_BRANCH || exec('git symbolic-ref HEAD --short'); - -// NOTE changes to env should be done before calling `getBaseConfig` -// `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) { - serverReject(error); - } else { - serverResolve(); - } +import getBaseConfig from './config/webpack.config'; +import loadConfig from './config'; + +buildDev({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -Promise.all([webpackValid, serverListening]) - .then(() => { - const proto = program.https ? 'https' : 'http'; - const address = `${proto}://${program.host || 'localhost'}:${program.port}/`; - logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); - if (program.smokeTest) { - process.exit(0); - } - }) - .catch(error => { - if (error instanceof Error) { - logger.error(error); - } - if (program.smokeTest) { - process.exit(1); - } - }); diff --git a/app/polymer/src/server/middleware.js b/app/polymer/src/server/middleware.js deleted file mode 100644 index 1dd6fa775b11..000000000000 --- a/app/polymer/src/server/middleware.js +++ /dev/null @@ -1,64 +0,0 @@ -import { Router } from 'express'; -import webpack from 'webpack'; -import path from 'path'; -import webpackDevMiddleware from 'webpack-dev-middleware'; -import webpackHotMiddleware from 'webpack-hot-middleware'; -import getBaseConfig from './config/webpack.config'; -import loadConfig from './config'; -import { 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 - const config = loadConfig('DEVELOPMENT', getBaseConfig(), configDir); - 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/app/polymer/src/server/utils.js b/app/polymer/src/server/utils.js index 71396437a03d..913f4c3219ea 100644 --- a/app/polymer/src/server/utils.js +++ b/app/polymer/src/server/utils.js @@ -5,10 +5,6 @@ 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 getPreviewHeadHtml(configDirPath) { const headHtmlPath = path.resolve(configDirPath, 'preview-head.html'); const fallbackHtmlPath = path.resolve(configDirPath, 'head.html'); @@ -32,25 +28,3 @@ export function getManagerHeadHtml(configDirPath) { return scriptHtml; } - -export function getEnvConfig(program, configEnv) { - Object.keys(configEnv).forEach(fieldName => { - const envVarName = configEnv[fieldName]; - const envVarValue = process.env[envVarName]; - if (envVarValue) { - program[fieldName] = envVarValue; // eslint-disable-line - } - }); -} - -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/app/react/package.json b/app/react/package.json index 12eedb0578af..3c9786b1331c 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -45,8 +45,6 @@ "babel-preset-stage-0": "^6.24.1", "babel-runtime": "^6.26.0", "case-sensitive-paths-webpack-plugin": "^2.1.1", - "chalk": "^2.3.0", - "commander": "^2.14.0", "common-tags": "^1.7.2", "configstore": "^3.1.1", "core-js": "^2.5.3", @@ -73,16 +71,11 @@ "react-dev-utils": "^5.0.0", "redux": "^3.7.2", "request": "^2.83.0", - "serve-favicon": "^2.4.5", - "shelljs": "^0.8.1", "style-loader": "^0.20.1", "uglifyjs-webpack-plugin": "^1.1.7", "url-loader": "^0.6.2", "util-deprecate": "^1.0.2", - "uuid": "^3.2.1", - "webpack": "^3.10.0", - "webpack-dev-middleware": "^1.12.2", - "webpack-hot-middleware": "^2.21.0" + "webpack": "^3.10.0" }, "devDependencies": { "nodemon": "^1.14.12" diff --git a/app/react/src/server/build.js b/app/react/src/server/build.js index cf7c810fbf34..804f258a0ba4 100755 --- a/app/react/src/server/build.js +++ b/app/react/src/server/build.js @@ -1,93 +1,12 @@ -import '@storybook/core/env'; - -import webpack from 'webpack'; -import program from 'commander'; +import { buildStatic } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import { logger } from '@storybook/node-logger'; import packageJson from '../../package.json'; import getBaseConfig from './config/webpack.config.prod'; import loadConfig from './config'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - -program - .version(packageJson.version) - .option('-s, --static-dir ', 'Directory where to load static files from', parseList) - .option('-o, --output-dir [dir-name]', 'Directory where to store built files') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option('-w, --watch', 'Enable watch mode') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}\n`)); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - staticDir: 'SBCONFIG_STATIC_DIR', - outputDir: 'SBCONFIG_OUTPUT_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', +buildStatic({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -const configDir = program.configDir || './.storybook'; -const outputDir = program.outputDir || './storybook-static'; - -// create output directory if not exists -shelljs.mkdir('-p', path.resolve(outputDir)); -// clear the static dir -shelljs.rm('-rf', path.resolve(outputDir, 'static')); -shelljs.cp(path.resolve(__dirname, 'public/favicon.ico'), outputDir); - -// 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 = loadConfig('PRODUCTION', getBaseConfig(configDir), configDir); -config.output.path = path.resolve(outputDir); - -// copy all static files -if (program.staticDir) { - program.staticDir.forEach(dir => { - if (!fs.existsSync(dir)) { - logger.error(`Error: no such directory to load static files: ${dir}`); - process.exit(-1); - } - logger.info(`=> Copying static files from: ${dir}`); - shelljs.cp('-r', `${dir}/*`, outputDir); - }); -} - -// compile all resources with webpack and write them to the disk. -logger.info('Building storybook ...'); -const webpackCb = (err, stats) => { - if (err || stats.hasErrors()) { - logger.error('Failed to build the storybook'); - // eslint-disable-next-line no-unused-expressions - err && logger.error(err.message); - // eslint-disable-next-line no-unused-expressions - stats && stats.hasErrors() && stats.toJson().errors.forEach(e => logger.error(e)); - process.exitCode = 1; - } - logger.info('Building storybook completed.'); -}; -const compiler = webpack(config); -if (program.watch) { - compiler.watch({}, webpackCb); -} else { - compiler.run(webpackCb); -} diff --git a/app/react/src/server/config/WatchMissingNodeModulesPlugin.js b/app/react/src/server/config/WatchMissingNodeModulesPlugin.js deleted file mode 100644 index 962bbb97ff45..000000000000 --- a/app/react/src/server/config/WatchMissingNodeModulesPlugin.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -// @remove-on-eject-end - -// This Webpack plugin ensures `npm install ` forces a project rebuild. -// We’re not sure why this isn't Webpack's default behavior. -// See https://github.com/facebookincubator/create-react-app/issues/186. - -function WatchMissingNodeModulesPlugin(nodeModulesPath) { - this.nodeModulesPath = nodeModulesPath; -} - -WatchMissingNodeModulesPlugin.prototype.apply = function apply(compiler) { - compiler.plugin('emit', (compilation, callback) => { - const missingDeps = compilation.missingDependencies; - const { nodeModulesPath } = this; - - // If any missing files are expected to appear in node_modules... - if (missingDeps.some(file => file.indexOf(nodeModulesPath) !== -1)) { - // ...tell webpack to watch node_modules recursively until they appear. - compilation.contextDependencies.push(nodeModulesPath); - } - - callback(); - }); -}; - -module.exports = WatchMissingNodeModulesPlugin; diff --git a/app/react/src/server/config/webpack.config.js b/app/react/src/server/config/webpack.config.js index 1b107f2278ab..cfedbdc3b89f 100644 --- a/app/react/src/server/config/webpack.config.js +++ b/app/react/src/server/config/webpack.config.js @@ -4,9 +4,9 @@ import Dotenv from 'dotenv-webpack'; import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; +import { WatchMissingNodeModulesPlugin } from '@storybook/core/server'; import { managerPath } from '@storybook/core/client'; -import WatchMissingNodeModulesPlugin from './WatchMissingNodeModulesPlugin'; import { includePaths, excludePaths, nodeModulesPaths, loadEnv, nodePaths } from './utils'; import babelLoaderConfig from './babel'; import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils'; diff --git a/app/react/src/server/index.js b/app/react/src/server/index.js index df61018103a7..69df1fe59e54 100755 --- a/app/react/src/server/index.js +++ b/app/react/src/server/index.js @@ -1,168 +1,12 @@ -import '@storybook/core/env'; - -import express from 'express'; -import https from 'https'; -import favicon from 'serve-favicon'; -import program from 'commander'; +import { buildDev } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import { logger } from '@storybook/node-logger'; -import storybook, { webpackValid } from './middleware'; import packageJson from '../../package.json'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -program - .version(packageJson.version) - .option('-p, --port [number]', 'Port to run Storybook (Required)', str => parseInt(str, 10)) - .option('-h, --host [string]', 'Host to run Storybook') - .option('-s, --static-dir ', 'Directory where to load static files from') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option( - '--https', - 'Serve Storybook over HTTPS. Note: You must provide your own certificate information.' - ) - .option( - '--ssl-ca ', - 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', - parseList - ) - .option('--ssl-cert ', 'Provide an SSL certificate. (Required with --https)') - .option('--ssl-key ', 'Provide an SSL key. (Required with --https)') - .option('--smoke-test', 'Exit after successful start') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}`) + chalk.reset('\n')); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} - -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - port: 'SBCONFIG_PORT', - host: 'SBCONFIG_HOSTNAME', - staticDir: 'SBCONFIG_STATIC_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', -}); - -if (!program.port) { - logger.error('Error: port to run Storybook is required!\n'); - program.help(); - process.exit(-1); -} - -// Used with `app.listen` below -const listenAddr = [program.port]; - -if (program.host) { - listenAddr.push(program.host); -} - -const app = express(); -let server = app; - -if (program.https) { - if (!program.sslCert) { - logger.error('Error: --ssl-cert is required with --https'); - process.exit(-1); - } - if (!program.sslKey) { - logger.error('Error: --ssl-key is required with --https'); - process.exit(-1); - } - - const sslOptions = { - ca: (program.sslCa || []).map(ca => fs.readFileSync(ca, 'utf-8')), - cert: fs.readFileSync(program.sslCert, 'utf-8'), - key: fs.readFileSync(program.sslKey, 'utf-8'), - }; - - server = https.createServer(sslOptions, app); -} - -let hasCustomFavicon = false; - -if (program.staticDir) { - program.staticDir = parseList(program.staticDir); - program.staticDir.forEach(dir => { - const staticPath = path.resolve(dir); - if (!fs.existsSync(staticPath)) { - logger.error(`Error: no such directory to load static files: ${staticPath}`); - process.exit(-1); - } - logger.info(`=> Loading static files from: ${staticPath} .`); - app.use(express.static(staticPath, { index: false })); - - const faviconPath = path.resolve(staticPath, 'favicon.ico'); - if (fs.existsSync(faviconPath)) { - hasCustomFavicon = true; - app.use(favicon(faviconPath)); - } - }); -} - -if (!hasCustomFavicon) { - app.use(favicon(path.resolve(__dirname, 'public/favicon.ico'))); -} - -// Build the webpack configuration using the `baseConfig` -// custom `.babelrc` file and `webpack.config.js` files -const configDir = program.configDir || './.storybook'; - -// The repository info is sent to the storybook while running on -// development mode so it'll be easier for tools to integrate. -const exec = cmd => shelljs.exec(cmd, { silent: true }).stdout.trim(); -process.env.STORYBOOK_GIT_ORIGIN = - process.env.STORYBOOK_GIT_ORIGIN || exec('git remote get-url origin'); -process.env.STORYBOOK_GIT_BRANCH = - process.env.STORYBOOK_GIT_BRANCH || exec('git symbolic-ref HEAD --short'); - -// NOTE changes to env should be done before calling `getBaseConfig` -// `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) { - serverReject(error); - } else { - serverResolve(); - } +import getBaseConfig from './config/webpack.config'; +import loadConfig from './config'; + +buildDev({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -Promise.all([webpackValid, serverListening]) - .then(() => { - const proto = program.https ? 'https' : 'http'; - const address = `${proto}://${program.host || 'localhost'}:${program.port}/`; - logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); - if (program.smokeTest) { - process.exit(0); - } - }) - .catch(error => { - if (error instanceof Error) { - logger.error(error); - } - if (program.smokeTest) { - process.exit(1); - } - }); diff --git a/app/react/src/server/middleware.js b/app/react/src/server/middleware.js deleted file mode 100644 index 28e8554bccf5..000000000000 --- a/app/react/src/server/middleware.js +++ /dev/null @@ -1,64 +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 getBaseConfig from './config/webpack.config'; -import loadConfig from './config'; -import { 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 - const config = loadConfig('DEVELOPMENT', getBaseConfig(configDir), configDir); - 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/app/react/src/server/utils.js b/app/react/src/server/utils.js index 71396437a03d..913f4c3219ea 100644 --- a/app/react/src/server/utils.js +++ b/app/react/src/server/utils.js @@ -5,10 +5,6 @@ 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 getPreviewHeadHtml(configDirPath) { const headHtmlPath = path.resolve(configDirPath, 'preview-head.html'); const fallbackHtmlPath = path.resolve(configDirPath, 'head.html'); @@ -32,25 +28,3 @@ export function getManagerHeadHtml(configDirPath) { return scriptHtml; } - -export function getEnvConfig(program, configEnv) { - Object.keys(configEnv).forEach(fieldName => { - const envVarName = configEnv[fieldName]; - const envVarValue = process.env[envVarName]; - if (envVarValue) { - program[fieldName] = envVarValue; // eslint-disable-line - } - }); -} - -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/app/vue/package.json b/app/vue/package.json index 739e4c4266b8..058786c5a089 100644 --- a/app/vue/package.json +++ b/app/vue/package.json @@ -43,8 +43,6 @@ "babel-preset-stage-0": "^6.24.1", "babel-runtime": "^6.26.0", "case-sensitive-paths-webpack-plugin": "^2.1.1", - "chalk": "^2.3.0", - "commander": "^2.14.0", "common-tags": "^1.7.2", "configstore": "^3.1.1", "core-js": "^2.5.3", @@ -69,18 +67,13 @@ "react-dom": "^16.2.0", "redux": "^3.7.2", "request": "^2.83.0", - "serve-favicon": "^2.4.5", - "shelljs": "^0.8.1", "style-loader": "^0.20.1", "uglifyjs-webpack-plugin": "^1.1.7", "url-loader": "^0.6.2", "util-deprecate": "^1.0.2", - "uuid": "^3.2.1", "vue-hot-reload-api": "^2.2.4", "vue-style-loader": "^3.1.2", - "webpack": "^3.10.0", - "webpack-dev-middleware": "^1.12.2", - "webpack-hot-middleware": "^2.21.0" + "webpack": "^3.10.0" }, "devDependencies": { "nodemon": "^1.14.12", diff --git a/app/vue/src/server/build.js b/app/vue/src/server/build.js index 189e227dff3e..804f258a0ba4 100755 --- a/app/vue/src/server/build.js +++ b/app/vue/src/server/build.js @@ -1,95 +1,12 @@ -import '@storybook/core/env'; - -import webpack from 'webpack'; -import program from 'commander'; +import { buildStatic } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; import packageJson from '../../package.json'; import getBaseConfig from './config/webpack.config.prod'; import loadConfig from './config'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'production'; - -// avoid ESLint errors -const logger = console; - -program - .version(packageJson.version) - .option('-s, --static-dir ', 'Directory where to load static files from', parseList) - .option('-o, --output-dir [dir-name]', 'Directory where to store built files') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option('-w, --watch', 'Enable watch mode') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}\n`)); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - staticDir: 'SBCONFIG_STATIC_DIR', - outputDir: 'SBCONFIG_OUTPUT_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', +buildStatic({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -const configDir = program.configDir || './.storybook'; -const outputDir = program.outputDir || './storybook-static'; - -// create output directory if not exists -shelljs.mkdir('-p', path.resolve(outputDir)); -// clear the static dir -shelljs.rm('-rf', path.resolve(outputDir, 'static')); -shelljs.cp(path.resolve(__dirname, 'public/favicon.ico'), outputDir); - -// 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 = loadConfig('PRODUCTION', getBaseConfig(configDir), configDir); -config.output.path = path.resolve(outputDir); - -// copy all static files -if (program.staticDir) { - program.staticDir.forEach(dir => { - if (!fs.existsSync(dir)) { - logger.error(`Error: no such directory to load static files: ${dir}`); - process.exit(-1); - } - logger.log(`=> Copying static files from: ${dir}`); - shelljs.cp('-r', `${dir}/*`, outputDir); - }); -} - -// compile all resources with webpack and write them to the disk. -logger.info('Building storybook ...'); -const webpackCb = (err, stats) => { - if (err || stats.hasErrors()) { - logger.error('Failed to build the storybook'); - // eslint-disable-next-line no-unused-expressions - err && logger.error(err.message); - // eslint-disable-next-line no-unused-expressions - stats && stats.hasErrors() && stats.toJson().errors.forEach(e => logger.error(e)); - process.exitCode = 1; - } - logger.info('Building storybook completed.'); -}; -const compiler = webpack(config); -if (program.watch) { - compiler.watch({}, webpackCb); -} else { - compiler.run(webpackCb); -} diff --git a/app/vue/src/server/config/WatchMissingNodeModulesPlugin.js b/app/vue/src/server/config/WatchMissingNodeModulesPlugin.js deleted file mode 100644 index 962bbb97ff45..000000000000 --- a/app/vue/src/server/config/WatchMissingNodeModulesPlugin.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -// @remove-on-eject-end - -// This Webpack plugin ensures `npm install ` forces a project rebuild. -// We’re not sure why this isn't Webpack's default behavior. -// See https://github.com/facebookincubator/create-react-app/issues/186. - -function WatchMissingNodeModulesPlugin(nodeModulesPath) { - this.nodeModulesPath = nodeModulesPath; -} - -WatchMissingNodeModulesPlugin.prototype.apply = function apply(compiler) { - compiler.plugin('emit', (compilation, callback) => { - const missingDeps = compilation.missingDependencies; - const { nodeModulesPath } = this; - - // If any missing files are expected to appear in node_modules... - if (missingDeps.some(file => file.indexOf(nodeModulesPath) !== -1)) { - // ...tell webpack to watch node_modules recursively until they appear. - compilation.contextDependencies.push(nodeModulesPath); - } - - callback(); - }); -}; - -module.exports = WatchMissingNodeModulesPlugin; diff --git a/app/vue/src/server/config/webpack.config.js b/app/vue/src/server/config/webpack.config.js index e03d83f452c1..88c1a9b9c3d8 100644 --- a/app/vue/src/server/config/webpack.config.js +++ b/app/vue/src/server/config/webpack.config.js @@ -4,9 +4,9 @@ import Dotenv from 'dotenv-webpack'; import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; +import { WatchMissingNodeModulesPlugin } from '@storybook/core/server'; import { managerPath } from '@storybook/core/client'; -import WatchMissingNodeModulesPlugin from './WatchMissingNodeModulesPlugin'; import { includePaths, excludePaths, nodeModulesPaths, loadEnv, nodePaths } from './utils'; import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils'; import babelLoaderConfig from './babel'; diff --git a/app/vue/src/server/index.js b/app/vue/src/server/index.js index 123aba1e8b1e..69df1fe59e54 100755 --- a/app/vue/src/server/index.js +++ b/app/vue/src/server/index.js @@ -1,169 +1,12 @@ -import '@storybook/core/env'; - -import express from 'express'; -import https from 'https'; -import favicon from 'serve-favicon'; -import program from 'commander'; +import { buildDev } from '@storybook/core/server'; import path from 'path'; -import fs from 'fs'; -import chalk from 'chalk'; -import shelljs from 'shelljs'; -import storybook, { webpackValid } from './middleware'; import packageJson from '../../package.json'; -import { parseList, getEnvConfig } from './utils'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - -const logger = console; - -program - .version(packageJson.version) - .option('-p, --port [number]', 'Port to run Storybook (Required)', str => parseInt(str, 10)) - .option('-h, --host [string]', 'Host to run Storybook') - .option('-s, --static-dir ', 'Directory where to load static files from') - .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') - .option( - '--https', - 'Serve Storybook over HTTPS. Note: You must provide your own certificate information.' - ) - .option( - '--ssl-ca ', - 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', - parseList - ) - .option('--ssl-cert ', 'Provide an SSL certificate. (Required with --https)') - .option('--ssl-key ', 'Provide an SSL key. (Required with --https)') - .option('--smoke-test', 'Exit after successful start') - .option('-d, --db-path [db-file]', 'DEPRECATED!') - .option('--enable-db', 'DEPRECATED!') - .parse(process.argv); - -logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}`) + chalk.reset('\n')); - -if (program.enableDb || program.dbPath) { - logger.error( - [ - 'Error: the experimental local database addon is no longer bundled with', - 'react-storybook. Please remove these flags (-d,--db-path,--enable-db)', - 'from the command or npm script and try again.', - ].join(' ') - ); - process.exit(1); -} - -// The key is the field created in `program` variable for -// each command line argument. Value is the env variable. -getEnvConfig(program, { - port: 'SBCONFIG_PORT', - host: 'SBCONFIG_HOSTNAME', - staticDir: 'SBCONFIG_STATIC_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', +import getBaseConfig from './config/webpack.config'; +import loadConfig from './config'; + +buildDev({ + packageJson, + getBaseConfig, + loadConfig, + defaultFavIcon: path.resolve(__dirname, 'public/favicon.ico'), }); - -if (!program.port) { - logger.error('Error: port to run Storybook is required!\n'); - program.help(); - process.exit(-1); -} - -// Used with `app.listen` below -const listenAddr = [program.port]; - -if (program.host) { - listenAddr.push(program.host); -} - -const app = express(); -let server = app; - -if (program.https) { - if (!program.sslCert) { - logger.error('Error: --ssl-cert is required with --https'); - process.exit(-1); - } - if (!program.sslKey) { - logger.error('Error: --ssl-key is required with --https'); - process.exit(-1); - } - - const sslOptions = { - ca: (program.sslCa || []).map(ca => fs.readFileSync(ca, 'utf-8')), - cert: fs.readFileSync(program.sslCert, 'utf-8'), - key: fs.readFileSync(program.sslKey, 'utf-8'), - }; - - server = https.createServer(sslOptions, app); -} - -let hasCustomFavicon = false; - -if (program.staticDir) { - program.staticDir = parseList(program.staticDir); - program.staticDir.forEach(dir => { - const staticPath = path.resolve(dir); - if (!fs.existsSync(staticPath)) { - logger.error(`Error: no such directory to load static files: ${staticPath}`); - process.exit(-1); - } - logger.log(`=> Loading static files from: ${staticPath} .`); - app.use(express.static(staticPath, { index: false })); - - const faviconPath = path.resolve(staticPath, 'favicon.ico'); - if (fs.existsSync(faviconPath)) { - hasCustomFavicon = true; - app.use(favicon(faviconPath)); - } - }); -} - -if (!hasCustomFavicon) { - app.use(favicon(path.resolve(__dirname, 'public/favicon.ico'))); -} - -// Build the webpack configuration using the `baseConfig` -// custom `.babelrc` file and `webpack.config.js` files -const configDir = program.configDir || './.storybook'; - -// The repository info is sent to the storybook while running on -// development mode so it'll be easier for tools to integrate. -const exec = cmd => shelljs.exec(cmd, { silent: true }).stdout.trim(); -process.env.STORYBOOK_GIT_ORIGIN = - process.env.STORYBOOK_GIT_ORIGIN || exec('git remote get-url origin'); -process.env.STORYBOOK_GIT_BRANCH = - process.env.STORYBOOK_GIT_BRANCH || exec('git symbolic-ref HEAD --short'); - -// NOTE changes to env should be done before calling `getBaseConfig` -// `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) { - serverReject(error); - } else { - serverResolve(); - } -}); - -Promise.all([webpackValid, serverListening]) - .then(() => { - const proto = program.https ? 'https' : 'http'; - const address = `${proto}://${program.host || 'localhost'}:${program.port}/`; - logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); - if (program.smokeTest) { - process.exit(0); - } - }) - .catch(error => { - if (error instanceof Error) { - logger.error(error); - } - if (program.smokeTest) { - process.exit(1); - } - }); diff --git a/app/vue/src/server/middleware.js b/app/vue/src/server/middleware.js deleted file mode 100644 index 3921bf4733f9..000000000000 --- a/app/vue/src/server/middleware.js +++ /dev/null @@ -1,64 +0,0 @@ -import { Router } from 'express'; -import webpack from 'webpack'; -import path from 'path'; -import webpackDevMiddleware from 'webpack-dev-middleware'; -import webpackHotMiddleware from 'webpack-hot-middleware'; -import getBaseConfig from './config/webpack.config'; -import loadConfig from './config'; -import { 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 - const config = loadConfig('DEVELOPMENT', getBaseConfig(configDir), configDir); - 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/app/vue/src/server/utils.js b/app/vue/src/server/utils.js index 71396437a03d..913f4c3219ea 100644 --- a/app/vue/src/server/utils.js +++ b/app/vue/src/server/utils.js @@ -5,10 +5,6 @@ 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 getPreviewHeadHtml(configDirPath) { const headHtmlPath = path.resolve(configDirPath, 'preview-head.html'); const fallbackHtmlPath = path.resolve(configDirPath, 'head.html'); @@ -32,25 +28,3 @@ export function getManagerHeadHtml(configDirPath) { return scriptHtml; } - -export function getEnvConfig(program, configEnv) { - Object.keys(configEnv).forEach(fieldName => { - const envVarName = configEnv[fieldName]; - const envVarValue = process.env[envVarName]; - if (envVarValue) { - program[fieldName] = envVarValue; // eslint-disable-line - } - }); -} - -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/package.json b/lib/core/package.json index 9594f2d47fe5..cfe2b6107967 100644 --- a/lib/core/package.json +++ b/lib/core/package.json @@ -20,12 +20,16 @@ "@storybook/addons": "^3.4.0-alpha.7", "@storybook/channel-postmessage": "^3.4.0-alpha.7", "@storybook/client-logger": "^3.4.0-alpha.7", + "@storybook/node-logger": "^3.4.0-alpha.7", "@storybook/ui": "^3.4.0-alpha.7", "autoprefixer": "^7.2.5", "babel-runtime": "^6.26.0", + "chalk": "^2.3.0", + "commander": "^2.14.0", "css-loader": "^0.28.9", "dotenv": "^4.0.0", "events": "^1.1.1", + "express": "^4.16.2", "file-loader": "^1.1.6", "global": "^4.3.2", "json-loader": "^0.5.7", @@ -35,8 +39,13 @@ "qs": "^6.5.1", "react": "^16.0.0", "react-dom": "^16.0.0", + "serve-favicon": "^2.4.5", + "shelljs": "^0.8.1", "style-loader": "^0.20.1", - "url-loader": "^0.6.2" + "url-loader": "^0.6.2", + "webpack": "^3.10.0", + "webpack-dev-middleware": "^1.12.2", + "webpack-hot-middleware": "^2.21.0" }, "devDependencies": { "babel-cli": "^6.26.0" diff --git a/lib/core/server.js b/lib/core/server.js index d154aae8392c..2d7e291e8a23 100644 --- a/lib/core/server.js +++ b/lib/core/server.js @@ -1,4 +1,9 @@ const assign = require('babel-runtime/core-js/object/assign').default; const defaultWebpackConfig = require('./dist/server/config/defaults/webpack.config'); +const WatchMissingNodeModulesPlugin = require('./dist/server/config/WatchMissingNodeModulesPlugin'); +const buildStatic = require('./dist/server/build-static'); +const buildDev = require('./dist/server/build-dev'); -module.exports = assign({}, defaultWebpackConfig); +module.exports = assign({}, defaultWebpackConfig, buildStatic, buildDev, { + WatchMissingNodeModulesPlugin, +}); diff --git a/lib/core/src/server/build-dev.js b/lib/core/src/server/build-dev.js new file mode 100644 index 000000000000..0516dc431462 --- /dev/null +++ b/lib/core/src/server/build-dev.js @@ -0,0 +1,168 @@ +import express from 'express'; +import https from 'https'; +import favicon from 'serve-favicon'; +import program from 'commander'; +import path from 'path'; +import fs from 'fs'; +import chalk from 'chalk'; +import shelljs from 'shelljs'; +import { logger } from '@storybook/node-logger'; +import storybook, { webpackValid } from './middleware'; +import { parseList, getEnvConfig } from './utils'; +import './config/env'; + +export function buildDev({ packageJson, getBaseConfig, loadConfig, defaultFavIcon }) { + process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + + program + .version(packageJson.version) + .option('-p, --port [number]', 'Port to run Storybook (Required)', str => parseInt(str, 10)) + .option('-h, --host [string]', 'Host to run Storybook') + .option('-s, --static-dir ', 'Directory where to load static files from') + .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') + .option( + '--https', + 'Serve Storybook over HTTPS. Note: You must provide your own certificate information.' + ) + .option( + '--ssl-ca ', + 'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)', + parseList + ) + .option('--ssl-cert ', 'Provide an SSL certificate. (Required with --https)') + .option('--ssl-key ', 'Provide an SSL key. (Required with --https)') + .option('--smoke-test', 'Exit after successful start') + .option('-d, --db-path [db-file]', 'DEPRECATED!') + .option('--enable-db', 'DEPRECATED!') + .parse(process.argv); + + logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}`) + chalk.reset('\n')); + + if (program.enableDb || program.dbPath) { + logger.error( + [ + 'Error: the experimental local database addon is no longer bundled with', + 'storybook. Please remove these flags (-d,--db-path,--enable-db)', + 'from the command or npm script and try again.', + ].join(' ') + ); + process.exit(1); + } + + // The key is the field created in `program` variable for + // each command line argument. Value is the env variable. + getEnvConfig(program, { + port: 'SBCONFIG_PORT', + host: 'SBCONFIG_HOSTNAME', + staticDir: 'SBCONFIG_STATIC_DIR', + configDir: 'SBCONFIG_CONFIG_DIR', + }); + + if (!program.port) { + logger.error('Error: port to run Storybook is required!\n'); + program.help(); + process.exit(-1); + } + + // Used with `app.listen` below + const listenAddr = [program.port]; + + if (program.host) { + listenAddr.push(program.host); + } + + const app = express(); + let server = app; + + if (program.https) { + if (!program.sslCert) { + logger.error('Error: --ssl-cert is required with --https'); + process.exit(-1); + } + if (!program.sslKey) { + logger.error('Error: --ssl-key is required with --https'); + process.exit(-1); + } + + const sslOptions = { + ca: (program.sslCa || []).map(ca => fs.readFileSync(ca, 'utf-8')), + cert: fs.readFileSync(program.sslCert, 'utf-8'), + key: fs.readFileSync(program.sslKey, 'utf-8'), + }; + + server = https.createServer(sslOptions, app); + } + + let hasCustomFavicon = false; + + if (program.staticDir) { + program.staticDir = parseList(program.staticDir); + program.staticDir.forEach(dir => { + const staticPath = path.resolve(dir); + if (!fs.existsSync(staticPath)) { + logger.error(`Error: no such directory to load static files: ${staticPath}`); + process.exit(-1); + } + logger.info(`=> Loading static files from: ${staticPath} .`); + app.use(express.static(staticPath, { index: false })); + + const faviconPath = path.resolve(staticPath, 'favicon.ico'); + if (fs.existsSync(faviconPath)) { + hasCustomFavicon = true; + app.use(favicon(faviconPath)); + } + }); + } + + if (!hasCustomFavicon) { + app.use(favicon(defaultFavIcon)); + } + + // Build the webpack configuration using the `baseConfig` + // custom `.babelrc` file and `webpack.config.js` files + const configDir = program.configDir || './.storybook'; + + // The repository info is sent to the storybook while running on + // development mode so it'll be easier for tools to integrate. + const exec = cmd => shelljs.exec(cmd, { silent: true }).stdout.trim(); + process.env.STORYBOOK_GIT_ORIGIN = + process.env.STORYBOOK_GIT_ORIGIN || exec('git remote get-url origin'); + process.env.STORYBOOK_GIT_BRANCH = + process.env.STORYBOOK_GIT_BRANCH || exec('git symbolic-ref HEAD --short'); + + // NOTE changes to env should be done before calling `getBaseConfig` + // `getBaseConfig` function which is called inside the middleware + app.use(storybook(configDir, loadConfig, getBaseConfig)); + + let serverResolve = () => {}; + let serverReject = () => {}; + const serverListening = new Promise((resolve, reject) => { + serverResolve = resolve; + serverReject = reject; + }); + server.listen(...listenAddr, error => { + if (error) { + serverReject(error); + } else { + serverResolve(); + } + }); + + Promise.all([webpackValid, serverListening]) + .then(() => { + const proto = program.https ? 'https' : 'http'; + const address = `${proto}://${program.host || 'localhost'}:${program.port}/`; + logger.info(`Storybook started on => ${chalk.cyan(address)}\n`); + if (program.smokeTest) { + process.exit(0); + } + }) + .catch(error => { + if (error instanceof Error) { + logger.error(error); + } + if (program.smokeTest) { + process.exit(1); + } + }); +} diff --git a/lib/core/src/server/build-static.js b/lib/core/src/server/build-static.js new file mode 100644 index 000000000000..574480ada37e --- /dev/null +++ b/lib/core/src/server/build-static.js @@ -0,0 +1,91 @@ +import webpack from 'webpack'; +import program from 'commander'; +import path from 'path'; +import fs from 'fs'; +import chalk from 'chalk'; +import shelljs from 'shelljs'; +import { logger } from '@storybook/node-logger'; +import { parseList, getEnvConfig } from './utils'; +import './config/env'; + +export function buildStatic({ packageJson, getBaseConfig, loadConfig, defaultFavIcon }) { + process.env.NODE_ENV = process.env.NODE_ENV || 'production'; + + program + .version(packageJson.version) + .option('-s, --static-dir ', 'Directory where to load static files from', parseList) + .option('-o, --output-dir [dir-name]', 'Directory where to store built files') + .option('-c, --config-dir [dir-name]', 'Directory where to load Storybook configurations from') + .option('-w, --watch', 'Enable watch mode') + .option('-d, --db-path [db-file]', 'DEPRECATED!') + .option('--enable-db', 'DEPRECATED!') + .parse(process.argv); + + logger.info(chalk.bold(`${packageJson.name} v${packageJson.version}\n`)); + + if (program.enableDb || program.dbPath) { + logger.error( + [ + 'Error: the experimental local database addon is no longer bundled with', + 'storybook. Please remove these flags (-d,--db-path,--enable-db)', + 'from the command or npm script and try again.', + ].join(' ') + ); + process.exit(1); + } + + // The key is the field created in `program` variable for + // each command line argument. Value is the env variable. + getEnvConfig(program, { + staticDir: 'SBCONFIG_STATIC_DIR', + outputDir: 'SBCONFIG_OUTPUT_DIR', + configDir: 'SBCONFIG_CONFIG_DIR', + }); + + const configDir = program.configDir || './.storybook'; + const outputDir = program.outputDir || './storybook-static'; + + // create output directory if not exists + shelljs.mkdir('-p', path.resolve(outputDir)); + // clear the static dir + shelljs.rm('-rf', path.resolve(outputDir, 'static')); + shelljs.cp(defaultFavIcon, outputDir); + + // 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 = loadConfig('PRODUCTION', getBaseConfig(configDir), configDir); + config.output.path = path.resolve(outputDir); + + // copy all static files + if (program.staticDir) { + program.staticDir.forEach(dir => { + if (!fs.existsSync(dir)) { + logger.error(`Error: no such directory to load static files: ${dir}`); + process.exit(-1); + } + logger.info(`=> Copying static files from: ${dir}`); + shelljs.cp('-r', `${dir}/*`, outputDir); + }); + } + + // compile all resources with webpack and write them to the disk. + logger.info('Building storybook ...'); + const webpackCb = (err, stats) => { + if (err || stats.hasErrors()) { + logger.error('Failed to build the storybook'); + // eslint-disable-next-line no-unused-expressions + err && logger.error(err.message); + // eslint-disable-next-line no-unused-expressions + stats && stats.hasErrors() && stats.toJson().errors.forEach(e => logger.error(e)); + process.exitCode = 1; + } + logger.info('Building storybook completed.'); + }; + const compiler = webpack(config); + if (program.watch) { + compiler.watch({}, webpackCb); + } else { + compiler.run(webpackCb); + } +} diff --git a/app/angular/src/server/config/WatchMissingNodeModulesPlugin.js b/lib/core/src/server/config/WatchMissingNodeModulesPlugin.js similarity index 100% rename from app/angular/src/server/config/WatchMissingNodeModulesPlugin.js rename to lib/core/src/server/config/WatchMissingNodeModulesPlugin.js diff --git a/app/angular/src/server/middleware.js b/lib/core/src/server/middleware.js similarity index 93% rename from app/angular/src/server/middleware.js rename to lib/core/src/server/middleware.js index 28e8554bccf5..ad9e1862d07a 100644 --- a/app/angular/src/server/middleware.js +++ b/lib/core/src/server/middleware.js @@ -3,18 +3,17 @@ import { Router } from 'express'; import webpack from 'webpack'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; -import getBaseConfig from './config/webpack.config'; -import loadConfig from './config'; import { getMiddleware } from './utils'; let webpackResolve = () => {}; let webpackReject = () => {}; + export const webpackValid = new Promise((resolve, reject) => { webpackResolve = resolve; webpackReject = reject; }); -export default function(configDir) { +export default function(configDir, loadConfig, getBaseConfig) { // Build the webpack configuration using the `getBaseConfig` // custom `.babelrc` file and `webpack.config.js` files const config = loadConfig('DEVELOPMENT', getBaseConfig(configDir), configDir); diff --git a/lib/core/src/server/utils.js b/lib/core/src/server/utils.js new file mode 100644 index 000000000000..7e501fd37d55 --- /dev/null +++ b/lib/core/src/server/utils.js @@ -0,0 +1,28 @@ +import path from 'path'; +import fs from 'fs'; + +export function parseList(str) { + return str.split(','); +} + +export function getEnvConfig(program, configEnv) { + Object.keys(configEnv).forEach(fieldName => { + const envVarName = configEnv[fieldName]; + const envVarValue = process.env[envVarName]; + if (envVarValue) { + program[fieldName] = envVarValue; // eslint-disable-line + } + }); +} + +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/yarn.lock b/yarn.lock index 7add7163a08f..c2ec8ae32356 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13370,7 +13370,7 @@ serialize-javascript@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005" -serve-favicon@^2.4.3, serve-favicon@^2.4.5: +serve-favicon@^2.4.5: version "2.4.5" resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.4.5.tgz#49d9a46863153a9240691c893d2b0e7d85d6d436" dependencies: @@ -15513,7 +15513,7 @@ webpack-core@^0.6.8: source-list-map "~0.1.7" source-map "~0.4.1" -webpack-dev-middleware@1.12.2, webpack-dev-middleware@^1.10.2, webpack-dev-middleware@^1.11.0, webpack-dev-middleware@^1.12.0, webpack-dev-middleware@^1.12.2, webpack-dev-middleware@~1.12.0: +webpack-dev-middleware@1.12.2, webpack-dev-middleware@^1.11.0, webpack-dev-middleware@^1.12.2, webpack-dev-middleware@~1.12.0: version "1.12.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" dependencies: @@ -15619,7 +15619,7 @@ webpack-dev-server@~2.11.0: webpack-dev-middleware "1.12.2" yargs "6.6.0" -webpack-hot-middleware@^2.18.0, webpack-hot-middleware@^2.20.0, webpack-hot-middleware@^2.21.0: +webpack-hot-middleware@^2.21.0: version "2.21.0" resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.21.0.tgz#7b3c113a7a4b301c91e0749573c7aab28b414b52" dependencies: