UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

300 lines (240 loc) 15.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = compile; var _fs = require("fs"); var _path = _interopRequireDefault(require("path")); var _webpack = _interopRequireDefault(require("webpack")); var _webpackNodeExternals = _interopRequireDefault(require("webpack-node-externals")); var _utils = require("../../utils"); var _helpers = require("../../storybook/helpers"); var _types = require("../../../types"); var _messages = require("../../messages"); var _logger = require("../../logger"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } let isInitiated = false; let dumpStats = _types.noop; function handleWebpackBuild(error, stats) { dumpStats(stats); if (error || !stats || stats.hasErrors()) { (0, _messages.emitWebpackMessage)({ type: isInitiated ? 'rebuild failed' : 'fail' }); console.error('=> Failed to build the Storybook preview bundle'); if (error) return console.error(error.message); if (stats && (stats.hasErrors() || stats.hasWarnings())) { const { warnings, errors } = stats.toJson(); errors.forEach(e => console.error(e)); warnings.forEach(e => console.error(e)); return; } } stats.toJson().warnings.forEach(e => console.warn(e)); if (!isInitiated) { isInitiated = true; (0, _messages.emitWebpackMessage)({ type: 'success' }); } else { (0, _messages.emitWebpackMessage)({ type: 'rebuild succeeded' }); } return; } async function applyMdxLoader(config, areAddonsRemoved, loader) { const { mdxLoaders } = await Promise.resolve().then(() => _interopRequireWildcard(require('./mdx-loader'))); mdxLoaders.splice(1, 0, loader); const mdxRegexps = (0, _helpers.isStorybookVersionLessThan)(6, 2) ? [/\.(stories|story).mdx$/, /\.(stories|story)\.mdx$/] : [/(stories|story)\.mdx$/]; // NOTE replace md/mdx to null loader const mdRegexps = [/\.md$/, /\.mdx$/]; if (areAddonsRemoved) { var _config$module2; mdRegexps.forEach(test => { var _config$module; return (_config$module = config.module) === null || _config$module === void 0 ? void 0 : _config$module.rules.push({ test, exclude: /(stories|story)\.mdx$/, use: require.resolve('null-loader') }); }); (_config$module2 = config.module) === null || _config$module2 === void 0 ? void 0 : _config$module2.rules.push({ test: /(stories|story)\.mdx$/, use: mdxLoaders }); } else { var _config$module3, _config$module4, _config$module$rules$, _config$module5; // NOTE Exclude addons' entry points config.entry = Array.isArray(config.entry) ? config.entry.filter(entry => !/@storybook(\/|\\)addon/.test(entry)) : config.entry; (_config$module3 = config.module) === null || _config$module3 === void 0 ? void 0 : _config$module3.rules.filter(rule => mdRegexps.some(test => { var _rule$test; return ((_rule$test = rule.test) === null || _rule$test === void 0 ? void 0 : _rule$test.toString()) == test.toString(); })).forEach(rule => rule.use = require.resolve('null-loader')); (_config$module4 = config.module) === null || _config$module4 === void 0 ? void 0 : _config$module4.rules.filter(rule => mdxRegexps.some(test => { var _rule$test2; return ((_rule$test2 = rule.test) === null || _rule$test2 === void 0 ? void 0 : _rule$test2.toString()) == test.toString(); })).forEach(rule => rule.use = mdxLoaders); // NOTE Exclude source-loader config.module = { ...config.module, rules: (_config$module$rules$ = (_config$module5 = config.module) === null || _config$module5 === void 0 ? void 0 : _config$module5.rules.filter(rule => !(typeof rule.loader == 'string' && /@storybook(\/|\\)source-loader/.test(rule.loader)))) !== null && _config$module$rules$ !== void 0 ? _config$module$rules$ : [] }; } } async function getWebpackConfigForStorybook_pre6_2(framework, configDir, outputDir) { const { default: storybookFrameworkOptions } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)(`@storybook/${framework}/dist/server/options`)}`).then(s => _interopRequireWildcard(require(s))); // eslint-disable-next-line node/no-missing-import, @typescript-eslint/no-unsafe-assignment, import/no-unresolved const { default: getConfig } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)('@storybook/core/dist/server/config')}`).then(s => _interopRequireWildcard(require(s))); // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call return getConfig({ // NOTE: 6.1 storybook don't support quite any more. But we still have older versions quiet: true, configType: 'PRODUCTION', outputDir, cache: {}, // eslint-disable-next-line node/no-missing-require corePresets: [(0, _helpers.resolveFromStorybook)('@storybook/core/dist/server/preview/preview-preset')], overridePresets: [...((0, _helpers.hasDocsAddon)() ? [require.resolve('./mdx-loader')] : []), // eslint-disable-next-line node/no-missing-require (0, _helpers.resolveFromStorybook)('@storybook/core/dist/server/preview/custom-webpack-preset')], ...storybookFrameworkOptions, configDir }); } async function getWebpackConfigForStorybook_6_2(framework, configDir, outputDir) { const { default: storybookFrameworkOptions } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)(`@storybook/${framework}/dist/cjs/server/options`)}`).then(s => _interopRequireWildcard(require(s))); const options = { quiet: true, configType: 'PRODUCTION', outputDir, configDir, ...storybookFrameworkOptions }; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { getPreviewBuilder } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)('@storybook/core-server/dist/cjs/utils/get-preview-builder')}`).then(s => _interopRequireWildcard(require(s))); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { loadAllPresets } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)('@storybook/core-common')}`).then(s => _interopRequireWildcard(require(s))); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call const builder = await getPreviewBuilder(configDir); // NOTE: Copy-paste from storybook/lib/core-server/src/build-dev.ts // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call const presets = loadAllPresets({ corePresets: [// eslint-disable-next-line node/no-missing-require (0, _helpers.resolveFromStorybook)('@storybook/core-server/dist/cjs/presets/common-preset'), // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access ...builder.corePresets, // eslint-disable-next-line node/no-missing-require (0, _helpers.resolveFromStorybook)('@storybook/core-server/dist/cjs/presets/babel-cache-preset')], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access overridePresets: [...((0, _helpers.hasDocsAddon)() ? [require.resolve('./mdx-loader')] : []), // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access ...builder.overridePresets], ...options }); // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment return builder.getConfig({ ...options, presets }); } async function removeAddons(configDir) { const storybookUtilsPath = (0, _helpers.isStorybookVersionLessThan)(6, 2) ? '@storybook/core/dist/server/utils' : '@storybook/core-common/dist/cjs/utils'; const serverRequireModule = (0, _helpers.isStorybookVersionLessThan)(6, 2) ? 'server-require' : 'interpret-require'; try { var _config$core; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { getInterpretedFile } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)(`${storybookUtilsPath}/interpret-files`)}`).then(s => _interopRequireWildcard(require(s))); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { serverRequire } = await Promise.resolve(`${(0, _helpers.resolveFromStorybook)(`${storybookUtilsPath}/${serverRequireModule}`)}`).then(s => _interopRequireWildcard(require(s))); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const mainConfigFile = (0, _helpers.isStorybookVersion)(6) ? _path.default.join(configDir, 'main') : // eslint-disable-next-line @typescript-eslint/no-unsafe-call getInterpretedFile(_path.default.join(configDir, 'main')); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call const config = serverRequire(mainConfigFile); if (((_config$core = config.core) === null || _config$core === void 0 ? void 0 : _config$core.builder) == 'webpack5') { _logger.logger.warn("Be aware Creevey doesn't fully support webpack@5, some feature might not work well"); } if (config.addons && config.stories) { config.addons = []; return true; } } catch (_) { /* noop */ } return false; } async function compile(config, { debug, ui }) { var _storybookWebpackConf, _storybookWebpackConf2, _storybookWebpackConf3, _extensions$map, _storybookWebpackConf4, _storybookWebpackConf5, _storybookWebpackConf6; const storybookFramework = (0, _helpers.getStorybookFramework)(); const outputDir = _path.default.join((0, _utils.getCreeveyCache)(), 'storybook'); try { (0, _fs.rmdirSync)(outputDir, { recursive: true }); } catch (_) { /* noop */ } const creeveyLoader = { loader: require.resolve('./creevey-loader'), options: { debug, storybookDir: config.storybookDir } }; // NOTE Remove addons by monkey patching, only for new config file (main.js) const areAddonsRemoved = await removeAddons(config.storybookDir); const getWebpackConfig = (0, _helpers.isStorybookVersionLessThan)(6, 2) ? getWebpackConfigForStorybook_pre6_2 : getWebpackConfigForStorybook_6_2; const storybookWebpackConfig = await getWebpackConfig(storybookFramework, config.storybookDir, outputDir); const extensions = (_storybookWebpackConf = (_storybookWebpackConf2 = storybookWebpackConfig.resolve) === null || _storybookWebpackConf2 === void 0 ? void 0 : _storybookWebpackConf2.extensions) !== null && _storybookWebpackConf !== void 0 ? _storybookWebpackConf : _utils.extensions; delete storybookWebpackConfig.optimization; storybookWebpackConfig.devtool = false; storybookWebpackConfig.performance = false; storybookWebpackConfig.profile = debug; storybookWebpackConfig.mode = 'development'; storybookWebpackConfig.target = 'node'; storybookWebpackConfig.output = { ...storybookWebpackConfig.output, filename: 'main.js' }; // NOTE Add hack to allow stories HMR work in nodejs Array.isArray(storybookWebpackConfig.entry) && storybookWebpackConfig.entry.unshift(require.resolve('./dummy-hmr')); // NOTE apply creevey loader to output from mdx loader if ((0, _helpers.hasDocsAddon)()) await applyMdxLoader(storybookWebpackConfig, areAddonsRemoved, creeveyLoader); // NOTE Add creevey-loader to cut off all unnecessary code except stories meta and tests (_storybookWebpackConf3 = storybookWebpackConfig.module) === null || _storybookWebpackConf3 === void 0 ? void 0 : _storybookWebpackConf3.rules.unshift({ enforce: 'pre', test: new RegExp(`\\.(${(_extensions$map = extensions.map(x => x.slice(1))) === null || _extensions$map === void 0 ? void 0 : _extensions$map.join('|')})$`), exclude: /node_modules/, use: creeveyLoader }); const aliases = (_storybookWebpackConf4 = (_storybookWebpackConf5 = storybookWebpackConfig.resolve) === null || _storybookWebpackConf5 === void 0 ? void 0 : _storybookWebpackConf5.alias) !== null && _storybookWebpackConf4 !== void 0 ? _storybookWebpackConf4 : {}; const excluded = ['@storybook/addons', '@storybook/api', '@storybook/channel-postmessage', '@storybook/channels', '@storybook/client-api', '@storybook/client-logger', '@storybook/components', '@storybook/core-events', '@storybook/router', '@storybook/semver', '@storybook/theming']; // NOTE Exclude from bundle all modules from node_modules storybookWebpackConfig.externals = [...Object.entries(aliases).filter(([alias]) => excluded.includes(alias)).map(([, aliasPath]) => ({ [aliasPath]: `commonjs ${aliasPath}` })), // NOTE Replace `@storybook/${framework}` to ../../storybook.ts { [`@storybook/${storybookFramework}`]: `commonjs ${require.resolve('../../storybook/entry')}` }, (0, _webpackNodeExternals.default)({ includeAbsolutePaths: true, allowlist: /(webpack|dummy-hmr|generated-stories-entry|generated-config-entry|generated-other-entry)/ }), // TODO Don't work well with monorepos (0, _webpackNodeExternals.default)({ modulesDir: (0, _helpers.resolveFromStorybook)('@storybook/core').split('@storybook')[0], includeAbsolutePaths: true, allowlist: /(webpack|dummy-hmr|generated-stories-entry|generated-config-entry|generated-other-entry)/ })]; // NOTE Exclude some plugins const excludedPlugins = ['DocgenPlugin', 'ForkTsCheckerWebpackPlugin']; storybookWebpackConfig.plugins = (_storybookWebpackConf6 = storybookWebpackConfig.plugins) === null || _storybookWebpackConf6 === void 0 ? void 0 : _storybookWebpackConf6.filter(plugin => !excludedPlugins.includes(plugin.constructor.name)); const storybookWebpackCompiler = (0, _webpack.default)(storybookWebpackConfig); if (debug) { dumpStats = stats => (0, _fs.writeFile)(_path.default.join(config.reportDir, 'stats.json'), JSON.stringify(stats.toJson(), null, 2), _types.noop); } if (ui) { const watcher = storybookWebpackCompiler.watch({}, handleWebpackBuild); (0, _messages.subscribeOn)('shutdown', () => watcher.close(_types.noop)); } else { storybookWebpackCompiler.run(handleWebpackBuild); } }