UNPKG

5.24 kBJavaScriptView Raw
1const path = require('path');
2const fs = require('fs');
3const glob = require('glob');
4const prettier = require('prettier');
5const { normalizeStories } = require('@storybook/core/common');
6const {
7 toRequireContext,
8 getFilePathExtension,
9 getMain,
10 ensureRelativePathHasDot,
11 getPreviewExists,
12} = require('./common');
13
14const cwd = process.cwd();
15
16// TODO check if we need clearDecorators();
17
18// we clear decorators as a workaround for global decorators getting infinitely applied on HMR
19const previewImports = `
20 import { decorators, parameters } from './preview';
21
22 if (decorators) {
23 decorators.forEach((decorator) => addDecorator(decorator));
24 }
25
26 if (parameters) {
27 addParameters(parameters);
28 }
29`;
30
31function normalizeExcludePaths(paths) {
32 // automatically convert a string to an array of a single string
33 if (typeof paths === 'string') {
34 return [paths];
35 }
36
37 // ensure the paths is an array and if any items exists, they are strings
38 if (Array.isArray(paths) && paths.every((p) => typeof p === 'string')) {
39 return paths;
40 }
41
42 // when the paths aren't a string or an (empty) array of strings, return
43 return undefined;
44}
45
46function writeRequires({ configPath, absolute = false, v6RequireContext = false }) {
47 const storybookRequiresLocation = path.resolve(cwd, configPath, 'storybook.requires.js');
48
49 const mainImport = getMain({ configPath });
50
51 const main = mainImport.default ?? mainImport;
52
53 const reactNativeOptions = main.reactNativeOptions;
54
55 const excludePaths = reactNativeOptions && reactNativeOptions.excludePaths;
56
57 const normalizedExcludePaths = normalizeExcludePaths(excludePaths);
58
59 const storiesSpecifiers = normalizeStories(main.stories, {
60 configDir: configPath,
61 workingDir: cwd,
62 });
63
64 let configure = '';
65
66 if (v6RequireContext) {
67 const contexts = storiesSpecifiers.map((specifier) => {
68 const { path: p, recursive: r, match: m } = toRequireContext(specifier);
69
70 const pathToStory = ensureRelativePathHasDot(path.relative(configPath, p));
71
72 return `require.context('${pathToStory}', ${r}, ${m})`;
73 });
74
75 configure = `
76 const stories = [${contexts.join(',')}];
77
78 configure(stories, module, false)
79 `;
80 } else {
81 const storyRequires = storiesSpecifiers.reduce((acc, specifier) => {
82 const paths = glob
83 .sync(specifier.files, {
84 cwd: path.resolve(cwd, specifier.directory),
85 absolute,
86 // default to always ignore (exclude) anything in node_modules
87 ignore:
88 normalizedExcludePaths !== undefined ? normalizedExcludePaths : ['**/node_modules'],
89 })
90 .map((storyPath) => {
91 const pathWithDirectory = path.join(specifier.directory, storyPath);
92
93 const requirePath = absolute
94 ? storyPath
95 : ensureRelativePathHasDot(path.relative(configPath, pathWithDirectory));
96
97 const absolutePath = absolute ? requirePath : path.resolve(configPath, requirePath);
98
99 const pathRelativeToCwd = path.relative(cwd, absolutePath);
100
101 const normalizePathForWindows = (str) =>
102 path.sep === '\\' ? str.replace(/\\/g, '/') : str;
103
104 return `"./${normalizePathForWindows(
105 pathRelativeToCwd
106 )}": require("${normalizePathForWindows(requirePath)}")`;
107 });
108 return [...acc, ...paths];
109 }, []);
110
111 const path_obj_str = `{${storyRequires.join(',')}}`;
112
113 configure = `
114 const getStories=() => {
115 return ${path_obj_str};
116 }
117
118 configure(getStories, module, false)
119 `;
120 }
121
122 fs.writeFileSync(storybookRequiresLocation, '');
123
124 const previewExists = getPreviewExists({ configPath });
125
126 let previewJs = previewExists ? previewImports : '';
127
128 const registerAddons = main.addons?.map((addon) => `import "${addon}/register";`).join('\n');
129
130 let enhancersImport = '';
131
132 let enhancers = '';
133
134 // TODO: implement presets or something similar
135 if (main.addons?.includes('@storybook/addon-ondevice-actions')) {
136 enhancersImport = 'import { argsEnhancers } from "@storybook/addon-actions/dist/preview"';
137
138 // try/catch is a temporary fix for https://github.com/storybookjs/react-native/issues/327 until a fix is found
139 enhancers = `
140 try {
141 argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer));
142 } catch{}
143 `;
144 }
145
146 const normalizedStories = storiesSpecifiers.map((specifier) => ({
147 ...specifier,
148 importPathMatcher: specifier.importPathMatcher.source,
149 }));
150
151 const globalStories = `global.STORIES = ${JSON.stringify(normalizedStories)}`;
152
153 const fileContent = `
154 /* do not change this file, it is auto generated by storybook. */
155
156 import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native/V6';
157
158 ${globalStories}
159
160 ${registerAddons}
161
162 ${enhancersImport}
163
164 ${previewJs}
165
166 ${enhancers}
167
168 ${configure}
169
170 `;
171
172 const formattedFileContent = prettier.format(fileContent, { parser: 'babel' });
173
174 fs.writeFileSync(storybookRequiresLocation, formattedFileContent, {
175 encoding: 'utf8',
176 flag: 'w',
177 });
178}
179
180module.exports = {
181 writeRequires,
182 getMain,
183 getPreviewExists,
184 getFilePathExtension,
185};