From 78d8c6c181c31c614944bb0d6491ccf869b076c4 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Thu, 2 Aug 2018 09:09:35 +0200 Subject: [PATCH] CHANGE html-webpack-plugin for generate-page-webpack-plugin --- app/react-native/package.json | 3 +- app/react-native/src/server/config.js | 2 +- .../src/server/config/webpack.config.js | 111 ++++++----- .../src/server/config/webpack.config.prod.js | 22 ++- app/react-native/src/server/middleware.js | 3 +- .../cra-kitchen-sink/.storybook/config.js | 2 +- lib/cli/test/snapshots/meteor/package.json | 4 +- lib/core/package.json | 3 +- lib/core/server.js | 5 +- lib/core/src/server/config/utils.js | 6 +- lib/core/src/server/config/webpack.config.js | 53 +++--- .../src/server/config/webpack.config.prod.js | 172 +++++++++--------- lib/core/src/server/iframe.html.ejs | 89 --------- lib/core/src/server/index.html.ejs | 23 --- .../server/templates/base-manager-head.html | 16 ++ .../server/templates/base-preview-body.html | 17 ++ .../server/templates/base-preview-head.html | 62 +++++++ lib/core/src/server/templates/index.html.ejs | 56 ++++++ lib/core/src/server/utils.js | 25 ++- lib/core/src/server/utils.test.js | 7 +- yarn.lock | 8 + 21 files changed, 394 insertions(+), 295 deletions(-) delete mode 100644 lib/core/src/server/iframe.html.ejs delete mode 100644 lib/core/src/server/index.html.ejs create mode 100644 lib/core/src/server/templates/base-manager-head.html create mode 100644 lib/core/src/server/templates/base-preview-body.html create mode 100644 lib/core/src/server/templates/base-preview-head.html create mode 100644 lib/core/src/server/templates/index.html.ejs diff --git a/app/react-native/package.json b/app/react-native/package.json index 16f61b93ea63..47bd18861d7c 100644 --- a/app/react-native/package.json +++ b/app/react-native/package.json @@ -47,10 +47,11 @@ "case-sensitive-paths-webpack-plugin": "^2.1.2", "commander": "^2.15.1", "dotenv-webpack": "^1.5.7", + "ejs": "^2.6.1", "express": "^4.16.3", "find-cache-dir": "^1.0.0", + "generate-page-webpack-plugin": "^1.0.0", "global": "^4.3.2", - "html-webpack-plugin": "^3.2.0", "json5": "^1.0.1", "prop-types": "^15.6.1", "raw-loader": "^0.5.1", diff --git a/app/react-native/src/server/config.js b/app/react-native/src/server/config.js index 07f21fa4c71a..2f929150d1e1 100644 --- a/app/react-native/src/server/config.js +++ b/app/react-native/src/server/config.js @@ -116,7 +116,7 @@ export default function(configType, baseConfig, projectDir, configDir) { if (typeof customConfig === 'function') { logger.info('=> Loading custom webpack config (full-control mode).'); - return customConfig(config, configType, defaultConfig); + return customConfig(config, configType, defaultConfig, configDir); } logger.info('=> Loading custom webpack config.'); diff --git a/app/react-native/src/server/config/webpack.config.js b/app/react-native/src/server/config/webpack.config.js index a9e3b90e8924..18b52b0bfa8e 100644 --- a/app/react-native/src/server/config/webpack.config.js +++ b/app/react-native/src/server/config/webpack.config.js @@ -4,54 +4,73 @@ import { getEnvironment } from 'universal-dotenv'; import Dotenv from 'dotenv-webpack'; import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import { indexHtmlPath } from '@storybook/core/server'; -import { version } from '../../../package.json'; + +import GeneratePagePlugin from 'generate-page-webpack-plugin'; + +import { getManagerHeadHtml } from '@storybook/core/server'; + import { includePaths, excludePaths, nodeModulesPaths } from './utils'; +import { version } from '../../../package.json'; + +const getConfig = options => { + const entriesMeta = { + manager: { + headHtmlSnippet: getManagerHeadHtml(options.configDir, process.env), + }, + }; -const getConfig = options => ({ - mode: 'development', - devtool: '#cheap-module-eval-source-map', - entry: { - manager: [require.resolve('../../manager')], - }, - output: { - path: path.join(__dirname, 'dist'), - filename: 'static/[name].bundle.js', - publicPath: '/', - }, - plugins: [ - new HtmlWebpackPlugin({ - filename: 'index.html', - data: { - version, - }, - template: indexHtmlPath, - }), - new webpack.HotModuleReplacementPlugin(), - new CaseSensitivePathsPlugin(), - new WatchMissingNodeModulesPlugin(nodeModulesPaths), - new webpack.DefinePlugin(getEnvironment().webpack), - new Dotenv({ silent: true }), - new webpack.DefinePlugin({ - storybookOptions: JSON.stringify(options), - }), - ], - module: { - rules: [ - { - test: /\.jsx?$/, - loader: require.resolve('babel-loader'), - query: require('./babel.js'), // eslint-disable-line - include: includePaths, - exclude: excludePaths, - }, - { - test: /\.md$/, - loader: require.resolve('raw-loader'), - }, + return { + mode: 'development', + devtool: '#cheap-module-eval-source-map', + entry: { + manager: [require.resolve('../../manager')], + }, + output: { + path: path.join(__dirname, 'dist'), + filename: 'static/[name].bundle.js', + publicPath: '/', + }, + plugins: [ + new GeneratePagePlugin( + { + template: require.resolve('@storybook/core/dist/server/templates/index.html.ejs'), + // eslint-disable-next-line global-require + parser: require('ejs'), + filename: entry => (entry === 'manager' ? 'index' : entry), + }, + { + data: { version }, + headHtmlSnippet: entry => + entriesMeta[entry] ? entriesMeta[entry].headHtmlSnippet : null, + bodyHtmlSnippet: entry => + entriesMeta[entry] ? entriesMeta[entry].bodyHtmlSnippet : null, + } + ), + new webpack.HotModuleReplacementPlugin(), + new CaseSensitivePathsPlugin(), + new WatchMissingNodeModulesPlugin(nodeModulesPaths), + new webpack.DefinePlugin(getEnvironment().webpack), + new Dotenv({ silent: true }), + new webpack.DefinePlugin({ + storybookOptions: JSON.stringify(options), + }), ], - }, -}); + module: { + rules: [ + { + test: /\.jsx?$/, + loader: require.resolve('babel-loader'), + query: require('./babel.js'), // eslint-disable-line + include: includePaths, + exclude: excludePaths, + }, + { + test: /\.md$/, + loader: require.resolve('raw-loader'), + }, + ], + }, + }; +}; export default getConfig; diff --git a/app/react-native/src/server/config/webpack.config.prod.js b/app/react-native/src/server/config/webpack.config.prod.js index 7f81e6291566..6393a47dc77b 100644 --- a/app/react-native/src/server/config/webpack.config.prod.js +++ b/app/react-native/src/server/config/webpack.config.prod.js @@ -2,8 +2,9 @@ import path from 'path'; import webpack from 'webpack'; import { getEnvironment } from 'universal-dotenv'; import Dotenv from 'dotenv-webpack'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import { indexHtmlPath } from '@storybook/core/server'; +import GeneratePagePlugin from 'generate-page-webpack-plugin'; + +import { getManagerHeadHtml } from '@storybook/core/dist/server/utils'; import { version } from '../../../package.json'; import { includePaths, excludePaths } from './utils'; @@ -26,13 +27,18 @@ const getConfig = options => { publicPath: '/', }, plugins: [ - new HtmlWebpackPlugin({ - filename: 'index.html', - data: { - version, + new GeneratePagePlugin( + { + template: require.resolve('@storybook/core/dist/server/templates/index.html.ejs'), + // eslint-disable-next-line global-require + parser: require('ejs'), + filename: entry => (entry === 'manager' ? 'index' : entry), }, - template: indexHtmlPath, - }), + { + data: { version }, + headHtmlSnippet: getManagerHeadHtml(options.configDir, process.env), + } + ), new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"', storybookOptions: JSON.stringify(options), diff --git a/app/react-native/src/server/middleware.js b/app/react-native/src/server/middleware.js index fc6e8a25c7ea..b3c391bf4a0a 100644 --- a/app/react-native/src/server/middleware.js +++ b/app/react-native/src/server/middleware.js @@ -20,7 +20,8 @@ function getMiddleware(configDir) { return () => {}; } -export default function({ projectDir, configDir, ...options }) { +export default function(options) { + const { projectDir, configDir } = options; // Build the webpack configuration using the `baseConfig` // custom `.babelrc` file and `webpack.config.js` files const environment = options.environment || 'DEVELOPMENT'; diff --git a/examples/cra-kitchen-sink/.storybook/config.js b/examples/cra-kitchen-sink/.storybook/config.js index 0a2e0ad678e9..1a0e64de735f 100644 --- a/examples/cra-kitchen-sink/.storybook/config.js +++ b/examples/cra-kitchen-sink/.storybook/config.js @@ -11,7 +11,7 @@ setOptions({ sortStoriesByKind: false, hierarchySeparator: /\./, hierarchyRootSeparator: /\|/, - enableShortcuts: false, + enableShortcuts: true, }); function loadStories() { diff --git a/lib/cli/test/snapshots/meteor/package.json b/lib/cli/test/snapshots/meteor/package.json index 608ee47adccb..cda48c092a4e 100644 --- a/lib/cli/test/snapshots/meteor/package.json +++ b/lib/cli/test/snapshots/meteor/package.json @@ -10,8 +10,8 @@ "dependencies": { "babel-runtime": "^6.20.0", "meteor-node-stubs": "~0.2.4", - "react": "^16.4.1", - "react-dom": "^16.4.1" + "react": "^16.4.2", + "react-dom": "^16.4.2" }, "devDependencies": { "babel-core": "^6.26.3", diff --git a/lib/core/package.json b/lib/core/package.json index d334ffe36899..f319293118a2 100644 --- a/lib/core/package.json +++ b/lib/core/package.json @@ -38,12 +38,13 @@ "core-js": "^2.5.7", "css-loader": "^0.28.11", "dotenv-webpack": "^1.5.7", + "ejs": "^2.6.1", "emotion": "^9.1.3", "express": "^4.16.3", "file-loader": "^1.1.11", "find-cache-dir": "^1.0.0", + "generate-page-webpack-plugin": "^1.0.0", "global": "^4.3.2", - "html-webpack-plugin": "^3.2.0", "interpret": "^1.1.0", "json5": "^1.0.1", "postcss-flexbugs-fixes": "^3.3.1", diff --git a/lib/core/server.js b/lib/core/server.js index e6092792cc17..2a518b2dc91f 100644 --- a/lib/core/server.js +++ b/lib/core/server.js @@ -4,7 +4,4 @@ const serverUtils = require('./dist/server/utils'); const buildStatic = require('./dist/server/build-static'); const buildDev = require('./dist/server/build-dev'); -module.exports = assign({}, defaultWebpackConfig, buildStatic, buildDev, serverUtils, { - indexHtmlPath: require.resolve('./src/server/index.html.ejs'), - iframeHtmlPath: require.resolve('./src/server/iframe.html.ejs'), -}); +module.exports = assign({}, defaultWebpackConfig, buildStatic, buildDev, serverUtils, {}); diff --git a/lib/core/src/server/config/utils.js b/lib/core/src/server/config/utils.js index de3e4ae4da08..189992f52ea6 100644 --- a/lib/core/src/server/config/utils.js +++ b/lib/core/src/server/config/utils.js @@ -37,7 +37,7 @@ export function loadEnv(options = {}) { } export function getEntries(configDir) { - const preview = [require.resolve('./polyfills'), require.resolve('./globals')]; + const iframe = [require.resolve('./polyfills'), require.resolve('./globals')]; const manager = [require.resolve('./polyfills'), require.resolve('../../client/manager')]; // Check whether a config.{ext} file exists inside the storybook @@ -47,7 +47,7 @@ export function getEntries(configDir) { throw new Error(`=> Create a storybook config file in "${configDir}/config.{ext}".`); } - preview.push(require.resolve(storybookConfigPath)); + iframe.push(require.resolve(storybookConfigPath)); // Check whether addons.{ext} file exists inside the storybook. const storybookCustomAddonsPath = getInterpretedFile(path.resolve(configDir, 'addons')); @@ -56,5 +56,5 @@ export function getEntries(configDir) { manager.unshift(storybookCustomAddonsPath); } - return { preview, manager }; + return { iframe, manager }; } diff --git a/lib/core/src/server/config/webpack.config.js b/lib/core/src/server/config/webpack.config.js index 9e81a1ab9e9e..dbcb827d7402 100644 --- a/lib/core/src/server/config/webpack.config.js +++ b/lib/core/src/server/config/webpack.config.js @@ -2,12 +2,10 @@ import path from 'path'; import webpack from 'webpack'; import { getEnvironment } from 'universal-dotenv'; import Dotenv from 'dotenv-webpack'; -import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin'; import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; - -import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils'; +import GeneratePagePlugin from 'generate-page-webpack-plugin'; +import { getPreviewHeadHtml, getManagerHeadHtml, getPreviewBodyHtml } from '../utils'; import { includePaths, @@ -21,13 +19,23 @@ import { version } from '../../../package.json'; export default ({ configDir, quiet, babelOptions }) => { const entries = getEntries(configDir, true); + const entriesMeta = { + iframe: { + headHtmlSnippet: getPreviewHeadHtml(configDir, process.env), + bodyHtmlSnippet: getPreviewBodyHtml(), + }, + manager: { + headHtmlSnippet: getManagerHeadHtml(configDir, process.env), + }, + }; + return { mode: 'development', devtool: 'cheap-module-source-map', entry: { ...entries, - preview: [ - ...entries.preview, + iframe: [ + ...entries.iframe, `${require.resolve('webpack-hot-middleware/client')}?reload=true`, ], }, @@ -42,26 +50,21 @@ export default ({ configDir, quiet, babelOptions }) => { publicPath: '', }, plugins: [ - new HtmlWebpackPlugin({ - filename: 'index.html', - chunks: ['manager'], - chunksSortMode: 'none', - data: { - managerHead: getManagerHeadHtml(configDir), - version, - }, - template: require.resolve('../index.html.ejs'), - }), - new HtmlWebpackPlugin({ - filename: 'iframe.html', - excludeChunks: ['manager'], - chunksSortMode: 'none', - data: { - previewHead: getPreviewHeadHtml(configDir), + new GeneratePagePlugin( + { + template: require.resolve('../templates/index.html.ejs'), + // eslint-disable-next-line global-require + parser: require('ejs'), + filename: entry => (entry === 'manager' ? 'index' : entry), }, - template: require.resolve('../iframe.html.ejs'), - }), - new InterpolateHtmlPlugin(process.env), + { + data: { version }, + headHtmlSnippet: entry => + entriesMeta[entry] ? entriesMeta[entry].headHtmlSnippet : null, + bodyHtmlSnippet: entry => + entriesMeta[entry] ? entriesMeta[entry].bodyHtmlSnippet : null, + } + ), new webpack.DefinePlugin(loadEnv()), new webpack.HotModuleReplacementPlugin(), new CaseSensitivePathsPlugin(), diff --git a/lib/core/src/server/config/webpack.config.prod.js b/lib/core/src/server/config/webpack.config.prod.js index 082435548c55..c3a73ed89207 100644 --- a/lib/core/src/server/config/webpack.config.prod.js +++ b/lib/core/src/server/config/webpack.config.prod.js @@ -1,92 +1,100 @@ import webpack from 'webpack'; import { getEnvironment } from 'universal-dotenv'; import Dotenv from 'dotenv-webpack'; -import InterpolateHtmlPlugin from 'react-dev-utils/InterpolateHtmlPlugin'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; +import GeneratePagePlugin from 'generate-page-webpack-plugin'; import { version } from '../../../package.json'; -import { getPreviewHeadHtml, getManagerHeadHtml } from '../utils'; +import { getPreviewHeadHtml, getManagerHeadHtml, getPreviewBodyHtml } from '../utils'; import { includePaths, excludePaths, loadEnv, nodePaths, getEntries } from './utils'; -export default ({ configDir, babelOptions }) => ({ - mode: 'production', - bail: true, - devtool: '#cheap-module-source-map', - entry: getEntries(configDir), - output: { - filename: 'static/[name].[chunkhash].bundle.js', - // Here we set the publicPath to ''. - // This allows us to deploy storybook into subpaths like GitHub pages. - // This works with css and image loaders too. - // This is working for storybook since, we don't use pushState urls and - // relative URLs works always. - publicPath: '', - }, - plugins: [ - new HtmlWebpackPlugin({ - filename: 'index.html', - chunks: ['manager', 'runtime~manager'], - chunksSortMode: 'none', - data: { - managerHead: getManagerHeadHtml(configDir), - version, - }, - template: require.resolve('../index.html.ejs'), - }), - new HtmlWebpackPlugin({ - filename: 'iframe.html', - excludeChunks: ['manager', 'runtime~manager'], - chunksSortMode: 'none', - data: { - previewHead: getPreviewHeadHtml(configDir), - }, - template: require.resolve('../iframe.html.ejs'), - }), - new InterpolateHtmlPlugin(process.env), - new webpack.DefinePlugin(loadEnv({ production: true })), - new webpack.DefinePlugin(getEnvironment().webpack), - new Dotenv({ silent: true }), - ], - module: { - rules: [ - { - test: /\.js$/, - use: [ - { - loader: require.resolve('babel-loader'), - options: babelOptions, - }, - ], - include: includePaths, - exclude: excludePaths, - }, - { - test: /\.md$/, - use: [ - { - loader: require.resolve('raw-loader'), - }, - ], - }, +export default ({ configDir, babelOptions }) => { + const entries = getEntries(configDir); + + const entriesMeta = { + iframe: { + headHtmlSnippet: getPreviewHeadHtml(configDir, process.env), + bodyHtmlSnippet: getPreviewBodyHtml(), + }, + manager: { + headHtmlSnippet: getManagerHeadHtml(configDir, process.env), + }, + }; + + return { + mode: 'production', + bail: true, + devtool: '#cheap-module-source-map', + entry: entries, + output: { + filename: 'static/[name].[chunkhash].bundle.js', + // Here we set the publicPath to ''. + // This allows us to deploy storybook into subpaths like GitHub pages. + // This works with css and image loaders too. + // This is working for storybook since, we don't use pushState urls and + // relative URLs works always. + publicPath: '', + }, + plugins: [ + new GeneratePagePlugin( + { + template: require.resolve('../templates/index.html.ejs'), + // eslint-disable-next-line global-require + parser: require('ejs'), + filename: entry => (entry === 'manager' ? 'index' : entry), + }, + { + data: { version }, + headHtmlSnippet: entry => + entriesMeta[entry] ? entriesMeta[entry].headHtmlSnippet : null, + bodyHtmlSnippet: entry => + entriesMeta[entry] ? entriesMeta[entry].bodyHtmlSnippet : null, + } + ), + new webpack.DefinePlugin(loadEnv({ production: true })), + new webpack.DefinePlugin(getEnvironment().webpack), + new Dotenv({ silent: true }), ], - }, - resolve: { - // Since we ship with json-loader always, it's better to move extensions to here - // from the default config. - extensions: ['.js', '.json'], - // Add support to NODE_PATH. With this we could avoid relative path imports. - // Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253 - modules: ['node_modules'].concat(nodePaths), - }, - optimization: { - // Automatically split vendor and commons for preview bundle - // https://twitter.com/wSokra/status/969633336732905474 - splitChunks: { - chunks: chunk => chunk.name !== 'manager', + module: { + rules: [ + { + test: /\.js$/, + use: [ + { + loader: require.resolve('babel-loader'), + options: babelOptions, + }, + ], + include: includePaths, + exclude: excludePaths, + }, + { + test: /\.md$/, + use: [ + { + loader: require.resolve('raw-loader'), + }, + ], + }, + ], + }, + resolve: { + // Since we ship with json-loader always, it's better to move extensions to here + // from the default config. + extensions: ['.js', '.json'], + // Add support to NODE_PATH. With this we could avoid relative path imports. + // Based on this CRA feature: https://github.com/facebookincubator/create-react-app/issues/253 + modules: ['node_modules'].concat(nodePaths), + }, + optimization: { + // Automatically split vendor and commons for preview bundle + // https://twitter.com/wSokra/status/969633336732905474 + splitChunks: { + chunks: chunk => chunk.name !== 'manager', + }, + // Keep the runtime chunk seperated to enable long term caching + // https://twitter.com/wSokra/status/969679223278505985 + runtimeChunk: true, }, - // Keep the runtime chunk seperated to enable long term caching - // https://twitter.com/wSokra/status/969679223278505985 - runtimeChunk: true, - }, -}); + }; +}; diff --git a/lib/core/src/server/iframe.html.ejs b/lib/core/src/server/iframe.html.ejs deleted file mode 100644 index 380291d3499c..000000000000 --- a/lib/core/src/server/iframe.html.ejs +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - Storybook - - <%= htmlWebpackPlugin.options.data.previewHead %> - - - -
- -
-
-

No Preview

-

Sorry, but you either have no stories or none are selected somehow.

-
    -
  • Please check the storybook config.
  • -
  • Try reloading the page.
  • -
-
-
- -
-
-
-            
-        
-
- - diff --git a/lib/core/src/server/index.html.ejs b/lib/core/src/server/index.html.ejs deleted file mode 100644 index 536147695d5e..000000000000 --- a/lib/core/src/server/index.html.ejs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - Storybook - - <%= htmlWebpackPlugin.options.data.managerHead %> - - - - -
- - diff --git a/lib/core/src/server/templates/base-manager-head.html b/lib/core/src/server/templates/base-manager-head.html new file mode 100644 index 000000000000..6a9935e30d2f --- /dev/null +++ b/lib/core/src/server/templates/base-manager-head.html @@ -0,0 +1,16 @@ + + + diff --git a/lib/core/src/server/templates/base-preview-body.html b/lib/core/src/server/templates/base-preview-body.html new file mode 100644 index 000000000000..acf6b91b89d7 --- /dev/null +++ b/lib/core/src/server/templates/base-preview-body.html @@ -0,0 +1,17 @@ +
+
+

No Preview

+

Sorry, but you either have no stories or none are selected somehow.

+
    +
  • Please check the storybook config.
  • +
  • Try reloading the page.
  • +
+
+
+ +
+
+
+    
+  
+
diff --git a/lib/core/src/server/templates/base-preview-head.html b/lib/core/src/server/templates/base-preview-head.html new file mode 100644 index 000000000000..ff61accb80b1 --- /dev/null +++ b/lib/core/src/server/templates/base-preview-head.html @@ -0,0 +1,62 @@ + + + + + diff --git a/lib/core/src/server/templates/index.html.ejs b/lib/core/src/server/templates/index.html.ejs new file mode 100644 index 000000000000..6e5e2a5b9bcb --- /dev/null +++ b/lib/core/src/server/templates/index.html.ejs @@ -0,0 +1,56 @@ + + + + + + + Storybook + + + <% if (options.links) { %> + <% for (item of options.links) { %> + <% if (typeof item === 'string' || item instanceof String) { item = { href: item, rel: 'stylesheet' } } %> + <%= key %>="<%= item[key] %>"<% } %>> + <% } %> + <% } %> + + <% if (options.headHtmlSnippet) { %> + <%- options.headHtmlSnippet %> + <% } %> + + + + + <% if (options.window) { %> + + <% } %> + + <% if (options.bodyHtmlSnippet) { %> + <%- options.bodyHtmlSnippet %> + <% } %> + +
+ + <% if (options.scripts) { %> + <% for (item of options.scripts) { %> + <% if (typeof item === 'string' || item instanceof String) { item = { src: item } } %> + + <% } %> + <% } %> + + <% for (key in dlls) { %> + + <% } %> + + <% for (index in chunks) { %> + <% for (key in chunks[index].files) { %> + + <% } %> + <% } %> + + + diff --git a/lib/core/src/server/utils.js b/lib/core/src/server/utils.js index da94ae54f648..4cf9e6905a09 100644 --- a/lib/core/src/server/utils.js +++ b/lib/core/src/server/utils.js @@ -27,22 +27,35 @@ export function getMiddleware(configDir) { return () => {}; } -export function getPreviewHeadHtml(configDirPath) { +const interpolate = (string, data = {}) => + Object.entries(data).reduce((acc, [k, v]) => acc.replace(`%${k}%`, v), string); + +export function getPreviewBodyHtml() { + return fs.readFileSync(path.resolve(__dirname, 'templates/base-preview-body.html'), 'utf8'); +} + +export function getPreviewHeadHtml(configDirPath, interpolations) { + const base = fs.readFileSync(path.resolve(__dirname, 'templates/base-preview-head.html'), 'utf8'); const headHtmlPath = path.resolve(configDirPath, 'preview-head.html'); + let result = base; + if (fs.existsSync(headHtmlPath)) { - return fs.readFileSync(headHtmlPath, 'utf8'); + result += fs.readFileSync(headHtmlPath, 'utf8'); } - return ''; + return interpolate(result, interpolations); } -export function getManagerHeadHtml(configDirPath) { +export function getManagerHeadHtml(configDirPath, interpolations) { + const base = fs.readFileSync(path.resolve(__dirname, 'templates/base-manager-head.html'), 'utf8'); const scriptPath = path.resolve(configDirPath, 'manager-head.html'); + let result = base; + if (fs.existsSync(scriptPath)) { - return fs.readFileSync(scriptPath, 'utf8'); + result += fs.readFileSync(scriptPath, 'utf8'); } - return ''; + return interpolate(result, interpolations); } diff --git a/lib/core/src/server/utils.test.js b/lib/core/src/server/utils.test.js index 7be24c21a659..e5e6ad6445c2 100644 --- a/lib/core/src/server/utils.test.js +++ b/lib/core/src/server/utils.test.js @@ -2,11 +2,13 @@ import mock from 'mock-fs'; import { getPreviewHeadHtml } from './utils'; const HEAD_HTML_CONTENTS = ''; +const BASE_HTML_CONTENTS = ''; describe('server.getPreviewHeadHtml', () => { describe('when .storybook/preview-head.html does not exist', () => { beforeEach(() => { mock({ + [`${__dirname}/templates/base-preview-head.html`]: BASE_HTML_CONTENTS, config: {}, }); }); @@ -17,13 +19,14 @@ describe('server.getPreviewHeadHtml', () => { it('return an empty string', () => { const result = getPreviewHeadHtml('./config'); - expect(result).toEqual(''); + expect(result).toEqual(BASE_HTML_CONTENTS); }); }); describe('when .storybook/preview-head.html exists', () => { beforeEach(() => { mock({ + [`${__dirname}/templates/base-preview-head.html`]: BASE_HTML_CONTENTS, config: { 'preview-head.html': HEAD_HTML_CONTENTS, }, @@ -36,7 +39,7 @@ describe('server.getPreviewHeadHtml', () => { it('return the contents of the file', () => { const result = getPreviewHeadHtml('./config'); - expect(result).toEqual(HEAD_HTML_CONTENTS); + expect(result).toEqual(BASE_HTML_CONTENTS + HEAD_HTML_CONTENTS); }); }); }); diff --git a/yarn.lock b/yarn.lock index d0997f8d8cf2..53fa3223507a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5731,6 +5731,10 @@ ejs@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" +ejs@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" + ejson@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ejson/-/ejson-2.1.2.tgz#0eed4055bc7e0e7561fe59e8c320edc3ff8ce7df" @@ -7414,6 +7418,10 @@ generate-object-property@^1.1.0: dependencies: is-property "^1.0.0" +generate-page-webpack-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/generate-page-webpack-plugin/-/generate-page-webpack-plugin-1.0.0.tgz#e6261efb7e6b9ef8a8136126b14fa26aedfb7f33" + genfun@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/genfun/-/genfun-4.0.1.tgz#ed10041f2e4a7f1b0a38466d17a5c3e27df1dfc1"