[canvas] Refactor Storybook from bespoke to standard configuration (#101962) (#102589)

Co-authored-by: Clint Andrew Hall <clint.hall@elastic.co>
This commit is contained in:
Kibana Machine 2021-06-17 21:05:55 -04:00 committed by GitHub
parent c05cb5326a
commit 2ffcc767e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 82 additions and 582 deletions

View file

@ -11,6 +11,6 @@
"scripts": {
"build": "../../node_modules/.bin/tsc",
"kbn:bootstrap": "yarn build",
"watch": "yarn build --watch"
"kbn:watch": "yarn build --watch"
}
}

View file

@ -71,11 +71,12 @@ export default function ({ config: storybookConfig }: { config: Configuration })
],
},
resolve: {
// Tell Webpack about the scss extension
extensions: ['.scss'],
extensions: ['.js', '.ts', '.tsx', '.json'],
mainFields: ['browser', 'main'],
alias: {
core_app_image_assets: resolve(REPO_ROOT, 'src/core/public/core_app/images'),
},
symlinks: false,
},
stats,
};

View file

@ -2,9 +2,6 @@
source src/dev/ci_setup/setup_env.sh
cd "$XPACK_DIR/plugins/canvas"
node scripts/storybook --dll
cd "$KIBANA_DIR"
yarn storybook --site apm

View file

@ -7,7 +7,6 @@
"scripts": {
"github-checks-reporter": "../node_modules/.bin/github-checks-reporter",
"kbn": "node ../scripts/kbn",
"kbn:bootstrap": "node plugins/canvas/scripts/storybook --clean",
"start": "node ../scripts/kibana --dev",
"build": "node --preserve-symlinks ../node_modules/.bin/gulp build",
"test:jest": "node ../scripts/jest"

View file

@ -113,27 +113,4 @@ Canvas uses [Storybook](https://storybook.js.org) to test and develop components
### Using Storybook
The Canvas Storybook instance can be started by running `node scripts/storybook` from the Canvas root directory. It has a number of options:
```
node scripts/storybook
Storybook runner for Canvas.
Options:
--clean Forces a clean of the Storybook DLL and exits.
--dll Cleans and builds the Storybook dependency DLL and exits.
--stats Produces a Webpack stats file.
--site Produces a site deployment of this Storybook.
--verbose, -v Log verbosely
--debug Log debug messages (less than verbose)
--quiet Only log errors
--silent Don't log anything
--help Show this message
```
### What about `kbn-storybook`?
Canvas wants to move to the Kibana Storybook instance as soon as feasible. There are few tweaks Canvas makes to Storybook, so we're actively working with the maintainers to make that migration successful.
In the meantime, people can test our progress by running `node scripts/storybook_new` from the Canvas root.
The Canvas Storybook instance can be started by running `yarn storybook canvas` from the Kibana root directory.

View file

@ -1,124 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
const path = require('path');
const fs = require('fs');
const del = require('del');
const { run } = require('@kbn/dev-utils');
// This is included in the main Kibana package.json
// eslint-disable-next-line import/no-extraneous-dependencies
const storybook = require('@storybook/react/standalone');
const execa = require('execa');
const { DLL_OUTPUT } = require('./../storybook/constants');
const storybookOptions = {
configDir: path.resolve(__dirname, './../storybook'),
mode: 'dev',
};
run(
({ log, flags }) => {
const { addon, dll, clean, stats, site } = flags;
// Delete the existing DLL if we're cleaning or building.
if (clean || dll) {
del.sync([DLL_OUTPUT], { force: true });
if (clean) {
return;
}
}
// Build the DLL if necessary.
if (fs.existsSync(DLL_OUTPUT)) {
log.info('storybook: DLL exists from previous build; skipping');
} else {
log.info('storybook: Building DLL');
execa.sync(
'yarn',
[
'webpack',
'--config',
'x-pack/plugins/canvas/storybook/webpack.dll.config.js',
...(process.stdout.isTTY && !process.env.CI ? ['--progress'] : []),
'--hide-modules',
'--display-entrypoints',
'false',
],
{
cwd: path.resolve(__dirname, '../../../..'),
stdio: ['ignore', 'inherit', 'inherit'],
buffer: false,
}
);
log.success('storybook: DLL built');
}
// If we're only building the DLL, we're done.
if (dll) {
return;
}
// Build statistics and exit
if (stats) {
log.success('storybook: Generating Storybook statistics');
storybook({
...storybookOptions,
smokeTest: true,
});
return;
}
// Build the addon
execa.sync('node', ['scripts/build'], {
cwd: path.resolve(__dirname, '../storybook/addon'),
stdio: ['ignore', 'inherit', 'inherit'],
buffer: false,
});
// Build site and exit
if (site) {
log.success('storybook: Generating Storybook site');
storybook({
...storybookOptions,
mode: 'static',
outputDir: path.resolve(__dirname, './../storybook/build'),
});
return;
}
log.info('storybook: Starting Storybook');
if (addon) {
execa('node', ['scripts/build', '--watch'], {
cwd: path.resolve(__dirname, '../storybook/addon'),
stdio: ['ignore', 'inherit', 'inherit'],
buffer: false,
});
}
storybook({
...storybookOptions,
port: 9001,
});
},
{
description: `
Storybook runner for Canvas.
`,
flags: {
boolean: ['addon', 'dll', 'clean', 'stats', 'site'],
help: `
--addon Watch the addon source code for changes.
--clean Forces a clean of the Storybook DLL and exits.
--dll Cleans and builds the Storybook dependency DLL and exits.
--stats Produces a Webpack stats file.
--site Produces a site deployment of this Storybook.
`,
},
}
);

View file

@ -153,7 +153,7 @@ You can test this functionality in a number of ways. The easiest would be:
### Run the Canvas Storybook
From `/canvas`: `node scripts/storybook`
From `/kibana`: `yarn storybook canvas`
### Run the Jest Tests

View file

@ -1,9 +0,0 @@
{
"env": {
"test": {
"plugins": [
"require-context-hook"
]
}
}
}

View file

@ -9,6 +9,8 @@ import React from 'react';
import { addons, types } from '@storybook/addons';
import { AddonPanel } from '@storybook/components';
import { STORY_CHANGED } from '@storybook/core-events';
import { create } from '@storybook/theming';
import { PANEL_ID } from '@storybook/addon-actions';
import { ADDON_ID, EVENTS, ACTIONS_PANEL_ID } from './constants';
import { Panel } from './panel';
@ -32,3 +34,16 @@ addons.register(ADDON_ID, (api) => {
},
});
});
addons.setConfig({
theme: create({
base: 'light',
brandTitle: 'Canvas Storybook',
brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas',
}),
showPanel: true,
isFullscreen: false,
panelPosition: 'bottom',
isToolshown: true,
selectedPanel: PANEL_ID,
});

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
const path = require('path');
const DLL_NAME = 'canvas_storybook_dll';
const KIBANA_ROOT = path.resolve(__dirname, '../../../..');
const DLL_OUTPUT = path.resolve(KIBANA_ROOT, 'built_assets', DLL_NAME);
module.exports = {
DLL_NAME,
KIBANA_ROOT,
DLL_OUTPUT,
};

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import path from 'path';
export const KIBANA_ROOT = path.resolve(__dirname, '../../../..');

View file

@ -1,13 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// This file defines CSS and Legacy style contexts for use in the DLL. This file
// is also require'd in the Storybook config so that the Storybook Webpack instance
// is aware of them, and can load them from the DLL.
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
require('../../../../src/core/server/core_app/assets/legacy_light_theme.css');

View file

@ -5,23 +5,58 @@
* 2.0.
*/
/* eslint-disable @typescript-eslint/no-var-requires */
const { existsSync } = require('fs');
const { join } = require('path');
import { resolve } from 'path';
import webpackMerge from 'webpack-merge';
import { defaultConfig } from '@kbn/storybook';
// Check for DLL if we're not running in Jest
if (
!existsSync(join(__dirname, '../../../../built_assets/canvas_storybook_dll/manifest.json')) &&
!process.env.JEST_WORKER_ID
) {
// eslint-disable-next-line no-console
console.error(
'No DLL found. Run `node scripts/storybook --dll` from the Canvas plugin directory.'
);
process.exit(1);
}
import type { Configuration } from 'webpack';
import { KIBANA_ROOT } from './constants';
const canvasWebpack = {
module: {
rules: [
// Enable CSS Modules in Storybook (Shareable Runtime)
{
test: /\.module\.s(a|c)ss$/,
loader: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]',
},
},
},
{
loader: 'postcss-loader',
options: {
path: resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'),
},
},
{
loader: 'sass-loader',
},
],
},
// Exclude large-dependency, troublesome or irrelevant modules.
{
test: [
resolve(KIBANA_ROOT, 'x-pack/plugins/canvas/public/components/embeddable_flyout'),
resolve(KIBANA_ROOT, 'x-pack/plugins/reporting/public'),
resolve(KIBANA_ROOT, 'src/plugins/kibana_legacy/public/angular'),
resolve(KIBANA_ROOT, 'src/plugins/kibana_legacy/public/paginate'),
],
use: 'null-loader',
},
],
},
};
module.exports = {
stories: ['../**/*.stories.tsx'],
addons: ['@storybook/addon-actions', '@storybook/addon-knobs', './addon/target/register'],
...defaultConfig,
addons: [...(defaultConfig.addons || []), './addon/target/register'],
webpackFinal: (config: Configuration) => webpackMerge(config, canvasWebpack),
};

View file

@ -1,23 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { addons } from '@storybook/addons';
import { create } from '@storybook/theming';
import { PANEL_ID } from '@storybook/addon-actions';
addons.setConfig({
theme: create({
base: 'light',
brandTitle: 'Canvas Storybook',
brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas',
}),
showPanel: true,
isFullscreen: false,
panelPosition: 'bottom',
isToolshown: true,
selectedPanel: PANEL_ID,
});

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import path from 'path';
// @ts-expect-error
import serve from 'serve-static';
// Extend the Storybook Middleware to include a route to access Legacy UI assets
module.exports = function (router: { get: (...args: any[]) => void }) {
router.get(
'/ui',
serve(path.resolve(__dirname, '../../../../../src/core/server/core_app/assets'))
);
};

View file

@ -1,6 +0,0 @@
<!--
This file is looked for by Storybook and included in the HEAD element
if it exists. This is how we load the DLL content into the Storybook UI.
-->
<script src="dll.js"></script>
<link href="dll.css" rel="stylesheet" />

View file

@ -10,9 +10,6 @@ import { action } from '@storybook/addon-actions';
import { startServices } from '../public/services/stubs';
import { addDecorators } from './decorators';
// Import the modules from the DLL.
import './dll_contexts';
// Import Canvas CSS
import '../public/style/index.scss';

View file

@ -1,210 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
const path = require('path');
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const { stringifyRequest } = require('loader-utils');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { DLL_OUTPUT, KIBANA_ROOT } = require('./constants');
// Extend the Storybook Webpack config with some customizations
module.exports = async ({ config: storybookConfig }) => {
const config = {
module: {
rules: [
// Include the React preset from Kibana for JS(X) and TS(X)
{
test: /\.(j|t)sx?$/,
exclude: /node_modules/,
loaders: 'babel-loader',
options: {
presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
},
},
// Parse props data for .tsx files
// This is notoriously slow, and is making Storybook unusable. Disabling for now.
// See: https://github.com/storybookjs/storybook/issues/7998
//
// {
// test: /\.tsx$/,
// // Exclude example files, as we don't display props info for them
// exclude: /\.examples.tsx$/,
// use: [
// // Parse TS comments to create Props tables in the UI
// require.resolve('react-docgen-typescript-loader'),
// ],
// },
// Enable SASS, but exclude CSS Modules in Storybook
{
test: /\.scss$/,
exclude: /\.module.(s(a|c)ss)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader', options: { importLoaders: 2 } },
{
loader: 'postcss-loader',
options: {
path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'),
},
},
{
loader: 'sass-loader',
options: {
prependData(loaderContext) {
return `@import ${stringifyRequest(
loaderContext,
path.resolve(
KIBANA_ROOT,
'src/core/public/core_app/styles/_globals_v7light.scss'
)
)};\n`;
},
sassOptions: {
includePaths: [path.resolve(KIBANA_ROOT, 'node_modules')],
},
},
},
],
},
// Enable CSS Modules in Storybook (Shareable Runtime)
{
test: /\.module\.s(a|c)ss$/,
loader: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]',
},
},
},
{
loader: 'postcss-loader',
options: {
path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'),
},
},
{
loader: 'sass-loader',
},
],
},
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
},
// Exclude large-dependency, troublesome or irrelevant modules.
{
test: [
path.resolve(__dirname, '../public/components/embeddable_flyout'),
path.resolve(__dirname, '../../reporting/public'),
path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/angular'),
path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/paginate'),
],
use: 'null-loader',
},
],
},
plugins: [
// Reference the built DLL file of static(ish) dependencies, which are removed
// during kbn:bootstrap and rebuilt if missing.
new webpack.DllReferencePlugin({
manifest: path.resolve(DLL_OUTPUT, 'manifest.json'),
context: KIBANA_ROOT,
}),
// Ensure jQuery is global for Storybook, specifically for the runtime.
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
// Copy the DLL files to the Webpack build for use in the Storybook UI
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(DLL_OUTPUT, 'dll.js'),
to: 'dll.js',
},
{
from: path.resolve(DLL_OUTPUT, 'dll.css'),
to: 'dll.css',
},
],
}),
// replace imports for `uiExports/*` modules with a synthetic module
// created by create_ui_exports_module.js
new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => {
// uiExports used by Canvas
const extensions = {
hacks: [],
chromeNavControls: [],
};
// everything following the first / in the request is
// treated as a type of appExtension
const type = resource.request.slice(resource.request.indexOf('/') + 1);
resource.request = [
// the "val-loader" is used to execute create_ui_exports_module
// and use its return value as the source for the module in the
// bundle. This allows us to bypass writing to the file system
require.resolve('val-loader'),
'!',
require.resolve(KIBANA_ROOT + '/src/optimize/create_ui_exports_module'),
'?',
// this JSON is parsed by create_ui_exports_module and determines
// what require() calls it will execute within the bundle
JSON.stringify({ type, modules: extensions[type] || [] }),
].join('');
}),
new webpack.NormalModuleReplacementPlugin(
/lib\/download_workpad/,
path.resolve(__dirname, '../tasks/mocks/downloadWorkpad')
),
new webpack.NormalModuleReplacementPlugin(
/(lib)?\/custom_element_service/,
path.resolve(__dirname, '../tasks/mocks/customElementService')
),
new webpack.NormalModuleReplacementPlugin(
/(lib)?\/ui_metric/,
path.resolve(__dirname, '../tasks/mocks/uiMetric')
),
new webpack.NormalModuleReplacementPlugin(
/lib\/es_service/,
path.resolve(__dirname, '../tasks/mocks/esService')
),
],
resolve: {
extensions: ['.ts', '.tsx', '.scss', '.mjs', '.html'],
alias: {
'ui/url/absolute_to_parsed_url': path.resolve(
__dirname,
'../tasks/mocks/uiAbsoluteToParsedUrl'
),
},
symlinks: false,
},
};
// Find and alter the CSS rule to replace the Kibana public path string with a path
// to the route we've added in middleware.js
const cssRule = storybookConfig.module.rules.find((rule) => rule.test.source.includes('.css$'));
cssRule.use.push({
loader: 'string-replace-loader',
options: {
search: '__REPLACE_WITH_PUBLIC_PATH__',
replace: '/',
flags: 'g',
},
});
return webpackMerge(storybookConfig, config);
};

View file

@ -1,110 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { DLL_NAME, DLL_OUTPUT, KIBANA_ROOT } = require('./constants');
// This is the Webpack config for the DLL of CSS and JS assets that are
// not expected to change during development. This saves compile and run
// times considerably.
module.exports = {
context: KIBANA_ROOT,
mode: 'development',
// This is a (potentially growing) list of modules that can be safely
// included in the DLL. Only add to this list modules or other code
// which Storybook stories and their components would require, but don't
// change during development.
entry: [
'@elastic/eui/dist/eui_theme_light.css',
'@kbn/ui-framework/dist/kui_light.css',
'@storybook/addon-actions/register',
'@storybook/core',
'@storybook/core/dist/server/common/polyfills.js',
'@storybook/react',
'@storybook/theming',
'angular-mocks',
'angular',
'brace',
'chroma-js',
'highlight.js',
'html-entities',
'jsondiffpatch',
'jquery',
'lodash',
'markdown-it',
'monaco-editor',
'prop-types',
'react-ace',
'react-beautiful-dnd',
'react-dom',
'react-focus-lock',
'react-markdown',
'react-monaco-editor',
'react-resize-detector',
'react-virtualized',
'react',
'recompose',
'redux-actions',
'remark-parse',
'rxjs',
'sinon',
'tinycolor2',
// Include the DLL UI contexts from Kibana
require.resolve('./dll_contexts'),
],
plugins: [
// Produce the DLL and its manifest
new webpack.DllPlugin({
name: DLL_NAME,
path: path.resolve(DLL_OUTPUT, 'manifest.json'),
}),
// Produce the DLL CSS file
new MiniCssExtractPlugin({
filename: 'dll.css',
}),
],
// Output the DLL JS file
output: {
path: DLL_OUTPUT,
filename: 'dll.js',
library: DLL_NAME,
},
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {},
},
{ loader: 'css-loader' },
{
loader: 'string-replace-loader',
options: {
search: '__REPLACE_WITH_PUBLIC_PATH__',
replace: '/',
flags: 'g',
},
},
],
},
{
test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/,
loader: 'file-loader',
},
],
},
node: {
fs: 'empty',
child_process: 'empty',
},
};