Merge pull request #19156 from storybookjs/shilman/fix-sandbox-multiple-reacts

Build: Fix sandbox running multiple versions of react
This commit is contained in:
Michael Shilman 2022-09-14 14:07:07 +08:00 committed by GitHub
commit 05664e45b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 31 deletions

View File

@ -13,7 +13,9 @@ export async function listStories(options: Options) {
}).map(({ directory, files }) => {
const pattern = path.join(directory, files);
return glob(path.isAbsolute(pattern) ? pattern : path.join(options.configDir, pattern));
return glob(path.isAbsolute(pattern) ? pattern : path.join(options.configDir, pattern), {
follow: true,
});
})
)
).reduce((carry, stories) => carry.concat(stories), []);

View File

@ -0,0 +1,16 @@
import React, { FC, useState } from 'react';
const ButtonWithState: FC = () => {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount(count + 1)}>
{`count: ${count}`}
</button>
);
};
export default {
component: ButtonWithState,
};
export const Basic = {};

View File

@ -25,6 +25,8 @@ import { ConfigFile, readConfig, writeConfig } from '../code/lib/csf-tools';
import { babelParse } from '../code/lib/csf-tools/src/babelParse';
import TEMPLATES from '../code/lib/cli/src/repro-templates';
import { servePackages } from './utils/serve-packages';
import { filterExistsInCodeDir, codeDir } from './utils/filterExistsInCodeDir';
import { JsPackageManagerFactory } from '../code/lib/cli/src/js-package-manager';
type Template = keyof typeof TEMPLATES;
const templates: Template[] = Object.keys(TEMPLATES) as any;
@ -44,7 +46,6 @@ const defaultAddons = [
'viewport',
];
const sandboxDir = path.resolve(__dirname, '../sandbox');
const codeDir = path.resolve(__dirname, '../code');
const reprosDir = path.resolve(__dirname, '../repros');
export const options = createOptions({
@ -221,7 +222,7 @@ function addEsbuildLoaderToStories(mainConfig: ConfigFile) {
...config.modules,
rules: [
{
test: [/\\/code\\/[^/]*\\/[^/]*\\/template\\/stories\\//],
test: [/\\/template-stories\\//],
loader: '${loaderPath}',
options: {
loader: 'tsx',
@ -263,43 +264,42 @@ function addPreviewAnnotations(mainConfig: ConfigFile, paths: string[]) {
}
// paths are of the form 'renderers/react', 'addons/actions'
async function addStories(paths: string[], { mainConfig }: { mainConfig: ConfigFile }) {
// Add `stories` entries of the form
// '../../../code/lib/store/template/stories/*.stories.@(js|jsx|ts|tsx)'
async function addStories(
packageDirs: string[],
{ mainConfig, cwd }: { mainConfig: ConfigFile; cwd: string }
) {
// Link `stories` directories
// '../../../code/lib/store/template/stories' to 'src/templates/lib/store'
// if the directory <code>/lib/store/template/stories exists
const extraStoryDirsAndExistence = await Promise.all(
paths
.map((p) => path.join(p, 'template', 'stories'))
.map(async (p) => [p, await pathExists(path.resolve(codeDir, p))] as const)
//
// We link rather than reference relative dir to avoid Running two versions
// of React in react-based projects
await Promise.all(
packageDirs.map(async (p) => {
const source = path.join(codeDir, p, 'template', 'stories');
await ensureSymlink(source, path.resolve(cwd, 'template-stories', p));
})
);
const stories = mainConfig.getFieldValue(['stories']) as string[];
const extraStories = extraStoryDirsAndExistence
.filter(([, exists]) => exists)
.map(([p]) => ({
directory: path.join('..', '..', '..', 'code', p),
titlePrefix: p.split('/').slice(-4, -2).join('/'),
files: '**/*.stories.@(js|jsx|ts|tsx)',
}));
mainConfig.setFieldValue(['stories'], [...stories, ...extraStories]);
// FIXME: '*.@(mdx|stories.mdx|stories.tsx|stories.ts|stories.jsx|stories.js'
const linkedStories = path.join('..', 'template-stories', '**', '*.stories.@(js|jsx|ts|tsx|mdx)');
mainConfig.setFieldValue(['stories'], [...stories, linkedStories]);
// Add `config` entries of the form
// '../../code/lib/store/template/stories/preview.ts'
// if the file <code>/lib/store/template/stories/preview.ts exists
const extraPreviewAndExistence = await Promise.all(
extraStoryDirsAndExistence
.filter(([, exists]) => exists)
.map(([storiesPath]) => path.join(storiesPath, 'preview.ts'))
.map(
async (previewPath) =>
[previewPath, await pathExists(path.resolve(codeDir, previewPath))] as const
)
const packageDirsWithPreview = await filterExistsInCodeDir(
packageDirs,
path.join('template', 'stories', 'preview.ts')
);
const extraConfig = extraPreviewAndExistence
.filter(([, exists]) => exists)
.map(([p]) => path.join('..', '..', 'code', p));
addPreviewAnnotations(mainConfig, extraConfig);
const config = mainConfig.getFieldValue(['config']) as string[];
const extraConfig = packageDirsWithPreview.map((p) => {
const previewFile = path.join('template-stories', p, 'preview.ts');
return `./${previewFile}`;
});
mainConfig.setFieldValue(['config'], [...(config || []), ...extraConfig]);
}
type Workspace = { name: string; location: string };
@ -320,6 +320,23 @@ function workspacePath(type: string, packageName: string, workspaces: Workspace[
return workspace.location;
}
function addExtraDependencies({
cwd,
dryRun,
debug,
}: {
cwd: string;
dryRun: boolean;
debug: boolean;
}) {
const extraDeps = ['@storybook/jest'];
if (debug) console.log('🎁 Adding extra deps', extraDeps);
if (!dryRun) {
const packageManager = JsPackageManagerFactory.getPackageManager(false, cwd);
packageManager.addDependencies({ installAsDevDependencies: true }, extraDeps);
}
}
export async function sandbox(optionValues: OptionValues<typeof options>) {
const { template, forceDelete, forceReuse, dryRun, debug, fromLocalRepro } = optionValues;
@ -405,7 +422,14 @@ export async function sandbox(optionValues: OptionValues<typeof options>) {
for (const addon of [...defaultAddons, ...optionValues.addon]) {
storiesToAdd.push(workspacePath('addon', `@storybook/addon-${addon}`, workspaces));
}
await addStories(storiesToAdd, { mainConfig });
const existingStories = await filterExistsInCodeDir(
storiesToAdd,
path.join('template', 'stories')
);
await addStories(existingStories, {
mainConfig,
cwd,
});
// Add some extra settings (see above for what these do)
mainConfig.setFieldValue(['core', 'disableTelemetry'], true);
@ -451,6 +475,9 @@ export async function sandbox(optionValues: OptionValues<typeof options>) {
);
}
// Some addon stories require extra dependencies
addExtraDependencies({ cwd, dryRun, debug });
await addPackageScripts({
cwd,
scripts: {

View File

@ -0,0 +1,15 @@
import path from 'path';
import { pathExists } from 'fs-extra';
export const codeDir = path.resolve(__dirname, '../../code');
// packageDirs of the form `lib/store`
// paths to check of the form 'template/stories'
export const filterExistsInCodeDir = async (packageDirs: string[], pathToCheck: string) =>
(
await Promise.all(
packageDirs.map(async (p) =>
(await pathExists(path.resolve(codeDir, path.join(p, pathToCheck)))) ? p : null
)
)
).filter(Boolean);