mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-09 00:19:13 +08:00
Merge pull request #28798 from tobiasdiez/previewAnno
Vite: Improve handling of preview annotations
This commit is contained in:
commit
bc4d3146fa
@ -130,9 +130,6 @@ const ThemedSetRoot = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const preview = (window as any).__STORYBOOK_PREVIEW__ as PreviewWeb<ReactRenderer> | undefined;
|
||||
const channel = (window as any).__STORYBOOK_ADDONS_CHANNEL__ as Channel | undefined;
|
||||
const loaders = [
|
||||
/**
|
||||
* This loader adds a DocsContext to the story, which is required for the most Blocks to work. A
|
||||
@ -147,6 +144,9 @@ const loaders = [
|
||||
* The DocsContext will then be added via the decorator below.
|
||||
*/
|
||||
async ({ parameters: { relativeCsfPaths, attached = true } }) => {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const preview = (window as any).__STORYBOOK_PREVIEW__ as PreviewWeb<ReactRenderer> | undefined;
|
||||
const channel = (window as any).__STORYBOOK_ADDONS_CHANNEL__ as Channel | undefined;
|
||||
// __STORYBOOK_PREVIEW__ and __STORYBOOK_ADDONS_CHANNEL__ is set in the PreviewWeb constructor
|
||||
// which isn't loaded in portable stories/vitest
|
||||
if (!relativeCsfPaths || !preview || !channel) {
|
||||
|
@ -0,0 +1,181 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { generateModernIframeScriptCodeFromPreviews } from './codegen-modern-iframe-script';
|
||||
|
||||
const projectRoot = 'projectRoot';
|
||||
|
||||
describe('generateModernIframeScriptCodeFromPreviews', () => {
|
||||
it('handle one annotation', async () => {
|
||||
const result = await generateModernIframeScriptCodeFromPreviews({
|
||||
previewAnnotations: ['/user/.storybook/preview'],
|
||||
projectRoot,
|
||||
frameworkName: 'frameworkName',
|
||||
isCsf4: false,
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"import { setup } from 'storybook/internal/preview/runtime';
|
||||
|
||||
import 'virtual:/@storybook/builder-vite/setup-addons.js';
|
||||
|
||||
setup();
|
||||
|
||||
import { composeConfigs, PreviewWeb } from 'storybook/internal/preview-api';
|
||||
import { isPreview } from 'storybook/internal/csf';
|
||||
import { importFn } from 'virtual:/@storybook/builder-vite/storybook-stories.js';
|
||||
|
||||
import * as preview_2408 from "/user/.storybook/preview";
|
||||
const getProjectAnnotations = (hmrPreviewAnnotationModules = []) => {
|
||||
const configs = [
|
||||
hmrPreviewAnnotationModules[0] ?? preview_2408
|
||||
]
|
||||
return composeConfigs(configs);
|
||||
}
|
||||
|
||||
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
|
||||
|
||||
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('virtual:/@storybook/builder-vite/storybook-stories.js', (newModule) => {
|
||||
// importFn has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onStoriesChanged({ importFn: newModule.importFn });
|
||||
});
|
||||
|
||||
import.meta.hot.accept(["/user/.storybook/preview"], (previewAnnotationModules) => {
|
||||
// getProjectAnnotations has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onGetProjectAnnotationsChanged({ getProjectAnnotations: () => getProjectAnnotations(previewAnnotationModules) });
|
||||
});
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
it('handle one annotation CSF4', async () => {
|
||||
const result = await generateModernIframeScriptCodeFromPreviews({
|
||||
previewAnnotations: ['/user/.storybook/preview'],
|
||||
projectRoot,
|
||||
frameworkName: 'frameworkName',
|
||||
isCsf4: true,
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"import { setup } from 'storybook/internal/preview/runtime';
|
||||
|
||||
import 'virtual:/@storybook/builder-vite/setup-addons.js';
|
||||
|
||||
setup();
|
||||
|
||||
import { composeConfigs, PreviewWeb } from 'storybook/internal/preview-api';
|
||||
import { isPreview } from 'storybook/internal/csf';
|
||||
import { importFn } from 'virtual:/@storybook/builder-vite/storybook-stories.js';
|
||||
|
||||
import * as preview_2408 from "/user/.storybook/preview";
|
||||
const getProjectAnnotations = (hmrPreviewAnnotationModules = []) => {
|
||||
const preview = hmrPreviewAnnotationModules[0] ?? preview_2408;
|
||||
return preview.default.composed;
|
||||
}
|
||||
|
||||
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
|
||||
|
||||
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('virtual:/@storybook/builder-vite/storybook-stories.js', (newModule) => {
|
||||
// importFn has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onStoriesChanged({ importFn: newModule.importFn });
|
||||
});
|
||||
|
||||
import.meta.hot.accept(["/user/.storybook/preview"], (previewAnnotationModules) => {
|
||||
// getProjectAnnotations has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onGetProjectAnnotationsChanged({ getProjectAnnotations: () => getProjectAnnotations(previewAnnotationModules) });
|
||||
});
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
it('handle multiple annotations', async () => {
|
||||
const result = await generateModernIframeScriptCodeFromPreviews({
|
||||
previewAnnotations: ['/user/previewAnnotations1', '/user/.storybook/preview'],
|
||||
projectRoot,
|
||||
frameworkName: 'frameworkName',
|
||||
isCsf4: false,
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"import { setup } from 'storybook/internal/preview/runtime';
|
||||
|
||||
import 'virtual:/@storybook/builder-vite/setup-addons.js';
|
||||
|
||||
setup();
|
||||
|
||||
import { composeConfigs, PreviewWeb } from 'storybook/internal/preview-api';
|
||||
import { isPreview } from 'storybook/internal/csf';
|
||||
import { importFn } from 'virtual:/@storybook/builder-vite/storybook-stories.js';
|
||||
|
||||
import * as previewAnnotations1_2526 from "/user/previewAnnotations1";
|
||||
import * as preview_2408 from "/user/.storybook/preview";
|
||||
const getProjectAnnotations = (hmrPreviewAnnotationModules = []) => {
|
||||
const configs = [
|
||||
hmrPreviewAnnotationModules[0] ?? previewAnnotations1_2526,
|
||||
hmrPreviewAnnotationModules[1] ?? preview_2408
|
||||
]
|
||||
return composeConfigs(configs);
|
||||
}
|
||||
|
||||
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
|
||||
|
||||
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('virtual:/@storybook/builder-vite/storybook-stories.js', (newModule) => {
|
||||
// importFn has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onStoriesChanged({ importFn: newModule.importFn });
|
||||
});
|
||||
|
||||
import.meta.hot.accept(["/user/previewAnnotations1","/user/.storybook/preview"], (previewAnnotationModules) => {
|
||||
// getProjectAnnotations has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onGetProjectAnnotationsChanged({ getProjectAnnotations: () => getProjectAnnotations(previewAnnotationModules) });
|
||||
});
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
it('handle multiple annotations CSF4', async () => {
|
||||
const result = await generateModernIframeScriptCodeFromPreviews({
|
||||
previewAnnotations: ['/user/previewAnnotations1', '/user/.storybook/preview'],
|
||||
projectRoot,
|
||||
frameworkName: 'frameworkName',
|
||||
isCsf4: true,
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"import { setup } from 'storybook/internal/preview/runtime';
|
||||
|
||||
import 'virtual:/@storybook/builder-vite/setup-addons.js';
|
||||
|
||||
setup();
|
||||
|
||||
import { composeConfigs, PreviewWeb } from 'storybook/internal/preview-api';
|
||||
import { isPreview } from 'storybook/internal/csf';
|
||||
import { importFn } from 'virtual:/@storybook/builder-vite/storybook-stories.js';
|
||||
|
||||
import * as preview_2408 from "/user/.storybook/preview";
|
||||
const getProjectAnnotations = (hmrPreviewAnnotationModules = []) => {
|
||||
const preview = hmrPreviewAnnotationModules[0] ?? preview_2408;
|
||||
return preview.default.composed;
|
||||
}
|
||||
|
||||
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
|
||||
|
||||
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('virtual:/@storybook/builder-vite/storybook-stories.js', (newModule) => {
|
||||
// importFn has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onStoriesChanged({ importFn: newModule.importFn });
|
||||
});
|
||||
|
||||
import.meta.hot.accept(["/user/.storybook/preview"], (previewAnnotationModules) => {
|
||||
// getProjectAnnotations has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onGetProjectAnnotationsChanged({ getProjectAnnotations: () => getProjectAnnotations(previewAnnotationModules) });
|
||||
});
|
||||
};"
|
||||
`);
|
||||
});
|
||||
});
|
@ -1,6 +1,9 @@
|
||||
import { getFrameworkName, loadPreviewOrConfigFile } from 'storybook/internal/common';
|
||||
import { isCsfFactoryPreview, readConfig } from 'storybook/internal/csf-tools';
|
||||
import type { Options, PreviewAnnotation } from 'storybook/internal/types';
|
||||
|
||||
import { genArrayFromRaw, genImport, genSafeVariableName } from 'knitwork';
|
||||
import { filename } from 'pathe/utils';
|
||||
import { dedent } from 'ts-dedent';
|
||||
|
||||
import { processPreviewAnnotation } from './utils/process-preview-annotation';
|
||||
@ -11,38 +14,72 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
|
||||
const frameworkName = await getFrameworkName(options);
|
||||
|
||||
const previewOrConfigFile = loadPreviewOrConfigFile({ configDir });
|
||||
const previewConfig = await readConfig(previewOrConfigFile!);
|
||||
const isCsf4 = isCsfFactoryPreview(previewConfig);
|
||||
|
||||
const previewAnnotations = await presets.apply<PreviewAnnotation[]>(
|
||||
'previewAnnotations',
|
||||
[],
|
||||
options
|
||||
);
|
||||
const [previewFileUrl, ...previewAnnotationURLs] = [previewOrConfigFile, ...previewAnnotations]
|
||||
.filter(Boolean)
|
||||
return generateModernIframeScriptCodeFromPreviews({
|
||||
previewAnnotations: [...previewAnnotations, previewOrConfigFile],
|
||||
projectRoot,
|
||||
frameworkName,
|
||||
isCsf4,
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateModernIframeScriptCodeFromPreviews(options: {
|
||||
previewAnnotations: (PreviewAnnotation | undefined)[];
|
||||
projectRoot: string;
|
||||
frameworkName: string;
|
||||
isCsf4: boolean;
|
||||
}) {
|
||||
const { projectRoot, frameworkName } = options;
|
||||
const previewAnnotationURLs = options.previewAnnotations
|
||||
.filter((path) => path !== undefined)
|
||||
.map((path) => processPreviewAnnotation(path, projectRoot));
|
||||
|
||||
const variables: string[] = [];
|
||||
const imports: string[] = [];
|
||||
for (const previewAnnotation of previewAnnotationURLs) {
|
||||
const variable =
|
||||
genSafeVariableName(filename(previewAnnotation)).replace(/_(45|46|47)/g, '_') +
|
||||
'_' +
|
||||
hash(previewAnnotation);
|
||||
variables.push(variable);
|
||||
imports.push(genImport(previewAnnotation, { name: '*', as: variable }));
|
||||
}
|
||||
|
||||
const previewFileURL = previewAnnotationURLs[previewAnnotationURLs.length - 1];
|
||||
const previewFileVariable = variables[variables.length - 1];
|
||||
const previewFileImport = imports[imports.length - 1];
|
||||
|
||||
// This is pulled out to a variable because it is reused in both the initial page load
|
||||
// and the HMR handler. We don't use the hot.accept callback params because only the changed
|
||||
// modules are provided, the rest are null. We can just re-import everything again in that case.
|
||||
const getPreviewAnnotationsFunction = dedent`
|
||||
const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => {
|
||||
const preview = await import('${previewFileUrl}');
|
||||
|
||||
if (isPreview(preview.default)) {
|
||||
return preview.default.composed;
|
||||
}
|
||||
|
||||
const configs = await Promise.all([${previewAnnotationURLs
|
||||
.map(
|
||||
// and the HMR handler.
|
||||
// The `hmrPreviewAnnotationModules` parameter is used to pass the updated modules from HMR.
|
||||
// However, only the changed modules are provided, the rest are null.
|
||||
const getPreviewAnnotationsFunction = options.isCsf4
|
||||
? dedent`
|
||||
const getProjectAnnotations = (hmrPreviewAnnotationModules = []) => {
|
||||
const preview = hmrPreviewAnnotationModules[0] ?? ${previewFileVariable};
|
||||
return preview.default.composed;
|
||||
}`
|
||||
: dedent`
|
||||
const getProjectAnnotations = (hmrPreviewAnnotationModules = []) => {
|
||||
const configs = ${genArrayFromRaw(
|
||||
variables.map(
|
||||
(previewAnnotation, index) =>
|
||||
// Prefer the updated module from an HMR update, otherwise import the original module
|
||||
`hmrPreviewAnnotationModules[${index}] ?? import('${previewAnnotation}')`
|
||||
)
|
||||
.join(',\n')}])
|
||||
return composeConfigs([...configs, preview]);
|
||||
// Prefer the updated module from an HMR update, otherwise the original module
|
||||
`hmrPreviewAnnotationModules[${index}] ?? ${previewAnnotation}`
|
||||
),
|
||||
' '
|
||||
)}
|
||||
return composeConfigs(configs);
|
||||
}`;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const generateHMRHandler = (frameworkName: string): string => {
|
||||
const generateHMRHandler = (): string => {
|
||||
// Web components are not compatible with HMR, so disable HMR, reload page instead.
|
||||
if (frameworkName === '@storybook/web-components-vite') {
|
||||
return dedent`
|
||||
@ -58,8 +95,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
|
||||
window.__STORYBOOK_PREVIEW__.onStoriesChanged({ importFn: newModule.importFn });
|
||||
});
|
||||
|
||||
import.meta.hot.accept(${JSON.stringify(previewAnnotationURLs)}, (previewAnnotationModules) => {
|
||||
${getPreviewAnnotationsFunction}
|
||||
import.meta.hot.accept(${JSON.stringify(options.isCsf4 ? [previewFileURL] : previewAnnotationURLs)}, (previewAnnotationModules) => {
|
||||
// getProjectAnnotations has changed so we need to patch the new one in
|
||||
window.__STORYBOOK_PREVIEW__.onGetProjectAnnotationsChanged({ getProjectAnnotations: () => getProjectAnnotations(previewAnnotationModules) });
|
||||
});
|
||||
@ -76,6 +112,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
|
||||
*/
|
||||
const code = dedent`
|
||||
import { setup } from 'storybook/internal/preview/runtime';
|
||||
|
||||
import '${SB_VIRTUAL_FILES.VIRTUAL_ADDON_SETUP_FILE}';
|
||||
|
||||
setup();
|
||||
@ -84,12 +121,17 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
|
||||
import { isPreview } from 'storybook/internal/csf';
|
||||
import { importFn } from '${SB_VIRTUAL_FILES.VIRTUAL_STORIES_FILE}';
|
||||
|
||||
${options.isCsf4 ? previewFileImport : imports.join('\n')}
|
||||
${getPreviewAnnotationsFunction}
|
||||
|
||||
window.__STORYBOOK_PREVIEW__ = window.__STORYBOOK_PREVIEW__ || new PreviewWeb(importFn, getProjectAnnotations);
|
||||
|
||||
window.__STORYBOOK_STORY_STORE__ = window.__STORYBOOK_STORY_STORE__ || window.__STORYBOOK_PREVIEW__.storyStore;
|
||||
|
||||
${generateHMRHandler(frameworkName)};`.trim();
|
||||
|
||||
${generateHMRHandler()};
|
||||
`.trim();
|
||||
return code;
|
||||
}
|
||||
function hash(value: string) {
|
||||
return value.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||
}
|
||||
|
@ -125,6 +125,7 @@ const INCLUDE_CANDIDATES = [
|
||||
'qs',
|
||||
'react-dom',
|
||||
'react-dom/client',
|
||||
'react-dom/test-utils',
|
||||
'react-fast-compare',
|
||||
'react-is',
|
||||
'react-textarea-autosize',
|
||||
@ -144,6 +145,7 @@ const INCLUDE_CANDIDATES = [
|
||||
'refractor/lang/typescript.js',
|
||||
'refractor/lang/yaml.js',
|
||||
'regenerator-runtime/runtime.js',
|
||||
'semver', // TODO: Remove once https://github.com/npm/node-semver/issues/712 is fixed
|
||||
'sb-original/default-loader',
|
||||
'sb-original/image-context',
|
||||
'slash',
|
||||
|
@ -4,64 +4,71 @@ import { onlyWindows, skipWindows } from '../../../../vitest.helpers';
|
||||
import { processPreviewAnnotation } from './process-preview-annotation';
|
||||
|
||||
describe('processPreviewAnnotation()', () => {
|
||||
it('should pull the `bare` value from an object', () => {
|
||||
it('should pull the `absolute` value from an object', () => {
|
||||
const annotation = {
|
||||
bare: '@storybook/addon-links/preview',
|
||||
absolute: '/Users/foo/storybook/node_modules/@storybook/addon-links/dist/preview.mjs',
|
||||
};
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('@storybook/addon-links/preview');
|
||||
expect(url).toBe('/Users/foo/storybook/node_modules/@storybook/addon-links/dist/preview.mjs');
|
||||
});
|
||||
|
||||
it('should convert relative paths into urls', () => {
|
||||
const annotation = './src/stories/components';
|
||||
it('should convert relative paths into absolute paths', () => {
|
||||
const annotation = './src/stories/preview';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('/src/stories/components');
|
||||
expect(url).toBe('/Users/foo/storybook/src/stories/preview');
|
||||
});
|
||||
|
||||
skipWindows(() => {
|
||||
it('should convert absolute filesystem paths into urls relative to project root', () => {
|
||||
const annotation = '/Users/foo/storybook/.storybook/preview.js';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('/.storybook/preview.js');
|
||||
});
|
||||
|
||||
// TODO: figure out why this fails on windows. Could be related to storybook-metadata.test file altering path.sep
|
||||
it('should convert node_modules into bare paths', () => {
|
||||
const annotation = '/Users/foo/storybook/node_modules/storybook-addon/preview';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('storybook-addon/preview');
|
||||
});
|
||||
|
||||
it('should convert relative paths outside the root into absolute', () => {
|
||||
const annotation = '../parent.js';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('/Users/foo/parent.js');
|
||||
});
|
||||
|
||||
it('should not change absolute paths outside of the project root', () => {
|
||||
const annotation = '/Users/foo/parent.js';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe(annotation);
|
||||
});
|
||||
it('should keep absolute filesystem paths', () => {
|
||||
const annotation = '/Users/foo/storybook/.storybook/preview.js';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('/Users/foo/storybook/.storybook/preview.js');
|
||||
});
|
||||
|
||||
onlyWindows(() => {
|
||||
it('should convert absolute windows filesystem paths into urls relative to project root', () => {
|
||||
const annotation = 'C:/foo/storybook/.storybook/preview.js';
|
||||
const url = processPreviewAnnotation(annotation, 'C:/foo/storybook');
|
||||
expect(url).toBe('/.storybook/preview.js');
|
||||
});
|
||||
it('should convert relative paths outside the root into absolute on Windows', () => {
|
||||
const annotation = '../parent.js';
|
||||
const url = processPreviewAnnotation(annotation, 'C:/Users/foo/storybook/');
|
||||
expect(url).toBe('C:/Users/foo/parent.js');
|
||||
});
|
||||
it('should keep absolute node_modules paths', () => {
|
||||
const annotation = '/Users/foo/storybook/node_modules/storybook-addon/preview';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('/Users/foo/storybook/node_modules/storybook-addon/preview');
|
||||
});
|
||||
|
||||
it('should not change Windows absolute paths outside of the project root', () => {
|
||||
const annotation = 'D:/Users/foo/parent.js';
|
||||
const url = processPreviewAnnotation(annotation, 'D:/Users/foo/storybook/');
|
||||
expect(url).toBe(annotation);
|
||||
});
|
||||
it('should convert relative paths outside the root into absolute', () => {
|
||||
const annotation = '../parent.js';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe('/Users/foo/parent.js');
|
||||
});
|
||||
|
||||
it('should not change absolute paths outside of the project root', () => {
|
||||
const annotation = '/Users/foo/parent.js';
|
||||
const url = processPreviewAnnotation(annotation, '/Users/foo/storybook/');
|
||||
expect(url).toBe(annotation);
|
||||
});
|
||||
|
||||
it('should keep absolute windows filesystem paths as is', () => {
|
||||
const annotation = 'C:/foo/storybook/.storybook/preview.js';
|
||||
const url = processPreviewAnnotation(annotation, 'C:/foo/storybook');
|
||||
expect(url).toBe('C:/foo/storybook/.storybook/preview.js');
|
||||
});
|
||||
it('should convert relative paths outside the root into absolute on Windows', () => {
|
||||
const annotation = '../parent.js';
|
||||
const url = processPreviewAnnotation(annotation, 'C:/Users/foo/storybook/');
|
||||
expect(url).toBe('C:/Users/foo/parent.js');
|
||||
});
|
||||
|
||||
it('should not change Windows absolute paths outside of the project root', () => {
|
||||
const annotation = 'D:/Users/foo/parent.js';
|
||||
const url = processPreviewAnnotation(annotation, 'D:/Users/foo/storybook/');
|
||||
expect(url).toBe(annotation);
|
||||
});
|
||||
|
||||
it('should normalize absolute Windows paths using \\', () => {
|
||||
const annotation = 'C:\\foo\\storybook\\.storybook\\preview.js';
|
||||
const url = processPreviewAnnotation(annotation, 'C:\\foo\\storybook');
|
||||
expect(url).toBe('C:/foo/storybook/.storybook/preview.js');
|
||||
});
|
||||
|
||||
it('should normalize relative Windows paths using \\', () => {
|
||||
const annotation = '.\\src\\stories\\preview';
|
||||
const url = processPreviewAnnotation(annotation, 'C:\\foo\\storybook');
|
||||
expect(url).toBe('C:/foo/storybook/src/stories/preview');
|
||||
});
|
||||
});
|
||||
|
@ -1,54 +1,25 @@
|
||||
import { isAbsolute, relative, resolve } from 'node:path';
|
||||
|
||||
import { stripAbsNodeModulesPath } from 'storybook/internal/common';
|
||||
import type { PreviewAnnotation } from 'storybook/internal/types';
|
||||
|
||||
import slash from 'slash';
|
||||
import { isAbsolute, normalize, resolve } from 'pathe';
|
||||
|
||||
/**
|
||||
* Preview annotations can take several forms, and vite needs them to be a bit more restrained.
|
||||
*
|
||||
* For node_modules, we want bare imports (so vite can process them), and for files in the user's
|
||||
* source, we want URLs absolute relative to project root.
|
||||
*/
|
||||
export function processPreviewAnnotation(path: PreviewAnnotation | undefined, projectRoot: string) {
|
||||
// If entry is an object, take the first, which is the
|
||||
// bare (non-absolute) specifier.
|
||||
// This is so that webpack can use an absolute path, and
|
||||
// continue supporting super-addons in pnp/pnpm without
|
||||
// requiring them to re-export their sub-addons as we do
|
||||
// in addon-essentials.
|
||||
/** Preview annotations can take several forms, so we normalize them here to absolute file paths. */
|
||||
export function processPreviewAnnotation(path: PreviewAnnotation, projectRoot: string) {
|
||||
// If entry is an object, take the absolute specifier.
|
||||
// This absolute specifier is automatically made for addons here:
|
||||
// https://github.com/storybookjs/storybook/blob/ac6e73b9d8ce31dd9acc80999c8d7c22a111f3cc/code/core/src/common/presets.ts#L161-L171
|
||||
if (typeof path === 'object') {
|
||||
return path.bare;
|
||||
// TODO: Remove this once the new version of Nuxt is released that removes this workaround:
|
||||
// https://github.com/nuxt-modules/storybook/blob/a2eec6e898386f76c74826842e8e007b185c3d35/packages/storybook-addon/src/preset.ts#L279-L306
|
||||
if (path.bare != null && path.absolute === '') {
|
||||
return path.bare;
|
||||
}
|
||||
return path.absolute;
|
||||
}
|
||||
|
||||
// This should not occur, since we use `.filter(Boolean)` prior to
|
||||
// calling this function, but this makes typescript happy
|
||||
if (!path) {
|
||||
throw new Error('Could not determine path for previewAnnotation');
|
||||
// If it's already an absolute path, return it.
|
||||
if (isAbsolute(path)) {
|
||||
return normalize(path);
|
||||
}
|
||||
|
||||
// For addon dependencies that use require.resolve(), we need to convert to a bare path
|
||||
// so that vite will process it as a dependency (cjs -> esm, etc).
|
||||
// TODO: Evaluate if searching for node_modules in a yarn pnp environment is correct
|
||||
if (path.includes('node_modules')) {
|
||||
return stripAbsNodeModulesPath(path);
|
||||
}
|
||||
|
||||
// resolve absolute paths relative to project root
|
||||
const relativePath = isAbsolute(path) ? slash(relative(projectRoot, path)) : path;
|
||||
|
||||
// resolve relative paths into absolute urls
|
||||
// note: this only works if vite's projectRoot === cwd.
|
||||
if (relativePath.startsWith('./')) {
|
||||
return slash(relativePath.replace(/^\.\//, '/'));
|
||||
}
|
||||
|
||||
// If something is outside of root, convert to absolute. Uncommon?
|
||||
if (relativePath.startsWith('../')) {
|
||||
return slash(resolve(projectRoot, relativePath));
|
||||
}
|
||||
|
||||
// At this point, it must be relative to the root but not start with a ./ or ../
|
||||
return slash(`/${relativePath}`);
|
||||
// resolve relative paths, relative to project root
|
||||
return normalize(resolve(projectRoot, path));
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ export const getVirtualModules = async (options: Options) => {
|
||||
// If entry is an object, use the absolute import specifier.
|
||||
// This is to maintain back-compat with community addons that bundle other addons
|
||||
// and package managers that "hide" sub dependencies (e.g. pnpm / yarn pnp)
|
||||
// The vite builder uses the bare import specifier.
|
||||
if (typeof entry === 'object') {
|
||||
return entry.absolute;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user