diff --git a/.babelrc.js b/.babelrc.js index 97f74e90d298..c58ed0e9a401 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -1,9 +1,14 @@ module.exports = { - presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-flow'], + presets: [ + ['@babel/preset-env', { shippedProposals: true }], + '@babel/preset-react', + '@babel/preset-flow', + ], plugins: [ 'babel-plugin-emotion', 'babel-plugin-macros', '@babel/plugin-proposal-class-properties', + '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-export-default-from', [ '@babel/plugin-transform-runtime', @@ -20,7 +25,7 @@ module.exports = { overrides: [ { test: './examples/vue-kitchen-sink', - presets: ['@babel/preset-env', 'babel-preset-vue'], + presets: [['@babel/preset-env', { shippedProposals: true }], 'babel-preset-vue'], }, { test: [ diff --git a/.eslintignore b/.eslintignore index 73f2bb84a21b..e8aacb9b8fa0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ dist +lib/**/dll build coverage node_modules diff --git a/.eslintrc.js b/.eslintrc.js index 917bf9b00706..6de2f6ae78be 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -123,6 +123,12 @@ module.exports = { ], }, overrides: [ + { + files: ['**/__tests__/**', '**/*.test.js/**', '**/*.spec.js/**'], + rules: { + 'import/no-extraneous-dependencies': ignore, + }, + }, { files: ['**/react-native*/**', '**/REACT_NATIVE*/**', '**/crna*/**'], rules: { diff --git a/.gitignore b/.gitignore index a1bdccd96d11..0ed76d9dcdc0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ integration/__image_snapshots__/__diff_output__ .jest-test-results.json /examples/cra-kitchen-sink/src/__image_snapshots__/__diff_output__/ lib/*.jar +lib/**/dll diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt index f18d4aaf8682..f609d196a3ea 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Bootstrap.kt @@ -17,6 +17,7 @@ object OpenSourceProjects_Storybook_Bootstrap : BuildType({ addons/storyshots/*/dist/** => dist.zip/addons/storyshots app/*/dist/** => dist.zip/app lib/*/dist/** => dist.zip/lib + lib/core/dll/** => dist.zip/lib/core/dll """.trimIndent() vcs { diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt index 53ee4bd40753..2fe164bb30e9 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/OpenSourceProjects_Storybook_Examples.kt @@ -12,10 +12,10 @@ object OpenSourceProjects_Storybook_Examples : BuildType({ name = "Examples" artifactRules = """ -${StorybookApp.values().map { it.artifactPath }.joinToString("\n")} -examples/official-storybook/storybook-static => official.zip -examples/official-storybook/image-snapshots/__image_snapshots__ => image-snapshots -""".trimIndent() + ${StorybookApp.values().map { it.artifactPath }.joinToString("\n")} + examples/official-storybook/storybook-static => official.zip + examples/official-storybook/image-snapshots/__image_snapshots__ => image-snapshots + """.trimIndent() vcs { root(OpenSourceProjects_Storybook.vcsRoots.OpenSourceProjects_Storybook_HttpsGithubComStorybooksStorybookRefsHeadsMaster) diff --git a/.teamcity/OpenSourceProjects_Storybook/buildTypes/StorybookApp.kt b/.teamcity/OpenSourceProjects_Storybook/buildTypes/StorybookApp.kt index 93a9b13b25d7..3f043631ceea 100644 --- a/.teamcity/OpenSourceProjects_Storybook/buildTypes/StorybookApp.kt +++ b/.teamcity/OpenSourceProjects_Storybook/buildTypes/StorybookApp.kt @@ -82,7 +82,9 @@ enum class StorybookApp(val appName: String, val exampleDir: String, val merged: } artifacts { - artifactRules = "dist.zip!**" + artifactRules = """ + dist.zip!** + """.trimIndent() } } } diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index 27c85249afd4..c6fe0901734f 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -30,6 +30,7 @@ "@emotion/styled": "^0.10.6", "@storybook/addons": "4.1.0-alpha.9", "@storybook/core-events": "4.1.0-alpha.9", + "eventemitter3": "^3.1.0", "global": "^4.3.2", "prop-types": "^15.6.2", "util-deprecate": "^1.0.2" diff --git a/addons/backgrounds/src/__tests__/BackgroundPanel.js b/addons/backgrounds/src/__tests__/BackgroundPanel.js index 6cdee1c901bb..c1591f483926 100644 --- a/addons/backgrounds/src/__tests__/BackgroundPanel.js +++ b/addons/backgrounds/src/__tests__/BackgroundPanel.js @@ -1,6 +1,6 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; -import EventEmitter from 'events'; +import EventEmitter from 'eventemitter3'; import BackgroundPanel from '../BackgroundPanel'; import Events from '../constants'; diff --git a/addons/options/README.md b/addons/options/README.md index e678f0eb9dfd..4de59b4ac8db 100644 --- a/addons/options/README.md +++ b/addons/options/README.md @@ -137,7 +137,7 @@ To install type definitions: `npm install -D @types/storybook__addon-options` Make sure you also have the type definitions installed for the following libs: - - node - - react +- node +- react You can install them using `npm install -D @types/node @types/react`, assuming you are using Typescript >2.0. diff --git a/lib/core/package.json b/lib/core/package.json index dfe605dfa5b1..3f0be45700d0 100644 --- a/lib/core/package.json +++ b/lib/core/package.json @@ -6,9 +6,6 @@ "storybook" ], "homepage": "https://github.com/storybooks/storybook/tree/master/lib/core", - "publishConfig": { - "access": "public" - }, "bugs": { "url": "https://github.com/storybooks/storybook/issues" }, @@ -30,7 +27,6 @@ "@emotion/core": "^0.13.1", "@emotion/provider": "^0.11.2", "@emotion/styled": "^0.10.6", - "@ndelangen/html-webpack-harddisk-plugin": "^0.2.0", "@storybook/addons": "4.1.0-alpha.9", "@storybook/channel-postmessage": "4.1.0-alpha.9", "@storybook/client-logger": "4.1.0-alpha.9", @@ -53,10 +49,12 @@ "detect-port": "^1.2.3", "dotenv-webpack": "^1.5.7", "ejs": "^2.6.1", + "eventemitter3": "^3.1.0", "express": "^4.16.3", "file-loader": "^2.0.0", "file-system-cache": "^1.0.5", "find-cache-dir": "^2.0.0", + "fs-extra": "^7.0.1", "global": "^4.3.2", "html-webpack-plugin": "^4.0.0-beta.2", "inquirer": "^6.2.0", @@ -94,5 +92,8 @@ "babel-loader": "^7.0.0 || ^8.0.0", "react": ">=16.3.0", "react-dom": ">=16.3.0" + }, + "publishConfig": { + "access": "public" } } diff --git a/lib/core/src/client/preview/story_store.js b/lib/core/src/client/preview/story_store.js index cb96b7b6ca96..a2b5498105e4 100644 --- a/lib/core/src/client/preview/story_store.js +++ b/lib/core/src/client/preview/story_store.js @@ -1,5 +1,5 @@ /* eslint no-underscore-dangle: 0 */ -import { EventEmitter } from 'events'; +import EventEmitter from 'eventemitter3'; import Events from '@storybook/core-events'; let count = 0; diff --git a/lib/core/src/server/build-dev.js b/lib/core/src/server/build-dev.js index 3956fc8e0725..ea9d878f5222 100644 --- a/lib/core/src/server/build-dev.js +++ b/lib/core/src/server/build-dev.js @@ -3,7 +3,7 @@ import https from 'https'; import ip from 'ip'; import favicon from 'serve-favicon'; import path from 'path'; -import fs from 'fs'; +import fs from 'fs-extra'; import chalk from 'chalk'; import { logger, colors } from '@storybook/node-logger'; import fetch from 'node-fetch'; @@ -21,12 +21,21 @@ import { getDevCli } from './cli'; const defaultFavIcon = require.resolve('./public/favicon.ico'); +const cacheDir = findCacheDir({ name: 'storybook' }); const cache = Cache({ - basePath: findCacheDir({ name: 'storybook' }), + basePath: cacheDir, ns: 'storybook', // Optional. A grouping namespace for items. }); -function getServer(app, options) { +const writeStats = async (name, stats) => { + await fs.writeFile( + path.join(cacheDir, `${name}-stats.json`), + JSON.stringify(stats.toJson(), null, 2), + 'utf8' + ); +}; + +async function getServer(app, options) { if (!options.https) { return app; } @@ -42,37 +51,40 @@ function getServer(app, options) { } const sslOptions = { - ca: (options.sslCa || []).map(ca => fs.readFileSync(ca, 'utf-8')), - cert: fs.readFileSync(options.sslCert, 'utf-8'), - key: fs.readFileSync(options.sslKey, 'utf-8'), + ca: await Promise.all((options.sslCa || []).map(ca => fs.readFile(ca, 'utf-8'))), + cert: await fs.readFile(options.sslCert, 'utf-8'), + key: await fs.readFile(options.sslKey, 'utf-8'), }; return https.createServer(sslOptions, app); } -function applyStatic(app, options) { +async function applyStatic(app, options) { const { staticDir } = options; + let hasCustomFavicon = false; - if (staticDir) { - staticDir.forEach(dir => { - const staticPath = path.resolve(dir); + if (staticDir && staticDir.length) { + await Promise.all( + staticDir.map(async dir => { + const staticPath = path.resolve(dir); - if (!fs.existsSync(staticPath)) { - logger.error(`Error: no such directory to load static files: ${staticPath}`); - process.exit(-1); - } + if (await !fs.exists(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 })); + logger.info(`=> Loading static files from: ${staticPath} .`); + app.use(express.static(staticPath, { index: false })); - const faviconPath = path.resolve(staticPath, 'favicon.ico'); + const faviconPath = path.resolve(staticPath, 'favicon.ico'); - if (fs.existsSync(faviconPath)) { - hasCustomFavicon = true; - app.use(favicon(faviconPath)); - } - }); + if (await fs.exists(faviconPath)) { + hasCustomFavicon = true; + app.use(favicon(faviconPath)); + } + }) + ); } if (!hasCustomFavicon) { @@ -137,9 +149,9 @@ export async function buildDevStandalone(options) { } const app = express(); - const server = getServer(app, options); + const server = await getServer(app, options); - applyStatic(app, options); + await applyStatic(app, options); const storybookMiddleware = await storybook(options); @@ -148,7 +160,7 @@ export async function buildDevStandalone(options) { const serverListening = listenToServer(server, listenAddr); const [ - { iframeStats, managerStats, managerTotalTime, iframeTotalTime }, + { previewStats, managerStats, managerTotalTime, previewTotalTime }, updateInfo, ] = await Promise.all([ webpackValid, @@ -214,7 +226,7 @@ export async function buildDevStandalone(options) { ${colors.green(`Storybook ${chalk.bold(options.packageJson.version)} started`)} ${chalk.gray(stripIndents` ${chalk.underline(prettyTime(managerTotalTime))} for manager and ${chalk.underline( - prettyTime(iframeTotalTime) + prettyTime(previewTotalTime) )} for preview`)} ${serveMessage.toString()}${updateMessage ? `\n\n${updateMessage}` : ''} @@ -224,7 +236,11 @@ export async function buildDevStandalone(options) { ); if (options.smokeTest) { - process.exit(iframeStats.toJson().warnings.length ? 1 : 0); + await writeStats('preview', previewStats); + await writeStats('manager', managerStats); + logger.info(`stats written to => ${chalk.cyan(path.join(cacheDir, '[name].json'))}`); + + process.exit(previewStats.toJson().warnings.length ? 1 : 0); process.exit(managerStats.toJson().warnings.length ? 1 : 0); } else if (!options.ci) { opn(address).catch(() => { diff --git a/lib/core/src/server/build-static.js b/lib/core/src/server/build-static.js index 03a7e33bcbca..d89080b58972 100644 --- a/lib/core/src/server/build-static.js +++ b/lib/core/src/server/build-static.js @@ -1,4 +1,4 @@ -import fs from 'fs'; +import fs from 'fs-extra'; import path from 'path'; import webpack from 'webpack'; import shelljs from 'shelljs'; @@ -9,23 +9,50 @@ import { getProdCli } from './cli'; import loadConfig from './config'; import loadManagerConfig from './manager/manager-config'; -const defaultFavIcon = require.resolve('./public/favicon.ico'); - export async function buildStaticStandalone(options) { - const { staticDir, watch, configDir, packageJson } = options; + const { staticDir, configDir, packageJson, watch } = options; const configType = 'PRODUCTION'; const outputDir = path.join(process.cwd(), options.outputDir); + const dllPath = path.join(__dirname, '../../dll/*'); + const defaultFavIcon = require.resolve('./public/favicon.ico'); // create output directory if not exists shelljs.mkdir('-p', outputDir); + shelljs.mkdir('-p', path.join(outputDir, 'sb_dll')); + // clear the static dir shelljs.rm('-rf', path.join(outputDir, 'static')); shelljs.cp(defaultFavIcon, outputDir); - logger.info('building manager..'); + logger.info('clean outputDir..'); + shelljs.rm('-rf', path.join(outputDir, 'static')); + + shelljs.cp(defaultFavIcon, outputDir); + + // copy all static files + 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(', ')}`); + } + + logger.info(`=> Copying prebuild dll's..`); + shelljs.cp('-r', dllPath, path.join(outputDir, 'sb_dll')); + + logger.info('=> Building manager..'); const managerStartTime = process.hrtime(); + logger.info('=> Loading manager config..'); const managerConfig = await loadManagerConfig({ configType, outputDir, @@ -33,25 +60,40 @@ export async function buildStaticStandalone(options) { corePresets: [require.resolve('./manager/manager-preset.js')], }); - await new Promise((res, rej) => { - webpack(managerConfig).run((err, stats) => { - const managerTotalTime = process.hrtime(managerStartTime); - logger.trace({ message: 'manager built', time: managerTotalTime }); - - if (err) { - rej(err); - } else if (stats.hasErrors()) { - rej(stats); - } else { - res(stats); + logger.info('=> Compiling manager..'); + await 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(); + + 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().warnings.forEach(e => logger.warn(e)); + + resolve(stats); }); }); - // Build the webpack configuration using the `baseConfig` - // custom `.babelrc` file and `webpack.config.js` files - // NOTE changes to env should be done before calling `getBaseConfig` - const config = await loadConfig({ + logger.info('=> Building preview..'); + const previewStartTime = process.hrtime(); + + logger.info('=> Loading preview config..'); + const previewConfig = await loadConfig({ configType, outputDir, packageJson, @@ -60,56 +102,60 @@ export async function buildStaticStandalone(options) { ...options, }); - // config.output.path = path.resolve(outputDir); - - // copy all static files - if (staticDir) { - 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}/!(index.html)`, outputDir); + if (watch) { + logger.info('=> Compiling preview in watch mode..'); + await new Promise(() => { + webpack(previewConfig).watch( + { + aggregateTimeout: 1, + }, + (error, stats) => { + if (!error) { + // eslint-disable-next-line no-console + console.log(stats.toString({ colors: true })); + } else { + logger.error(error.message); + } + } + ); + }); + } else { + logger.info('=> Compiling preview..'); + await 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(); + + 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().warnings.forEach(e => logger.warn(e)); + + return resolve(stats); + }); }); } - // compile all resources with webpack and write them to the disk. - return new Promise((resolve, reject) => { - const previewStartTime = process.hrtime(); - - const webpackCb = (err, stats) => { - if (err || stats.hasErrors()) { - logger.error('Failed to build the storybook'); - // 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; - return reject(err); - } - - const previewTotalTime = process.hrtime(previewStartTime); - logger.trace({ message: 'preview built', time: previewTotalTime }); - - return resolve(stats); - }; - - logger.info('building preview..'); - const compiler = webpack(config); - - if (watch) { - compiler.watch({}, webpackCb); - } else { - compiler.run(webpackCb); - } - }); + logger.info(`=> Output directory: ${outputDir}`); } -export async function buildStatic({ packageJson, ...loadOptions }) { +export function buildStatic({ packageJson, ...loadOptions }) { const cliOptions = getProdCli(packageJson); - await buildStaticStandalone({ + return buildStaticStandalone({ ...cliOptions, ...loadOptions, packageJson, diff --git a/lib/core/src/server/dev-server.js b/lib/core/src/server/dev-server.js index 10766dfb4ebc..7fd664eded1c 100644 --- a/lib/core/src/server/dev-server.js +++ b/lib/core/src/server/dev-server.js @@ -13,52 +13,59 @@ import loadManagerConfig from './manager/manager-config'; let webpackResolve = () => {}; let webpackReject = () => {}; +const dllPath = path.join(__dirname, '../../dll'); + export const webpackValid = new Promise((resolve, reject) => { webpackResolve = resolve; webpackReject = reject; }); +const cache = {}; + export default async function(options) { const configDir = path.resolve(options.configDir); const outputDir = path.resolve(options.outputDir || path.join(__dirname, '..', 'public')); const configType = 'DEVELOPMENT'; - const managerStartTime = process.hrtime(); + const startTime = process.hrtime(); let managerTotalTime; + let previewTotalTime; - const managerConfig = await loadManagerConfig({ + const managerPromise = loadManagerConfig({ configType, outputDir, configDir, + cache, corePresets: [require.resolve('./manager/manager-preset.js')], - }); + }).then( + config => + new Promise((resolve, reject) => { + webpack(config).watch( + { + aggregateTimeout: 1, + ignored: /node_modules/, + }, + (err, stats) => { + managerTotalTime = process.hrtime(startTime); + if (err || stats.hasErrors()) { + reject(stats); + } else { + resolve(stats); + } + } + ); + }) + ); const iframeConfig = await loadConfig({ configType, outputDir, + cache, corePresets: [require.resolve('./preview/preview-preset.js')], overridePresets: [require.resolve('./preview/custom-webpack-preset.js')], ...options, }); - const managerPromise = new Promise((res, rej) => { - webpack(managerConfig).watch( - { - aggregateTimeout: 1, - ignored: /node_modules/, - }, - (err, stats) => { - managerTotalTime = process.hrtime(managerStartTime); - - if (err || stats.hasErrors()) { - rej(stats); - } else { - res(stats); - } - } - ); - }); - const middlewareFn = getMiddleware(configDir); // remove the leading '/' @@ -67,8 +74,6 @@ export default async function(options) { publicPath = publicPath.slice(1); } - const iframeStartTime = process.hrtime(); - let iframeTotalTime; const iframeCompiler = webpack(iframeConfig); const devMiddlewareOptions = { publicPath: iframeConfig.output.publicPath, @@ -92,30 +97,40 @@ export default async function(options) { // custom middleware middlewareFn(router); - const iframePromise = new Promise((res, rej) => { + const previewPromise = new Promise((resolve, reject) => { webpackDevMiddlewareInstance.waitUntilValid(stats => { - iframeTotalTime = process.hrtime(iframeStartTime); + previewTotalTime = process.hrtime(startTime); - if (stats.hasErrors()) { - rej(stats); + if (!stats) { + reject(new Error('no stats after building iframe')); + } else if (stats.hasErrors()) { + reject(stats); } else { - res(stats); + resolve(stats); } }); }); - Promise.all([managerPromise, iframePromise]) - .then(([managerStats, iframeStats]) => { + Promise.all([managerPromise, previewPromise]) + .then(([managerStats, previewStats]) => { router.get('/', (request, response) => { response.set('Content-Type', 'text/html'); response.sendFile(path.join(`${outputDir}/index.html`)); }); + router.get(/\/sb_dll\/(.+\.js)$/, (request, response) => { + response.set('Content-Type', 'text/javascript'); + response.sendFile(path.join(`${dllPath}/${request.params[0]}`)); + }); + router.get(/\/sb_dll\/(.+\.LICENCE)$/, (request, response) => { + response.set('Content-Type', 'text/html'); + response.sendFile(path.join(`${dllPath}/${request.params[0]}`)); + }); router.get(/(.+\.js)$/, (request, response) => { - response.set('Content-Type', 'text/javascript '); + response.set('Content-Type', 'text/javascript'); response.sendFile(path.join(`${outputDir}/${request.params[0]}`)); }); - webpackResolve({ iframeStats, managerStats, managerTotalTime, iframeTotalTime }); + webpackResolve({ previewStats, managerStats, managerTotalTime, previewTotalTime }); }) .catch(e => webpackReject(e)); diff --git a/lib/core/src/server/manager/manager-preset.js b/lib/core/src/server/manager/manager-preset.js index 91956f8b1531..51123ec73b9d 100644 --- a/lib/core/src/server/manager/manager-preset.js +++ b/lib/core/src/server/manager/manager-preset.js @@ -7,7 +7,7 @@ export async function managerWebpack(_, options) { export async function managerEntries(_, options) { const { presets } = options; - const entries = [require.resolve('../common/polyfills')]; + const entries = []; const installedAddons = await presets.apply('addons', [], options); diff --git a/lib/core/src/server/manager/manager-webpack.config.js b/lib/core/src/server/manager/manager-webpack.config.js index a40366842338..d70913b2b01b 100644 --- a/lib/core/src/server/manager/manager-webpack.config.js +++ b/lib/core/src/server/manager/manager-webpack.config.js @@ -1,22 +1,27 @@ +import path from 'path'; import webpack from 'webpack'; import Dotenv from 'dotenv-webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; -import HtmlWebpackHarddiskPlugin from '@ndelangen/html-webpack-harddisk-plugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; +import findCacheDir from 'find-cache-dir'; + import { version } from '../../../package.json'; import { getManagerHeadHtml } from '../utils/template'; import { loadEnv, getBabelRuntimePath } from '../config/utils'; -export default ({ configDir, entries, outputDir }) => { - const { raw, stringified } = loadEnv(); +const coreDirName = path.dirname(require.resolve('@storybook/core/package.json')); +const context = path.join(coreDirName, '../../node_modules'); +const cacheDir = findCacheDir({ name: 'storybook' }); - const exclude = /node_modules/; +export default ({ configDir, configType, entries, outputDir, cache }) => { + const { raw, stringified } = loadEnv(); + const isProd = configType === 'PRODUCTION'; return { name: 'manager', - mode: 'production', - bail: true, + mode: isProd ? 'production' : 'development', + bail: isProd, devtool: 'none', entry: entries, output: { @@ -24,7 +29,12 @@ export default ({ configDir, entries, outputDir }) => { filename: '[name].[chunkhash].bundle.js', publicPath: '', }, + cache, plugins: [ + new webpack.DllReferencePlugin({ + context, + manifest: path.join(__dirname, '../../../dll/storybook_ui-manifest.json'), + }), new HtmlWebpackPlugin({ filename: `index.html`, chunksSortMode: 'none', @@ -35,36 +45,15 @@ export default ({ configDir, entries, outputDir }) => { files, options, version, + dlls: ['/sb_dll/storybook_ui_dll.js'], headHtmlSnippet: getManagerHeadHtml(configDir, process.env), }), template: require.resolve(`../templates/index.ejs`), }), - new HtmlWebpackHarddiskPlugin(), new webpack.DefinePlugin({ 'process.env': stringified }), new CaseSensitivePathsPlugin(), new Dotenv({ silent: true }), ], - module: { - rules: [ - { - test: /\.jsx?$/, - use: [ - { - loader: 'babel-loader', - }, - ], - exclude, - }, - { - test: /\.md$/, - use: [ - { - loader: require.resolve('raw-loader'), - }, - ], - }, - ], - }, resolve: { extensions: ['.mjs', '.js', '.jsx', '.json'], modules: ['node_modules'].concat(raw.NODE_PATH || []), @@ -74,6 +63,7 @@ export default ({ configDir, entries, outputDir }) => { 'react-dom': require.resolve('react-dom'), }, }, + recordsPath: path.join(cacheDir, 'records.json'), optimization: { splitChunks: { chunks: 'all', diff --git a/lib/core/src/server/preview/iframe-webpack.config.js b/lib/core/src/server/preview/iframe-webpack.config.js index 56d154b4341b..30ae9cc9cec8 100644 --- a/lib/core/src/server/preview/iframe-webpack.config.js +++ b/lib/core/src/server/preview/iframe-webpack.config.js @@ -49,6 +49,7 @@ export default ({ options, version: packageJson.version, headHtmlSnippet: getPreviewHeadHtml(configDir, process.env), + dlls: [], bodyHtmlSnippet: getPreviewBodyHtml(), }), template: require.resolve(`../templates/index.ejs`), diff --git a/lib/core/src/server/templates/index.ejs b/lib/core/src/server/templates/index.ejs index cb0822c14f90..11870aa616f2 100644 --- a/lib/core/src/server/templates/index.ejs +++ b/lib/core/src/server/templates/index.ejs @@ -38,6 +38,10 @@ <% } %> +<% dlls.forEach(file => { %> + +<% }); %> + <% files.js.forEach(file => { %> <% }); %> diff --git a/lib/ui/package.json b/lib/ui/package.json index 29af613ae4dc..897cd0c65268 100644 --- a/lib/ui/package.json +++ b/lib/ui/package.json @@ -6,9 +6,6 @@ "storybook" ], "homepage": "https://github.com/storybooks/storybook/tree/master/lib/ui", - "publishConfig": { - "access": "public" - }, "bugs": { "url": "https://github.com/storybooks/storybook/issues" }, @@ -20,9 +17,8 @@ "main": "dist/index.js", "jsnext:main": "src/index.js", "scripts": { - "prepare": "node ../../scripts/prepare.js", - "publish-storybook": "bash ./.scripts/publish_storybook.sh", - "storybook": "start-storybook -p 9010" + "createDlls": "node -r esm ./scripts/createDlls.js", + "prepare": "node ../../scripts/prepare.js" }, "dependencies": { "@emotion/core": "^0.13.1", @@ -34,7 +30,7 @@ "@storybook/podda": "^1.2.3", "@storybook/react-komposer": "^2.0.5", "deep-equal": "^1.0.1", - "events": "^3.0.0", + "eventemitter3": "^3.1.0", "fuse.js": "^3.3.0", "global": "^4.3.2", "keycode": "^2.2.0", @@ -47,11 +43,14 @@ "react-treebeard": "^3.1.0" }, "devDependencies": { - "@storybook/addon-actions": "4.1.0-alpha.9", - "@storybook/react": "4.1.0-alpha.9" + "webpack": "^4.23.1", + "terser-webpack-plugin": "^1.1.0" }, "peerDependencies": { "react": "*", "react-dom": "*" + }, + "publishConfig": { + "access": "public" } } diff --git a/lib/ui/scripts/createDlls.js b/lib/ui/scripts/createDlls.js new file mode 100644 index 000000000000..427b4c0870ba --- /dev/null +++ b/lib/ui/scripts/createDlls.js @@ -0,0 +1,52 @@ +import path from 'path'; +import webpack from 'webpack'; + +import config from './webpackDllsConfig'; + +const resolveLocal = dir => path.join(__dirname, dir); +const webpackAsPromised = c => + new Promise((res, rej) => { + webpack(c).run((err, stats) => { + if (err || stats.hasErrors() || stats.hasWarnings()) { + rej(stats); + return; + } + res(stats); + }); + }); + +const run = () => + webpackAsPromised( + config({ + entry: { + storybook_ui: [ + 'core-js/fn/array/iterator', + 'airbnb-js-shims', + 'core-js/es6/symbol', + 'react', + 'prop-types', + 'react-dom', + '@storybook/components', + '@storybook/addons', + '@storybook/core-events', + '@emotion/styled', + '@emotion/provider', + '@emotion/core', + resolveLocal('../dist/index.js'), + ], + }, + }) + ); + +run().then( + s => { + // eslint-disable-next-line no-console + console.log('success: ', s.toString()); + process.exitCode = 0; + }, + s => { + // eslint-disable-next-line no-console + console.error('failed: ', s.toString()); + process.exitCode = 1; + } +); diff --git a/lib/ui/scripts/webpackDllsConfig.js b/lib/ui/scripts/webpackDllsConfig.js new file mode 100644 index 000000000000..1e472c18b0d6 --- /dev/null +++ b/lib/ui/scripts/webpackDllsConfig.js @@ -0,0 +1,52 @@ +import path from 'path'; +import webpack from 'webpack'; +import TerserPlugin from 'terser-webpack-plugin'; + +const resolveLocal = dir => path.join(__dirname, dir); + +const r = resolveLocal('../../../node_modules'); +const out = resolveLocal('../../core/dll'); + +export default ({ entry, provided = [] }) => ({ + name: 'storybook-ui', + mode: 'production', + + entry, + output: { + path: out, + filename: '[name]_dll.js', + library: '[name]_dll', + }, + externals: provided, + + resolve: { + extensions: ['.mjs', '.js', '.jsx', '.json'], + modules: [path.join(__dirname, '../../../node_modules')], + }, + + plugins: [ + new webpack.ProgressPlugin(), + new webpack.DllPlugin({ + context: r, + path: `${out}/[name]-manifest.json`, + name: '[name]_dll', + }), + ], + optimization: { + concatenateModules: true, + portableRecords: true, + moduleIds: 'hashed', + minimizer: [ + new TerserPlugin({ + extractComments: { + condition: /^\**!|@preserve|@license|@cc_on/i, + filename: file => file.replace('.js', '.LICENCE'), + banner: licenseFile => `License information can be found in ${licenseFile}`, + }, + }), + ], + }, + performance: { + hints: false, + }, +}); diff --git a/lib/ui/src/modules/api/configs/init_api.js b/lib/ui/src/modules/api/configs/init_api.js index 8e00ce5d0732..ddbb680c2cb2 100644 --- a/lib/ui/src/modules/api/configs/init_api.js +++ b/lib/ui/src/modules/api/configs/init_api.js @@ -1,4 +1,4 @@ -import { EventEmitter } from 'events'; +import EventEmitter from 'eventemitter3'; import { getUrlState } from '../../ui/configs/handle_routing'; export default function(provider, clientStore, actions) { diff --git a/lib/ui/src/modules/ui/components/menu_item.stories.js b/lib/ui/src/modules/ui/components/menu_item.stories.js index 23971b87c18f..1849a614bb64 100644 --- a/lib/ui/src/modules/ui/components/menu_item.stories.js +++ b/lib/ui/src/modules/ui/components/menu_item.stories.js @@ -1,6 +1,6 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies +import { action } from '@storybook/addon-actions'; // eslint-disable-line import/no-extraneous-dependencies import MenuItem from './menu_item'; diff --git a/lib/ui/src/modules/ui/components/search_box.stories.js b/lib/ui/src/modules/ui/components/search_box.stories.js index 2d2d90819e71..98b746e0ad30 100644 --- a/lib/ui/src/modules/ui/components/search_box.stories.js +++ b/lib/ui/src/modules/ui/components/search_box.stories.js @@ -1,7 +1,7 @@ import React from 'react'; import ReactModal from 'react-modal'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies +import { action } from '@storybook/addon-actions'; // eslint-disable-line import/no-extraneous-dependencies import { document, navigator } from 'global'; import Component from './search_box'; diff --git a/lib/ui/src/modules/ui/components/shortcuts_help.stories.js b/lib/ui/src/modules/ui/components/shortcuts_help.stories.js index 804b7481b9db..008cbede4c6d 100644 --- a/lib/ui/src/modules/ui/components/shortcuts_help.stories.js +++ b/lib/ui/src/modules/ui/components/shortcuts_help.stories.js @@ -1,7 +1,7 @@ import React from 'react'; import ReactModal from 'react-modal'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies +import { action } from '@storybook/addon-actions'; // eslint-disable-line import/no-extraneous-dependencies import { document, navigator } from 'global'; import ShortcutsHelp from './shortcuts_help'; diff --git a/lib/ui/src/modules/ui/components/stories_panel/index.stories.js b/lib/ui/src/modules/ui/components/stories_panel/index.stories.js index d7f6aa967c09..ba212e127358 100644 --- a/lib/ui/src/modules/ui/components/stories_panel/index.stories.js +++ b/lib/ui/src/modules/ui/components/stories_panel/index.stories.js @@ -1,6 +1,6 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies +import { action } from '@storybook/addon-actions'; // eslint-disable-line import/no-extraneous-dependencies import StoriesPanel from './index'; import { createHierarchies } from '../../libs/hierarchy'; diff --git a/lib/ui/src/modules/ui/components/stories_panel/stories_tree/index.stories.js b/lib/ui/src/modules/ui/components/stories_panel/stories_tree/index.stories.js index 91ce0bc7f4f0..7438df3a6466 100644 --- a/lib/ui/src/modules/ui/components/stories_panel/stories_tree/index.stories.js +++ b/lib/ui/src/modules/ui/components/stories_panel/stories_tree/index.stories.js @@ -1,5 +1,5 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; +import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies import Stories from './index'; import { setContext } from '../../../../../compose'; diff --git a/lib/ui/src/modules/ui/components/stories_panel/text_filter.stories.js b/lib/ui/src/modules/ui/components/stories_panel/text_filter.stories.js index fe41c7940438..a5489fe26a5f 100644 --- a/lib/ui/src/modules/ui/components/stories_panel/text_filter.stories.js +++ b/lib/ui/src/modules/ui/components/stories_panel/text_filter.stories.js @@ -1,6 +1,6 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; // eslint-disable-line import/no-extraneous-dependencies +import { action } from '@storybook/addon-actions'; // eslint-disable-line import/no-extraneous-dependencies import TextFilter from './text_filter'; diff --git a/package.json b/package.json index d905acdbd615..d19dc9d83e7e 100644 --- a/package.json +++ b/package.json @@ -65,10 +65,10 @@ "babel-eslint": "^10.0.1", "babel-jest": "^23.6.0", "babel-loader": "^8.0.4", + "babel-plugin-dynamic-import-node": "^2.2.0", "babel-plugin-emotion": "^9.2.11", "babel-plugin-macros": "^2.4.2", "babel-plugin-require-context-hook": "^1.0.0", - "babel-plugin-dynamic-import-node": "^2.2.0", "babel-preset-vue": "^2.0.2", "chalk": "^2.4.1", "codecov": "^3.1.0", @@ -89,6 +89,7 @@ "eslint-plugin-prettier": "^3.0.0", "eslint-plugin-react": "^7.10.0", "eslint-teamcity": "^2.1.0", + "esm": "^3.0.84", "github-release-from-changelog": "^1.3.2", "glob": "^7.1.3", "husky": "^1.1.1", @@ -121,7 +122,8 @@ "tslint": "~5.11.0", "tslint-config-prettier": "^1.15.0", "tslint-plugin-prettier": "^2.0.1", - "typescript": "^3.1.6" + "typescript": "^3.1.6", + "uglifyjs-webpack-plugin": "^2.0.1" }, "husky": { "hooks": { diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js index 0f40276abbc8..b968eff9def3 100755 --- a/scripts/bootstrap.js +++ b/scripts/bootstrap.js @@ -73,7 +73,7 @@ const tasks = { }, }), core: createTask({ - name: `Core & Examples ${chalk.gray('(core)')}`, + name: `Core, Dll & Examples ${chalk.gray('(core)')}`, defaultValue: true, option: '--core', command: () => { @@ -81,6 +81,17 @@ const tasks = { spawn('yarn install'); log.info(prefix, 'prepare'); spawn('lerna run prepare -- --silent'); + log.info(prefix, 'dll'); + spawn('lerna run createDlls --scope "@storybook/ui"'); + }, + }), + dll: createTask({ + name: `Generate DLL ${chalk.gray('(dll)')}`, + defaultValue: false, + option: '--dll', + command: () => { + log.info(prefix, 'dll'); + spawn('lerna run createDlls --scope "@storybook/ui"'); }, }), docs: createTask({ diff --git a/scripts/compile-js.js b/scripts/compile-js.js index 78230420170b..1ebba2962c76 100644 --- a/scripts/compile-js.js +++ b/scripts/compile-js.js @@ -10,7 +10,7 @@ function getCommand(watch) { '--ignore **/__mocks__/,**/tests/*,**/__tests__/,**/**.test.js,**/stories/,**/**.story.js,**/**.stories.js,**/__snapshots__', './src --out-dir ./dist', '--copy-files', - `--config-file ${path.resolve(__dirname, '../.babelrc')}`, + `--config-file ${path.resolve(__dirname, '../.babelrc.js')}`, ]; if (watch) { diff --git a/yarn.lock b/yarn.lock index 225b61f3ec9f..e1f843a2e74a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1959,13 +1959,6 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@ndelangen/html-webpack-harddisk-plugin@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@ndelangen/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-0.2.0.tgz#d2eb570597c83c1aa93d1f2fcb3d874a5855de07" - integrity sha512-55Mo2b5WtIT0l653y6ocu7C6QzznbasEnlixGzA26WK8Fj81wuEY3xf5N5bNAvDVfrwTLIPTXdEUGgPdrPLszw== - dependencies: - mkdirp "^0.5.1" - "@ngrx/store@^6.1.2": version "6.1.2" resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-6.1.2.tgz#20fb5ab4d79571b804a348093aa11a167fe2946f" @@ -5967,6 +5960,26 @@ cacache@^11.0.1, cacache@^11.0.2: unique-filename "^1.1.0" y18n "^4.0.0" +cacache@^11.2.0: + version "11.3.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.1.tgz#d09d25f6c4aca7a6d305d141ae332613aa1d515f" + integrity sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA== + dependencies: + bluebird "^3.5.1" + chownr "^1.0.1" + figgy-pudding "^3.1.0" + glob "^7.1.2" + graceful-fs "^4.1.11" + lru-cache "^4.1.3" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.2" + ssri "^6.0.0" + unique-filename "^1.1.0" + y18n "^4.0.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -9325,6 +9338,11 @@ eslint@^5.8.0: table "^5.0.2" text-table "^0.2.0" +esm@^3.0.84: + version "3.0.84" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.0.84.tgz#bb108989f4673b32d4f62406869c28eed3815a63" + integrity sha512-SzSGoZc17S7P+12R9cg21Bdb7eybX25RnIeRZ80xZs+VZ3kdQKzqTp2k4hZJjR7p9l0186TTXSgrxzlMDBktlw== + espree@^3.4.3: version "3.5.4" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" @@ -10397,6 +10415,15 @@ fs-extra@^5.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -22259,7 +22286,7 @@ uglify-es@^3.1.9, uglify-es@^3.3.4: commander "~2.13.0" source-map "~0.6.1" -uglify-js@3.4.x, uglify-js@^3.1.4, uglify-js@^3.4.0: +uglify-js@3.4.x, uglify-js@^3.0.0, uglify-js@^3.1.4, uglify-js@^3.4.0: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== @@ -22281,6 +22308,20 @@ uglifyjs-webpack-plugin@^1.2.4: webpack-sources "^1.1.0" worker-farm "^1.5.2" +uglifyjs-webpack-plugin@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.0.1.tgz#f346af53ed496ce72fef462517d417f62bec3010" + integrity sha512-1HhCHkOB6wRCcv7htcz1QRPVbWPEY074RP9vzt/X0LF4xXm9l4YGd0qja7z88abDixQlnVwBjXsTBs+Xsn/eeQ== + dependencies: + cacache "^11.2.0" + find-cache-dir "^2.0.0" + schema-utils "^1.0.0" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + uglify-js "^3.0.0" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" + uid-number@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"