Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Support recursive addon imports #19530

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 61 additions & 32 deletions code/addons/essentials/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path, { dirname, join } from 'path';
import { logger } from '@storybook/node-logger';
import { serverRequire } from '@storybook/core-common';
import { serverRequire, resolveAddonName, interopRequireDefault } from '@storybook/core-common';

interface PresetOptions {
configDir: string;
Expand Down Expand Up @@ -30,41 +30,70 @@ export function addons(options: PresetOptions) {
return name?.startsWith(addon);
});
if (existingAddon) {
logger.info(`Found existing addon ${JSON.stringify(existingAddon)}, skipping.`);
logger.info(
`[addon-essentials] Found existing addon ${JSON.stringify(existingAddon)}, skipping.`
);
}
return !!existingAddon;
};

const main = requireMain(options.configDir);
return (
[
'docs',
'controls',
'actions',
'backgrounds',
'viewport',
'toolbars',
'measure',
'outline',
'highlight',
]
.filter((key) => (options as any)[key] !== false)
.map((key) => `@storybook/addon-${key}`)
.filter((addon) => !checkInstalled(addon, main))
// Use `require.resolve` to ensure Yarn PnP compatibility
// Files of various addons should be resolved in the context of `addon-essentials` as they are listed as deps here
// and not in `@storybook/core` nor in SB user projects. If `@storybook/core` make the require itself Yarn 2 will
// throw an error saying that the package to require must be added as a dependency. Doing `require.resolve` will
// allow `@storybook/core` to work with absolute path directly, no more require of dep no more issue.
// File to load can be `preset.js`, `register.js`, or the package entry point, so we need to check all these cases
// as it's done in `lib/core/src/server/presets.js`.
.map((addon) => {
try {
return dirname(require.resolve(join(addon, 'package.json')));
// eslint-disable-next-line no-empty
} catch (err) {}
const addonNames = [
'docs',
'controls',
'actions',
'backgrounds',
'viewport',
'toolbars',
'measure',
'outline',
'highlight',
]
.filter((key) => (options as any)[key] !== false)
.map((key) => `@storybook/addon-${key}`)
.filter((addon) => !checkInstalled(addon, main));
// Use require() to ensure Yarn PnP and pnpm compatibility
// Files of various addons should be imported in the context of `addon-essentials` as they are listed as deps here
// and not in `@storybook/core` nor in SB user projects. If `@storybook/core` make the require itself Yarn 2/pnpm will
// throw an error saying that the package to require must be added as a dependency.

return require.resolve(addon);
})
);
return addonNames.map((addon) => {
const name = resolveAddonName(options.configDir, addon, {});
const parts = getContent(name);
return parts;

try {
return dirname(require.resolve(join(addon, 'package.json')));
// eslint-disable-next-line no-empty
} catch (err) {}

return require.resolve(addon);
});
}

// copied from core-common and adjusted to create a `content` type
function getContent(input: any) {
if (input.type === 'virtual') {
const { type, name, ...rest } = input;

const content: Record<string, any> = {
name,
type: 'content',
};
if (rest.managerEntries) {
content.managerEntries = rest.managerEntries;
}
if (rest.previewAnnotations) {
content.previewAnnotations = rest.previewAnnotations.map((name: string) =>
interopRequireDefault(name)
);
}
if (rest.presets) {
content.presets = rest.presets;
}
return content;
}
const name = input.name ? input.name : input;

return interopRequireDefault(name);
}
13 changes: 8 additions & 5 deletions code/lib/core-common/src/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function resolvePresetFunction<T = any>(
* => { type: 'presets', item }
*
* - '@storybook/addon-docs'
* => { type: 'presets', item: '@storybook/addon-docs/preset' }
* => { type: 'presets', name: '/Users/me/project/node_modules/@storybook/addon-docs/dist/index.js' }
*
* - { name: '@storybook/addon-docs(/preset)?', options: { ... } }
* => { type: 'presets', item: { name: '@storybook/addon-docs/preset', options } }
Expand Down Expand Up @@ -97,7 +97,7 @@ export const resolveAddonName = (
// (vite cannot import absolute files: https://github.com/vitejs/vite/issues/5494
// this also means vite suffers issues with pnpm etc)
const absolutizeExport = (exportName: string) => {
if (resolve(`${name}${exportName}`)) return `${absoluteDir}${exportName}`;
if (resolve(`${name}${exportName}`)) return `${name}${exportName}`;
return undefined;
};

Expand Down Expand Up @@ -169,11 +169,11 @@ const map =
};

async function getContent(input: any) {
if (input.type === 'virtual') {
if (input.type === 'virtual' || input.type === 'content') {
const { type, name, ...rest } = input;
return rest;
}
const name = input.name ? input.name : input;
const name = input.name ?? input;

return interopRequireDefault(name);
}
Expand Down Expand Up @@ -206,14 +206,17 @@ export async function loadPreset(

const subPresets = resolvePresetFunction(presetsInput, presetOptions, storybookOptions);
const subAddons = resolvePresetFunction(addonsInput, presetOptions, storybookOptions);
const stringSubAddons = subAddons.filter((a) => typeof a === 'string');
const objectSubAddons = subAddons.filter((a) => typeof a === 'object');

return [
...(await loadPresets([...subPresets], level + 1, storybookOptions)),
...(await loadPresets(
[...subAddons.map(map(storybookOptions))].filter(Boolean) as PresetConfig[],
[...stringSubAddons.map(map(storybookOptions))].filter(Boolean) as PresetConfig[],
level + 1,
storybookOptions
)),
...(await loadPresets([...objectSubAddons], level + 1, storybookOptions)),
{
name,
preset: rest,
Expand Down