@storybook/react-native
Version:
A better way to develop React Native Components for your app
186 lines (134 loc) • 5.24 kB
JavaScript
const path = require('path');
const fs = require('fs');
const glob = require('glob');
const prettier = require('prettier');
const { normalizeStories } = require('@storybook/core/common');
const {
toRequireContext,
getFilePathExtension,
getMain,
ensureRelativePathHasDot,
getPreviewExists,
} = require('./common');
const cwd = process.cwd();
// TODO check if we need clearDecorators();
// we clear decorators as a workaround for global decorators getting infinitely applied on HMR
const previewImports = `
import { decorators, parameters } from './preview';
if (decorators) {
decorators.forEach((decorator) => addDecorator(decorator));
}
if (parameters) {
addParameters(parameters);
}
`;
function normalizeExcludePaths(paths) {
// automatically convert a string to an array of a single string
if (typeof paths === 'string') {
return [paths];
}
// ensure the paths is an array and if any items exists, they are strings
if (Array.isArray(paths) && paths.every((p) => typeof p === 'string')) {
return paths;
}
// when the paths aren't a string or an (empty) array of strings, return
return undefined;
}
function writeRequires({ configPath, absolute = false, v6RequireContext = false }) {
const storybookRequiresLocation = path.resolve(cwd, configPath, 'storybook.requires.js');
const mainImport = getMain({ configPath });
const main = mainImport.default ?? mainImport;
const reactNativeOptions = main.reactNativeOptions;
const excludePaths = reactNativeOptions && reactNativeOptions.excludePaths;
const normalizedExcludePaths = normalizeExcludePaths(excludePaths);
const storiesSpecifiers = normalizeStories(main.stories, {
configDir: configPath,
workingDir: cwd,
});
let configure = '';
if (v6RequireContext) {
const contexts = storiesSpecifiers.map((specifier) => {
const { path: p, recursive: r, match: m } = toRequireContext(specifier);
const pathToStory = ensureRelativePathHasDot(path.relative(configPath, p));
return `require.context('${pathToStory}', ${r}, ${m})`;
});
configure = `
const stories = [${contexts.join(',')}];
configure(stories, module, false)
`;
} else {
const storyRequires = storiesSpecifiers.reduce((acc, specifier) => {
const paths = glob
.sync(specifier.files, {
cwd: path.resolve(cwd, specifier.directory),
absolute,
// default to always ignore (exclude) anything in node_modules
ignore:
normalizedExcludePaths !== undefined ? normalizedExcludePaths : ['**/node_modules'],
})
.map((storyPath) => {
const pathWithDirectory = path.join(specifier.directory, storyPath);
const requirePath = absolute
? storyPath
: ensureRelativePathHasDot(path.relative(configPath, pathWithDirectory));
const absolutePath = absolute ? requirePath : path.resolve(configPath, requirePath);
const pathRelativeToCwd = path.relative(cwd, absolutePath);
const normalizePathForWindows = (str) =>
path.sep === '\\' ? str.replace(/\\/g, '/') : str;
return `"./${normalizePathForWindows(
pathRelativeToCwd
)}": require("${normalizePathForWindows(requirePath)}")`;
});
return [...acc, ...paths];
}, []);
const path_obj_str = `{${storyRequires.join(',')}}`;
configure = `
const getStories=() => {
return ${path_obj_str};
}
configure(getStories, module, false)
`;
}
fs.writeFileSync(storybookRequiresLocation, '');
const previewExists = getPreviewExists({ configPath });
let previewJs = previewExists ? previewImports : '';
const registerAddons = main.addons?.map((addon) => `import "${addon}/register";`).join('\n');
let enhancersImport = '';
let enhancers = '';
// TODO: implement presets or something similar
if (main.addons?.includes('@storybook/addon-ondevice-actions')) {
enhancersImport = 'import { argsEnhancers } from "@storybook/addon-actions/dist/preview"';
// try/catch is a temporary fix for https://github.com/storybookjs/react-native/issues/327 until a fix is found
enhancers = `
try {
argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer));
} catch{}
`;
}
const normalizedStories = storiesSpecifiers.map((specifier) => ({
...specifier,
importPathMatcher: specifier.importPathMatcher.source,
}));
const globalStories = `global.STORIES = ${JSON.stringify(normalizedStories)}`;
const fileContent = `
/* do not change this file, it is auto generated by storybook. */
import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native/V6';
${globalStories}
${registerAddons}
${enhancersImport}
${previewJs}
${enhancers}
${configure}
`;
const formattedFileContent = prettier.format(fileContent, { parser: 'babel' });
fs.writeFileSync(storybookRequiresLocation, formattedFileContent, {
encoding: 'utf8',
flag: 'w',
});
}
module.exports = {
writeRequires,
getMain,
getPreviewExists,
getFilePathExtension,
};