mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-02 05:03:44 +08:00
initial check0in
This commit is contained in:
parent
4a5508dfec
commit
554b379bfa
@ -24,15 +24,19 @@
|
||||
"*.d.ts"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"prepare": "node ../../scripts/prepare.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addons": "5.3.5",
|
||||
"@storybook/api": "5.3.5",
|
||||
"@storybook/components": "5.3.5",
|
||||
"@storybook/core-events": "5.3.5",
|
||||
"@storybook/router": "5.3.5",
|
||||
"@storybook/source-loader": "5.3.5",
|
||||
"@storybook/theming": "5.3.5",
|
||||
"@types/react-syntax-highlighter": "11.0.2",
|
||||
"core-js": "^3.0.1",
|
||||
"estraverse": "^4.2.0",
|
||||
"loader-utils": "^1.2.3",
|
||||
@ -42,6 +46,9 @@
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-syntax-highlighter": "^11.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@storybook/source-loader": "*",
|
||||
"react": "*"
|
||||
|
@ -1,188 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Link } from '@storybook/router';
|
||||
import { SyntaxHighlighter } from '@storybook/components';
|
||||
|
||||
import createElement from 'react-syntax-highlighter/dist/esm/create-element';
|
||||
import { EVENT_ID } from './events';
|
||||
|
||||
const StyledStoryLink = styled(Link)(({ theme }) => ({
|
||||
display: 'block',
|
||||
textDecoration: 'none',
|
||||
borderRadius: theme.appBorderRadius,
|
||||
color: 'inherit',
|
||||
|
||||
'&:hover': {
|
||||
background: theme.background.hoverable,
|
||||
},
|
||||
}));
|
||||
|
||||
const SelectedStoryHighlight = styled.div(({ theme }) => ({
|
||||
background: theme.background.hoverable,
|
||||
borderRadius: theme.appBorderRadius,
|
||||
}));
|
||||
|
||||
const StyledSyntaxHighlighter = styled(SyntaxHighlighter)(({ theme }) => ({
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
}));
|
||||
|
||||
const areLocationsEqual = (a, b) =>
|
||||
a.startLoc.line === b.startLoc.line &&
|
||||
a.startLoc.col === b.startLoc.col &&
|
||||
a.endLoc.line === b.endLoc.line &&
|
||||
a.endLoc.col === b.endLoc.col;
|
||||
|
||||
const getLocationKeys = locationsMap =>
|
||||
locationsMap
|
||||
? Array.from(Object.keys(locationsMap)).sort(
|
||||
(key1, key2) => locationsMap[key1].startLoc.line - locationsMap[key2].startLoc.line
|
||||
)
|
||||
: [];
|
||||
|
||||
export default class StoryPanel extends Component {
|
||||
state = { source: 'loading source...' };
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
const { api } = this.props;
|
||||
|
||||
api.on(EVENT_ID, this.listener);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.selectedStoryRef) {
|
||||
this.selectedStoryRef.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { api } = this.props;
|
||||
|
||||
api.off(EVENT_ID, this.listener);
|
||||
}
|
||||
|
||||
setSelectedStoryRef = ref => {
|
||||
this.selectedStoryRef = ref;
|
||||
};
|
||||
|
||||
listener = ({ edition: { source }, location: { currentLocation, locationsMap } }) => {
|
||||
const locationsKeys = getLocationKeys(locationsMap);
|
||||
this.setState({
|
||||
source,
|
||||
currentLocation,
|
||||
locationsMap,
|
||||
locationsKeys,
|
||||
});
|
||||
};
|
||||
|
||||
createPart = (rows, stylesheet, useInlineStyles) =>
|
||||
rows.map((node, i) =>
|
||||
createElement({
|
||||
node,
|
||||
stylesheet,
|
||||
useInlineStyles,
|
||||
key: `code-segement${i}`,
|
||||
})
|
||||
);
|
||||
|
||||
createStoryPart = (rows, stylesheet, useInlineStyles, location, id) => {
|
||||
const { currentLocation } = this.state;
|
||||
const first = location.startLoc.line - 1;
|
||||
const last = location.endLoc.line;
|
||||
|
||||
const storyRows = rows.slice(first, last);
|
||||
const story = this.createPart(storyRows, stylesheet, useInlineStyles);
|
||||
const storyKey = `${first}-${last}`;
|
||||
|
||||
if (location && currentLocation && areLocationsEqual(location, currentLocation)) {
|
||||
return (
|
||||
<SelectedStoryHighlight key={storyKey} ref={this.setSelectedStoryRef}>
|
||||
{story}
|
||||
</SelectedStoryHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledStoryLink to={`/story/${id}`} key={storyKey}>
|
||||
{story}
|
||||
</StyledStoryLink>
|
||||
);
|
||||
};
|
||||
|
||||
createParts = (rows, stylesheet, useInlineStyles) => {
|
||||
const { locationsMap, locationsKeys } = this.state;
|
||||
|
||||
const parts = [];
|
||||
let lastRow = 0;
|
||||
|
||||
locationsKeys.forEach(key => {
|
||||
const location = locationsMap[key];
|
||||
const first = location.startLoc.line - 1;
|
||||
const last = location.endLoc.line;
|
||||
|
||||
const start = this.createPart(rows.slice(lastRow, first), stylesheet, useInlineStyles);
|
||||
const storyPart = this.createStoryPart(rows, stylesheet, useInlineStyles, location, key);
|
||||
|
||||
parts.push(start);
|
||||
parts.push(storyPart);
|
||||
|
||||
lastRow = last;
|
||||
});
|
||||
|
||||
const lastPart = this.createPart(rows.slice(lastRow), stylesheet, useInlineStyles);
|
||||
|
||||
parts.push(lastPart);
|
||||
|
||||
return parts;
|
||||
};
|
||||
|
||||
lineRenderer = ({ rows, stylesheet, useInlineStyles }) => {
|
||||
const { locationsMap, locationsKeys } = this.state;
|
||||
|
||||
// because of the usage of lineRenderer, all lines will be wrapped in a span
|
||||
// these spans will receive all classes on them for some reason
|
||||
// which makes colours casecade incorrectly
|
||||
// this removed that list of classnames
|
||||
const myrows = rows.map(({ properties, ...rest }) => ({
|
||||
...rest,
|
||||
properties: { className: [] },
|
||||
}));
|
||||
|
||||
if (!locationsMap || !locationsKeys.length) {
|
||||
return this.createPart(myrows, stylesheet, useInlineStyles);
|
||||
}
|
||||
|
||||
const parts = this.createParts(myrows, stylesheet, useInlineStyles);
|
||||
|
||||
return <span>{parts}</span>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { active } = this.props;
|
||||
const { source } = this.state;
|
||||
|
||||
return active ? (
|
||||
<StyledSyntaxHighlighter
|
||||
language="jsx"
|
||||
showLineNumbers="true"
|
||||
renderer={this.lineRenderer}
|
||||
format={false}
|
||||
copyable={false}
|
||||
padded
|
||||
>
|
||||
{source}
|
||||
</StyledSyntaxHighlighter>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
StoryPanel.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
api: PropTypes.shape({
|
||||
selectStory: PropTypes.func.isRequired,
|
||||
emit: PropTypes.func,
|
||||
on: PropTypes.func,
|
||||
off: PropTypes.func,
|
||||
}).isRequired,
|
||||
};
|
209
addons/storysource/src/StoryPanel.tsx
Normal file
209
addons/storysource/src/StoryPanel.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import React from 'react';
|
||||
import { API } from '@storybook/api';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Link } from '@storybook/router';
|
||||
import {
|
||||
SyntaxHighlighter,
|
||||
SyntaxHighlighterProps,
|
||||
SyntaxHighlighterRendererProps,
|
||||
} from '@storybook/components';
|
||||
|
||||
import createElement from 'react-syntax-highlighter/dist/esm/create-element';
|
||||
|
||||
const StyledStoryLink = styled(Link)<{ to: string; key: string }>(({ theme }) => ({
|
||||
display: 'block',
|
||||
textDecoration: 'none',
|
||||
borderRadius: theme.appBorderRadius,
|
||||
color: 'inherit',
|
||||
|
||||
'&:hover': {
|
||||
background: theme.background.hoverable,
|
||||
},
|
||||
}));
|
||||
|
||||
const SelectedStoryHighlight = styled.div<{ ref: React.RefObject<HTMLDivElement>; key: string }>(
|
||||
({ theme }) => ({
|
||||
background: theme.background.hoverable,
|
||||
borderRadius: theme.appBorderRadius,
|
||||
})
|
||||
);
|
||||
|
||||
const StyledSyntaxHighlighter = styled(SyntaxHighlighter)<SyntaxHighlighterProps>(({ theme }) => ({
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
}));
|
||||
|
||||
interface SourceLoc {
|
||||
line: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
interface SourceBlock {
|
||||
startLoc: SourceLoc;
|
||||
endLoc: SourceLoc;
|
||||
}
|
||||
|
||||
const areLocationsEqual = (a: SourceBlock, b: SourceBlock): boolean =>
|
||||
a.startLoc.line === b.startLoc.line &&
|
||||
a.startLoc.col === b.startLoc.col &&
|
||||
a.endLoc.line === b.endLoc.line &&
|
||||
a.endLoc.col === b.endLoc.col;
|
||||
|
||||
interface LocationsMap {
|
||||
[key: string]: SourceBlock;
|
||||
}
|
||||
const getLocationKeys = (locationsMap: LocationsMap) =>
|
||||
locationsMap
|
||||
? Array.from(Object.keys(locationsMap)).sort(
|
||||
(key1, key2) => locationsMap[key1].startLoc.line - locationsMap[key2].startLoc.line
|
||||
)
|
||||
: [];
|
||||
|
||||
interface StoryPanelProps {
|
||||
api: API;
|
||||
}
|
||||
|
||||
interface SourceParams {
|
||||
source: string;
|
||||
locationsMap: LocationsMap;
|
||||
}
|
||||
export interface StoryData {
|
||||
id: string;
|
||||
kind?: string;
|
||||
parameters?: {
|
||||
storySource?: SourceParams;
|
||||
};
|
||||
}
|
||||
export const StoryPanel: React.FC<StoryPanelProps> = ({ api }) => {
|
||||
const [state, setState] = React.useState<SourceParams & { currentLocation?: SourceBlock }>({
|
||||
source: 'loading source...',
|
||||
locationsMap: {},
|
||||
});
|
||||
|
||||
const story: StoryData | undefined = api.getCurrentStoryData();
|
||||
const selectedStoryRef = React.useRef<HTMLDivElement>();
|
||||
React.useEffect(() => {
|
||||
if (story) {
|
||||
const {
|
||||
parameters: {
|
||||
storySource: { source, locationsMap } = { source: '', locationsMap: {} },
|
||||
} = {},
|
||||
} = story;
|
||||
const currentLocation =
|
||||
locationsMap[
|
||||
Object.keys(locationsMap).find((key: string) => {
|
||||
const sourceLoaderId = key.split('--');
|
||||
return story.id.endsWith(sourceLoaderId[sourceLoaderId.length - 1]);
|
||||
})
|
||||
];
|
||||
setState({ source, locationsMap, currentLocation });
|
||||
}
|
||||
}, [story ? story.id : null]);
|
||||
React.useEffect(() => {
|
||||
if (selectedStoryRef.current) {
|
||||
selectedStoryRef.current.scrollIntoView();
|
||||
}
|
||||
}, [selectedStoryRef.current]);
|
||||
|
||||
const { source, locationsMap, currentLocation } = state;
|
||||
|
||||
const createPart = ({ rows, stylesheet, useInlineStyles }: SyntaxHighlighterRendererProps) =>
|
||||
rows.map((node, i) =>
|
||||
createElement({
|
||||
node,
|
||||
stylesheet,
|
||||
useInlineStyles,
|
||||
key: `code-segement${i}`,
|
||||
})
|
||||
);
|
||||
|
||||
const createStoryPart = ({
|
||||
rows,
|
||||
stylesheet,
|
||||
useInlineStyles,
|
||||
location,
|
||||
id,
|
||||
}: SyntaxHighlighterRendererProps & { location: SourceBlock; id: string }): React.ReactNode => {
|
||||
const first = location.startLoc.line - 1;
|
||||
const last = location.endLoc.line;
|
||||
|
||||
const storyRows = rows.slice(first, last);
|
||||
const storySource = createPart({ rows: storyRows, stylesheet, useInlineStyles });
|
||||
const storyKey = `${first}-${last}`;
|
||||
|
||||
if (location && currentLocation && areLocationsEqual(location, currentLocation)) {
|
||||
return (
|
||||
<SelectedStoryHighlight key={storyKey} ref={selectedStoryRef}>
|
||||
{storySource}
|
||||
</SelectedStoryHighlight>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StyledStoryLink to={`/story/${id}`} key={storyKey}>
|
||||
{storySource}
|
||||
</StyledStoryLink>
|
||||
);
|
||||
};
|
||||
|
||||
const createParts = ({ rows, stylesheet, useInlineStyles }: SyntaxHighlighterRendererProps) => {
|
||||
const parts = [];
|
||||
let lastRow = 0;
|
||||
|
||||
Object.keys(locationsMap).forEach(key => {
|
||||
const location = locationsMap[key];
|
||||
const first = location.startLoc.line - 1;
|
||||
const last = location.endLoc.line;
|
||||
const { kind } = story;
|
||||
// source loader ids are differnet from story id
|
||||
const sourceIdParts = key.split('--');
|
||||
const id = api.storyId(kind, sourceIdParts[sourceIdParts.length - 1]);
|
||||
const start = createPart({ rows: rows.slice(lastRow, first), stylesheet, useInlineStyles });
|
||||
const storyPart = createStoryPart({ rows, stylesheet, useInlineStyles, location, id });
|
||||
|
||||
parts.push(start);
|
||||
parts.push(storyPart);
|
||||
|
||||
lastRow = last;
|
||||
});
|
||||
|
||||
const lastPart = createPart({ rows: rows.slice(lastRow), stylesheet, useInlineStyles });
|
||||
|
||||
parts.push(lastPart);
|
||||
|
||||
return parts;
|
||||
};
|
||||
|
||||
const lineRenderer = ({
|
||||
rows,
|
||||
stylesheet,
|
||||
useInlineStyles,
|
||||
}: SyntaxHighlighterRendererProps): React.ReactNode => {
|
||||
// because of the usage of lineRenderer, all lines will be wrapped in a span
|
||||
// these spans will receive all classes on them for some reason
|
||||
// which makes colours casecade incorrectly
|
||||
// this removed that list of classnames
|
||||
const myrows = rows.map(({ properties, ...rest }) => ({
|
||||
...rest,
|
||||
properties: { className: [] },
|
||||
}));
|
||||
|
||||
if (!locationsMap || !Object.keys(locationsMap).length) {
|
||||
return createPart({ rows: myrows, stylesheet, useInlineStyles });
|
||||
}
|
||||
|
||||
const parts = createParts({ rows: myrows, stylesheet, useInlineStyles });
|
||||
|
||||
return <span>{parts}</span>;
|
||||
};
|
||||
return (
|
||||
<StyledSyntaxHighlighter
|
||||
language="jsx"
|
||||
showLineNumbers
|
||||
renderer={lineRenderer}
|
||||
format={false}
|
||||
copyable={false}
|
||||
padded
|
||||
>
|
||||
{source}
|
||||
</StyledSyntaxHighlighter>
|
||||
);
|
||||
};
|
@ -1,3 +1,2 @@
|
||||
export const ADDON_ID = 'storybook/source-loader';
|
||||
export const PANEL_ID = `${ADDON_ID}/panel`;
|
||||
export const EVENT_ID = `${ADDON_ID}/set`;
|
@ -1,8 +0,0 @@
|
||||
import { ADDON_ID, PANEL_ID, EVENT_ID } from './events';
|
||||
import { withStorySource } from './preview';
|
||||
|
||||
export { ADDON_ID, PANEL_ID, EVENT_ID, withStorySource };
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
7
addons/storysource/src/index.ts
Normal file
7
addons/storysource/src/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { ADDON_ID, PANEL_ID } from './events';
|
||||
|
||||
export { ADDON_ID, PANEL_ID };
|
||||
|
||||
if (module && module.hot && module.hot.decline) {
|
||||
module.hot.decline();
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import addons from '@storybook/addons';
|
||||
|
||||
import StoryPanel from './StoryPanel';
|
||||
import { StoryPanel } from './StoryPanel';
|
||||
import { ADDON_ID, PANEL_ID } from '.';
|
||||
|
||||
export function register() {
|
||||
addons.register(ADDON_ID, api => {
|
||||
addons.addPanel(PANEL_ID, {
|
||||
title: 'Story',
|
||||
render: ({ active, key }) => <StoryPanel key={key} api={api} active={active} />,
|
||||
render: ({ active, key }) =>
|
||||
active ? <StoryPanel key={key} api={api} active={active} /> : null,
|
||||
paramKey: 'storysource',
|
||||
});
|
||||
});
|
@ -1,21 +0,0 @@
|
||||
import addons from '@storybook/addons';
|
||||
import { EVENT_ID } from './events';
|
||||
|
||||
const getLocation = (context, locationsMap) => locationsMap[context.id];
|
||||
|
||||
function setStorySource(context, source, locationsMap) {
|
||||
const currentLocation = getLocation(context, locationsMap);
|
||||
|
||||
addons.getChannel().emit(EVENT_ID, {
|
||||
source,
|
||||
currentLocation,
|
||||
locationsMap,
|
||||
});
|
||||
}
|
||||
|
||||
export function withStorySource(source, locationsMap = {}) {
|
||||
return (storyFn, context) => {
|
||||
setStorySource(context, source, locationsMap);
|
||||
return storyFn();
|
||||
};
|
||||
}
|
1
addons/storysource/src/typings.d.ts
vendored
Normal file
1
addons/storysource/src/typings.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'react-syntax-highlighter/dist/esm/create-element';
|
@ -1,9 +1,7 @@
|
||||
module.exports = {
|
||||
stories: [
|
||||
// FIXME: Breaks e2e tests './intro.stories.mdx',
|
||||
'../../lib/ui/src/**/*.stories.(js|tsx|mdx)',
|
||||
'../../lib/components/src/**/*.stories.(js|tsx|mdx)',
|
||||
'./stories/**/*.stories.(js|tsx|mdx)',
|
||||
'../../lib/ui/src/components/notifications/item.stories.js',
|
||||
],
|
||||
addons: [
|
||||
'@storybook/addon-docs',
|
||||
|
@ -3,7 +3,11 @@ export { Badge } from './Badge/Badge';
|
||||
// Typography
|
||||
export { Link } from './typography/link/link';
|
||||
export { DocumentWrapper } from './typography/DocumentWrapper';
|
||||
export { SyntaxHighlighter } from './syntaxhighlighter/syntaxhighlighter';
|
||||
export {
|
||||
SyntaxHighlighter,
|
||||
SyntaxHighlighterProps,
|
||||
SyntaxHighlighterRendererProps,
|
||||
} from './syntaxhighlighter/syntaxhighlighter';
|
||||
|
||||
// UI
|
||||
export { ActionBar } from './ActionBar/ActionBar';
|
||||
|
@ -82,6 +82,11 @@ const Code = styled.code({
|
||||
opacity: 1,
|
||||
});
|
||||
|
||||
export interface SyntaxHighlighterRendererProps {
|
||||
rows: any[];
|
||||
stylesheet: string;
|
||||
useInlineStyles: boolean;
|
||||
}
|
||||
export interface SyntaxHighlighterProps {
|
||||
language: string;
|
||||
copyable?: boolean;
|
||||
@ -89,6 +94,7 @@ export interface SyntaxHighlighterProps {
|
||||
padded?: boolean;
|
||||
format?: boolean;
|
||||
className?: string;
|
||||
renderer?: (props: SyntaxHighlighterRendererProps) => React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SyntaxHighlighterState {
|
||||
|
@ -1,2 +0,0 @@
|
||||
export const ADDON_ID = 'storybook/source-loader';
|
||||
export const STORY_EVENT_ID = `${ADDON_ID}/set`;
|
@ -1,100 +0,0 @@
|
||||
import addons from '@storybook/addons';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { STORY_EVENT_ID } from './events';
|
||||
|
||||
const getLocation = (context, locationsMap) => locationsMap[context.id];
|
||||
|
||||
function sendEvent(
|
||||
context,
|
||||
source,
|
||||
locationsMap,
|
||||
mainFileLocation,
|
||||
dependencies,
|
||||
localDependencies,
|
||||
prefix,
|
||||
idsToFrameworks
|
||||
) {
|
||||
if (!context || !context.id || !context.kind || !context.story) {
|
||||
logger.warn(
|
||||
'@storybook/source-loader was applied to a file which does not contain a story. Please check your webpack configuration and make sure to apply @storybook/source-loader only to files containg stories. Related file:'
|
||||
);
|
||||
logger.warn(source);
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = addons.getChannel();
|
||||
const currentLocation = getLocation(context, locationsMap);
|
||||
|
||||
channel.emit(STORY_EVENT_ID, {
|
||||
edition: {
|
||||
source,
|
||||
mainFileLocation,
|
||||
dependencies,
|
||||
localDependencies,
|
||||
prefix,
|
||||
idsToFrameworks,
|
||||
},
|
||||
story: {
|
||||
kind: context.kind,
|
||||
story: context.story,
|
||||
},
|
||||
location: {
|
||||
currentLocation,
|
||||
locationsMap,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function addSource(storyFn, sourceContext) {
|
||||
const {
|
||||
__STORY__: source,
|
||||
__ADDS_MAP__: locationsMap = {},
|
||||
__MAIN_FILE_LOCATION__: mainFileLocation = '/index.js',
|
||||
__MODULE_DEPENDENCIES__: dependencies = [],
|
||||
__LOCAL_DEPENDENCIES__: localDependencies = {},
|
||||
__SOURCE_PREFIX__: prefix,
|
||||
__IDS_TO_FRAMEWORKS__: idsToFrameworks,
|
||||
} = sourceContext;
|
||||
const decorated = (context = {}) => {
|
||||
sendEvent(
|
||||
context,
|
||||
source,
|
||||
locationsMap,
|
||||
mainFileLocation,
|
||||
dependencies,
|
||||
localDependencies,
|
||||
prefix,
|
||||
idsToFrameworks
|
||||
);
|
||||
if (typeof storyFn === 'function') {
|
||||
return storyFn(context);
|
||||
}
|
||||
return storyFn;
|
||||
};
|
||||
decorated.story = (storyFn || {}).story;
|
||||
return decorated;
|
||||
}
|
||||
|
||||
export function withSource(
|
||||
source,
|
||||
locationsMap = {},
|
||||
mainFileLocation = '/index.js',
|
||||
dependencies = [],
|
||||
localDependencies = {},
|
||||
prefix,
|
||||
idsToFrameworks
|
||||
) {
|
||||
return (storyFn, context) => {
|
||||
sendEvent(
|
||||
context,
|
||||
source,
|
||||
locationsMap,
|
||||
mainFileLocation,
|
||||
dependencies,
|
||||
localDependencies,
|
||||
prefix,
|
||||
idsToFrameworks
|
||||
);
|
||||
return storyFn(context);
|
||||
};
|
||||
}
|
@ -3,7 +3,6 @@ import getParser from './parsers';
|
||||
import {
|
||||
splitSTORYOF,
|
||||
findAddsMap,
|
||||
findDependencies,
|
||||
splitExports,
|
||||
popParametersObjectFromDefaultExport,
|
||||
findExportsMap as generateExportsMap,
|
||||
@ -69,8 +68,8 @@ const ADD_PARAMETERS_STATEMENT =
|
||||
'.addParameters({ storySource: { source: __STORY__, locationsMap: __ADDS_MAP__ } })';
|
||||
const applyExportDecoratorStatement = part =>
|
||||
part.declaration.isVariableDeclaration
|
||||
? ` addSourceDecorator(${part.source}, {__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__});`
|
||||
: ` const ${part.declaration.ident} = addSourceDecorator(${part.source}, {__STORY__, __ADDS_MAP__,__MAIN_FILE_LOCATION__,__MODULE_DEPENDENCIES__,__LOCAL_DEPENDENCIES__,__SOURCE_PREFIX__,__IDS_TO_FRAMEWORKS__});`;
|
||||
? ` ${part.source};`
|
||||
: ` const ${part.declaration.ident} = ${part.source};`;
|
||||
|
||||
export function generateSourceWithDecorators(source, ast, withParameters) {
|
||||
const { comments = [] } = ast;
|
||||
@ -120,7 +119,7 @@ export function generateAddsMap(ast, storiesOfIdentifiers) {
|
||||
|
||||
export function generateStoriesLocationsMap(ast, storiesOfIdentifiers) {
|
||||
const usingAddsMap = generateAddsMap(ast, storiesOfIdentifiers);
|
||||
const { addsMap } = usingAddsMap;
|
||||
const addsMap = usingAddsMap;
|
||||
|
||||
if (Object.keys(addsMap).length > 0) {
|
||||
return usingAddsMap;
|
||||
@ -130,10 +129,6 @@ export function generateStoriesLocationsMap(ast, storiesOfIdentifiers) {
|
||||
return usingExportsMap || usingAddsMap;
|
||||
}
|
||||
|
||||
export function generateDependencies(ast) {
|
||||
return findDependencies(ast);
|
||||
}
|
||||
|
||||
export function generateStorySource({ source, ...options }) {
|
||||
let storySource = source;
|
||||
|
||||
|
@ -6,7 +6,6 @@ import {
|
||||
generateSourceWithoutDecorators,
|
||||
generateStorySource,
|
||||
generateStoriesLocationsMap,
|
||||
generateDependencies,
|
||||
generateSourcesInExportedParameters,
|
||||
} from './generate-helpers';
|
||||
|
||||
@ -21,7 +20,7 @@ function extendOptions(source, comments, filepath, options) {
|
||||
}
|
||||
|
||||
function inject(source, filepath, options = {}, log = message => {}) {
|
||||
const { injectDecorator = true, injectParameters = true, inspectDependencies } = options;
|
||||
const { injectDecorator = true, injectParameters = true } = options;
|
||||
const obviouslyNotCode = ['md', 'txt', 'json'].includes(options.parser);
|
||||
let parser = null;
|
||||
try {
|
||||
@ -36,7 +35,6 @@ function inject(source, filepath, options = {}, log = message => {}) {
|
||||
storySource: {},
|
||||
addsMap: {},
|
||||
changed: false,
|
||||
dependencies: [],
|
||||
};
|
||||
}
|
||||
const ast = parser.parse(source);
|
||||
@ -48,10 +46,7 @@ function inject(source, filepath, options = {}, log = message => {}) {
|
||||
|
||||
const storySource = generateStorySource(extendOptions(source, comments, filepath, options));
|
||||
const newAst = parser.parse(storySource);
|
||||
const { dependencies, storiesOfIdentifiers } = inspectDependencies
|
||||
? generateDependencies(newAst)
|
||||
: { dependencies: [], storiesOfIdentifiers: {} };
|
||||
const { addsMap, idsToFrameworks } = generateStoriesLocationsMap(newAst, storiesOfIdentifiers);
|
||||
const addsMap = generateStoriesLocationsMap(newAst, []);
|
||||
|
||||
let newSource = cleanedSource;
|
||||
if (exportTokenFound) {
|
||||
@ -68,8 +63,6 @@ function inject(source, filepath, options = {}, log = message => {}) {
|
||||
storySource,
|
||||
addsMap: {},
|
||||
changed,
|
||||
dependencies,
|
||||
idsToFrameworks: idsToFrameworks || {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -78,8 +71,6 @@ function inject(source, filepath, options = {}, log = message => {}) {
|
||||
storySource,
|
||||
addsMap,
|
||||
changed,
|
||||
dependencies,
|
||||
idsToFrameworks: idsToFrameworks || {},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,6 @@ export function splitExports(ast, source) {
|
||||
|
||||
export function findAddsMap(ast, storiesOfIdentifiers) {
|
||||
const addsMap = {};
|
||||
const idsToFrameworks = {};
|
||||
|
||||
estraverse.traverse(ast, {
|
||||
fallback: 'iteration',
|
||||
@ -149,17 +148,15 @@ export function findAddsMap(ast, storiesOfIdentifiers) {
|
||||
if (node.type === 'MemberExpression') {
|
||||
const { toAdd, idToFramework } = handleADD(node, parent, storiesOfIdentifiers);
|
||||
Object.assign(addsMap, toAdd);
|
||||
Object.assign(idsToFrameworks, idToFramework);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { addsMap, idsToFrameworks };
|
||||
return addsMap;
|
||||
}
|
||||
|
||||
export function findExportsMap(ast) {
|
||||
const addsMap = {};
|
||||
const idsToFrameworks = {};
|
||||
const program = (ast && ast.program) || ast;
|
||||
const metaDeclaration =
|
||||
program &&
|
||||
@ -177,7 +174,7 @@ export function findExportsMap(ast) {
|
||||
metaDeclaration.declaration.properties.find(p => p.key && p.key.name === 'title');
|
||||
|
||||
if (!titleProperty) {
|
||||
return { addsMap, idsToFrameworks };
|
||||
return addsMap;
|
||||
}
|
||||
const titleValue = titleProperty.value;
|
||||
let title;
|
||||
@ -229,32 +226,7 @@ export function findExportsMap(ast) {
|
||||
},
|
||||
});
|
||||
}
|
||||
return { addsMap, idsToFrameworks };
|
||||
}
|
||||
|
||||
export function findDependencies(ast) {
|
||||
const dependencies = [];
|
||||
const storiesOfIdentifiers = {};
|
||||
|
||||
estraverse.traverse(ast, {
|
||||
fallback: 'iteration',
|
||||
enter: node => {
|
||||
patchNode(node);
|
||||
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
const candidateSpecifier = (node.specifiers || []).find(
|
||||
s => (s.imported || {}).name === 'storiesOf'
|
||||
);
|
||||
if (node.source.value.startsWith('@storybook/') && candidateSpecifier) {
|
||||
Object.assign(storiesOfIdentifiers, {
|
||||
[candidateSpecifier.local.name]: node.source.value,
|
||||
});
|
||||
}
|
||||
dependencies.push(node.source.value);
|
||||
}
|
||||
},
|
||||
});
|
||||
return { dependencies, storiesOfIdentifiers };
|
||||
return addsMap;
|
||||
}
|
||||
|
||||
export function popParametersObjectFromDefaultExport(source, ast) {
|
||||
|
@ -1,54 +1,23 @@
|
||||
import { readStory } from './dependencies-lookup/readAsObject';
|
||||
import { getRidOfUselessFilePrefixes } from './dependencies-lookup/getRidOfUselessFilePrefixes';
|
||||
|
||||
export function transform(inputSource) {
|
||||
return readStory(this, inputSource)
|
||||
.then(sourceObject => {
|
||||
// if source-loader had trouble parsing the story exports, return the original story
|
||||
// example is
|
||||
// const myStory = () => xxx
|
||||
// export { myStory }
|
||||
if (!sourceObject.source || sourceObject.source.length === 0) {
|
||||
return { source: inputSource };
|
||||
}
|
||||
return getRidOfUselessFilePrefixes(sourceObject);
|
||||
})
|
||||
.then(
|
||||
({
|
||||
prefix,
|
||||
resource,
|
||||
source,
|
||||
sourceJson,
|
||||
addsMap,
|
||||
dependencies,
|
||||
localDependencies,
|
||||
idsToFrameworks,
|
||||
}) => {
|
||||
const preamble = prefix
|
||||
? `
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// @ts-ignore
|
||||
var withSourceLoader = require('@storybook/source-loader/preview').withSource;
|
||||
// @ts-ignore
|
||||
var addSourceDecorator = require("@storybook/source-loader/preview").addSource;
|
||||
// @ts-ignore
|
||||
var __SOURCE_PREFIX__ = "${prefix.replace(/\\([^\\ ])/g, '\\\\$1')}";
|
||||
// @ts-ignore
|
||||
var __STORY__ = ${sourceJson};
|
||||
// @ts-ignore
|
||||
var __ADDS_MAP__ = ${JSON.stringify(addsMap)};
|
||||
// @ts-ignore
|
||||
var __MAIN_FILE_LOCATION__ = ${JSON.stringify(resource)};
|
||||
// @ts-ignore
|
||||
var __MODULE_DEPENDENCIES__ = ${JSON.stringify(dependencies)};
|
||||
// @ts-ignore
|
||||
var __LOCAL_DEPENDENCIES__ = ${JSON.stringify(localDependencies)};
|
||||
// @ts-ignore
|
||||
var __IDS_TO_FRAMEWORKS__ = ${JSON.stringify(idsToFrameworks)};
|
||||
`
|
||||
: '';
|
||||
return `${preamble}\n${source}`;
|
||||
}
|
||||
);
|
||||
return readStory(this, inputSource).then(sourceObject => {
|
||||
// if source-loader had trouble parsing the story exports, return the original story
|
||||
// example is
|
||||
// const myStory = () => xxx
|
||||
// export { myStory }
|
||||
if (!sourceObject.source || sourceObject.source.length === 0) {
|
||||
return inputSource;
|
||||
}
|
||||
const { source, sourceJson, addsMap } = sourceObject;
|
||||
const preamble = `
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// @ts-ignore
|
||||
var __STORY__ = ${sourceJson};
|
||||
// @ts-ignore
|
||||
var __ADDS_MAP__ = ${JSON.stringify(addsMap)};
|
||||
`;
|
||||
return `${preamble}\n${source}`;
|
||||
});
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
import path from 'path';
|
||||
|
||||
function commonDir(...resources) {
|
||||
const firstResource = (resources[0] || '').split(path.sep);
|
||||
let i = 1;
|
||||
while (
|
||||
i < firstResource.length &&
|
||||
// eslint-disable-next-line no-loop-func
|
||||
resources.every(resource => resource.startsWith(firstResource.slice(0, i).join(path.sep)))
|
||||
) {
|
||||
i += 1;
|
||||
}
|
||||
return firstResource.slice(0, i - 1).join(path.sep);
|
||||
}
|
||||
|
||||
export function getRidOfUselessFilePrefixes({
|
||||
resource,
|
||||
source,
|
||||
sourceJson,
|
||||
addsMap,
|
||||
dependencies,
|
||||
localDependencies,
|
||||
idsToFrameworks,
|
||||
}) {
|
||||
const commondir = commonDir(resource, ...Object.keys(localDependencies || {}));
|
||||
return {
|
||||
prefix: commondir,
|
||||
source,
|
||||
sourceJson,
|
||||
addsMap,
|
||||
dependencies,
|
||||
idsToFrameworks,
|
||||
resource:
|
||||
commondir === resource
|
||||
? '/index.js'
|
||||
: resource.substring(commondir.length).replace(path.sep === '\\' ? /\\/g : /\//g, '/'),
|
||||
localDependencies: Object.assign(
|
||||
{},
|
||||
...Object.entries(localDependencies || {}).map(([depFileName, dependency]) => ({
|
||||
[depFileName
|
||||
.substring(commondir.length)
|
||||
.replace(new RegExp(path.sep === '\\' ? /\\/g : /\//g, 'g'), '/')]: dependency,
|
||||
}))
|
||||
),
|
||||
};
|
||||
}
|
@ -2,29 +2,8 @@ import { getOptions } from 'loader-utils';
|
||||
import path from 'path';
|
||||
import injectDecorator from '../abstract-syntax-tree/inject-decorator';
|
||||
|
||||
function extractDependenciesFrom(tree) {
|
||||
return !Object.entries(tree || {}).length
|
||||
? []
|
||||
: Object.entries(tree)
|
||||
.map(([, value]) =>
|
||||
(value.dependencies || []).concat(extractDependenciesFrom(value.localDependencies))
|
||||
)
|
||||
.reduce((acc, value) => acc.concat(value), []);
|
||||
}
|
||||
|
||||
function extractLocalDependenciesFrom(tree) {
|
||||
return Object.assign(
|
||||
{},
|
||||
...Object.entries(tree || {}).map(([thisPath, value]) => ({
|
||||
[thisPath]: { code: value.source || value.code },
|
||||
...extractLocalDependenciesFrom(value.localDependencies),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
function readAsObject(classLoader, inputSource, mainFile) {
|
||||
const options = getOptions(classLoader) || {};
|
||||
const { inspectLocalDependencies } = options;
|
||||
const result = injectDecorator(
|
||||
inputSource,
|
||||
classLoader.resourcePath,
|
||||
@ -40,15 +19,10 @@ function readAsObject(classLoader, inputSource, mainFile) {
|
||||
.replace(/\u2029/g, '\\u2029');
|
||||
|
||||
const addsMap = result.addsMap || {};
|
||||
const dependencies = result.dependencies || [];
|
||||
const source = mainFile ? result.source : inputSource;
|
||||
const idsToFrameworks = result.idsToFrameworks || {};
|
||||
const resource = classLoader.resourcePath || classLoader.resource;
|
||||
|
||||
const moduleDependencies = (result.dependencies || []).filter(d => d[0] === '.' || d[0] === '/');
|
||||
const workspaceFileNames = inspectLocalDependencies
|
||||
? moduleDependencies.map(d => path.join(path.dirname(resource), d))
|
||||
: [];
|
||||
const workspaceFileNames = [];
|
||||
|
||||
return Promise.all(
|
||||
workspaceFileNames.map(
|
||||
@ -97,22 +71,11 @@ function readAsObject(classLoader, inputSource, mainFile) {
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(localDependencies => ({
|
||||
.then(() => ({
|
||||
resource,
|
||||
source,
|
||||
sourceJson,
|
||||
addsMap,
|
||||
idsToFrameworks,
|
||||
dependencies: dependencies
|
||||
.concat(extractDependenciesFrom(localDependencies))
|
||||
.filter(d => d[0] !== '.' && d[0] !== '/')
|
||||
.map(d => (d[0] === '@' ? `${d.split('/')[0]}/${d.split('/')[1]}` : d.split('/')[0])),
|
||||
localDependencies: Object.assign(
|
||||
...Object.entries(localDependencies).map(([name, value]) => ({
|
||||
[name]: { code: value.source },
|
||||
})),
|
||||
extractLocalDependenciesFrom(localDependencies)
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user