Skip to content

Commit

Permalink
Merge pull request #19156 from storybookjs/shilman/fix-sandbox-multip…
Browse files Browse the repository at this point in the history
…le-reacts

Build: Fix sandbox running multiple versions of react
  • Loading branch information
shilman authored Sep 14, 2022
2 parents c8d8bee + ea6715a commit 05664e4
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 31 deletions.
4 changes: 3 additions & 1 deletion code/lib/builder-vite/src/list-stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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), []);
Expand Down
16 changes: 16 additions & 0 deletions code/renderers/react/template/stories/hooks.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 = {};
87 changes: 57 additions & 30 deletions scripts/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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({
Expand Down Expand Up @@ -221,7 +222,7 @@ function addEsbuildLoaderToStories(mainConfig: ConfigFile) {
...config.modules,
rules: [
{
test: [/\\/code\\/[^/]*\\/[^/]*\\/template\\/stories\\//],
test: [/\\/template-stories\\//],
loader: '${loaderPath}',
options: {
loader: 'tsx',
Expand Down Expand Up @@ -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 };
Expand All @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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: {
Expand Down
15 changes: 15 additions & 0 deletions scripts/utils/filterExistsInCodeDir.ts
Original file line number Diff line number Diff line change
@@ -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);

0 comments on commit 05664e4

Please sign in to comment.