import fs from 'fs-extra'; import path from 'path'; import webpack from 'webpack'; import shelljs from 'shelljs'; import { logger } from '@storybook/node-logger'; import { getProdCli } from './cli'; import loadConfig from './config'; import loadManagerConfig from './manager/manager-config'; import { logConfig } from './logConfig'; async function compileManager(managerConfig, managerStartTime) { logger.info('=> Compiling manager..'); return new Promise((resolve, reject) => { webpack(managerConfig).run((error, stats) => { if (error || !stats || stats.hasErrors()) { logger.error('=> Failed to build the manager'); if (error) { logger.error(error.message); } if (stats && (stats.hasErrors() || stats.hasWarnings())) { const { warnings, errors } = stats.toJson(managerConfig.stats); errors.forEach(e => logger.error(e)); warnings.forEach(e => logger.error(e)); } process.exitCode = 1; reject(error || stats); return; } logger.trace({ message: '=> manager built', time: process.hrtime(managerStartTime) }); stats.toJson(managerConfig.stats).warnings.forEach(e => logger.warn(e)); resolve(stats); }); }); } async function watchPreview(previewConfig) { logger.info('=> Compiling preview in watch mode..'); return new Promise(() => { webpack(previewConfig).watch( { aggregateTimeout: 1, }, (error, stats) => { if (!error) { const statsConfig = previewConfig.stats ? previewConfig.stats : { colors: true }; // eslint-disable-next-line no-console console.log(stats.toString(statsConfig)); } else { logger.error(error.message); } } ); }); } async function compilePreview(previewConfig, previewStartTime) { logger.info('=> Compiling preview..'); return new Promise((resolve, reject) => { webpack(previewConfig).run((error, stats) => { if (error || !stats || stats.hasErrors()) { logger.error('=> Failed to build the preview'); process.exitCode = 1; if (error) { logger.error(error.message); return reject(error); } if (stats && (stats.hasErrors() || stats.hasWarnings())) { const { warnings, errors } = stats.toJson(previewConfig.stats); errors.forEach(e => logger.error(e)); warnings.forEach(e => logger.error(e)); return reject(stats); } } logger.trace({ message: '=> Preview built', time: process.hrtime(previewStartTime) }); stats.toJson(previewConfig.stats).warnings.forEach(e => logger.warn(e)); return resolve(stats); }); }); } async function copyAllStaticFiles(staticDir, outputDir) { if (staticDir && staticDir.length) { await Promise.all( staticDir.map(async dir => { const staticPath = path.resolve(dir); if (await !fs.exists(staticPath)) { logger.error(`Error: no such directory to load static files: ${staticPath}`); process.exit(-1); } shelljs.cp('-r', `${dir}/!(index.html)`, outputDir); }) ); logger.info(`=> Copying static files from: ${staticDir.join(', ')}`); } } async function buildManager(configType, outputDir, configDir, options) { logger.info('=> Building manager..'); const managerStartTime = process.hrtime(); logger.info('=> Loading manager config..'); const managerConfig = await loadManagerConfig({ configType, outputDir, configDir, corePresets: [require.resolve('./manager/manager-preset.js')], frameworkPresets: options.frameworkPresets, docsMode: options.docsMode, }); if (options.debugWebpack) { logConfig('Manager webpack config', managerConfig); } return compileManager(managerConfig, managerStartTime); } async function buildPreview(configType, outputDir, packageJson, options) { const { watch, debugWebpack } = options; logger.info('=> Building preview..'); const previewStartTime = process.hrtime(); logger.info('=> Loading preview config..'); const previewConfig = await loadConfig({ configType, outputDir, packageJson, corePresets: [require.resolve('./preview/preview-preset.js')], overridePresets: [require.resolve('./preview/custom-webpack-preset.js')], ...options, }); if (debugWebpack) { logConfig('Preview webpack config', previewConfig); } if (watch) { return watchPreview(previewConfig); } return compilePreview(previewConfig, previewStartTime); } function prepareFilesStructure(outputDir, defaultFavIcon) { // clear the output dir logger.info('clean outputDir..'); shelljs.rm('-rf', outputDir); // create output directory if not exists shelljs.mkdir('-p', outputDir); shelljs.mkdir('-p', path.join(outputDir, 'sb_dll')); shelljs.cp(defaultFavIcon, outputDir); } export async function buildStaticStandalone(options) { const { staticDir, configDir, packageJson } = options; const configType = 'PRODUCTION'; const outputDir = path.isAbsolute(options.outputDir) ? options.outputDir : path.join(process.cwd(), options.outputDir); const dllPath = path.join(__dirname, '../../dll/*'); const defaultFavIcon = require.resolve('./public/favicon.ico'); prepareFilesStructure(outputDir, defaultFavIcon); await copyAllStaticFiles(staticDir, outputDir); logger.info(`=> Copying prebuild dll's..`); shelljs.cp('-r', dllPath, path.join(outputDir, 'sb_dll')); await buildManager(configType, outputDir, configDir, options); await buildPreview(configType, outputDir, packageJson, options); logger.info(`=> Output directory: ${outputDir}`); } export function buildStatic({ packageJson, ...loadOptions }) { const cliOptions = getProdCli(packageJson); return buildStaticStandalone({ ...cliOptions, ...loadOptions, packageJson, configDir: loadOptions.configDir || cliOptions.configDir || './.storybook', outputDir: loadOptions.outputDir || cliOptions.outputDir || './storybook-static', docsMode: !!cliOptions.docs, }); }