import fs from 'fs'; import path from 'path'; import JSON5 from 'json5'; import findCacheDir from 'find-cache-dir'; import { createDefaultWebpackConfig } from '@storybook/core/server'; // avoid ESLint errors const logger = console; function removeReactHmre(presets) { const index = presets.indexOf('react-hmre'); if (index > -1) { presets.splice(index, 1); } } // Tries to load a .babelrc and returns the parsed object if successful function loadBabelConfig(babelConfigPath) { let config; if (fs.existsSync(babelConfigPath)) { const content = fs.readFileSync(babelConfigPath, 'utf-8'); try { config = JSON5.parse(content); config.babelrc = false; } catch (e) { logger.error(`=> Error parsing .babelrc file: ${e.message}`); throw e; } } if (!config) return null; // Remove react-hmre preset. // It causes issues with react-storybook. // We don't really need it. // Earlier, we fix this by runnign storybook in the production mode. // But, that hide some useful debug messages. if (config.presets) { removeReactHmre(config.presets); } if (config.env && config.env.development && config.env.development.presets) { removeReactHmre(config.env.development.presets); } return config; } // `baseConfig` is a webpack configuration bundled with storybook. // Storybook will look in the `configDir` directory // (inside working directory) if a config path is not provided. export default function(configType, baseConfig, projectDir, configDir) { const config = baseConfig; // Search for a .babelrc in project directory, config directory, and storybook // module directory. If found, use that to extend webpack configurations. const babelConfigInConfig = loadBabelConfig(path.resolve(configDir, '.babelrc')); const babelConfigInProject = loadBabelConfig(path.resolve(projectDir, '.babelrc')); const babelConfigInModule = loadBabelConfig('.babelrc'); let babelConfig = null; let babelConfigDir = ''; if (babelConfigInConfig) { logger.info('=> Loading custom .babelrc from config directory.'); babelConfig = babelConfigInConfig; babelConfigDir = configDir; } else if (babelConfigInProject) { logger.info('=> Loading custom .babelrc from project directory.'); babelConfig = babelConfigInProject; babelConfigDir = projectDir; } else { babelConfig = babelConfigInModule; } if (babelConfig) { // If the custom config uses babel's `extends` clause, then replace it with // an absolute path. `extends` will not work unless we do this. if (babelConfig.extends) { babelConfig.extends = babelConfigDir ? path.resolve(babelConfigDir, babelConfig.extends) : path.resolve(babelConfig.extends); } config.module.rules[0].query = 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. config.module.rules[0].query.cacheDirectory = findCacheDir({ name: 'react-storybook', }); // Check whether addons.js file exists inside the storybook. const storybookCustomAddonsPath = path.resolve(configDir, 'addons.js'); if (fs.existsSync(storybookCustomAddonsPath)) { logger.info('=> Loading custom addons config.'); config.entry.manager.unshift(storybookCustomAddonsPath); } const defaultConfig = createDefaultWebpackConfig(config); // Check whether user has a custom webpack config file and // return the (extended) base configuration if it's not available. const customConfigPath = path.resolve(configDir, 'webpack.config.js'); if (!fs.existsSync(customConfigPath)) { logger.info('=> Using default webpack setup based on "Create React App".'); return defaultConfig; } const customConfig = require(customConfigPath); // eslint-disable-line if (typeof customConfig === 'function') { logger.info('=> Loading custom webpack config (full-control mode).'); return customConfig(config, configType, defaultConfig, configDir); } logger.info('=> Loading custom webpack config.'); customConfig.module = customConfig.module || {}; return { ...customConfig, // We'll always load our configurations after the custom config. // So, we'll always load the stuff we need. ...config, // We need to use our and custom plugins. plugins: [...config.plugins, ...(customConfig.plugins || [])], module: { ...config.module, // We need to use our and custom rules. ...customConfig.module, rules: [...config.module.rules, ...(customConfig.module.rules || [])], }, }; }