@storybook/react-native
Version:
A better way to develop React Native Components for your app
203 lines (165 loc) • 5.15 kB
JavaScript
const {
toRequireContext,
ensureRelativePathHasDot,
getPreviewExists,
resolveAddonFile,
getAddonName,
} = require('./common');
const { normalizeStories, globToRegexp, loadMainConfig } = require('storybook/internal/common');
const { interopRequireDefault } = require('./require-interop');
const fs = require('fs');
const { networkInterfaces } = require('node:os');
const path = require('path');
const cwd = process.cwd();
const loadMain = async ({ configPath, cwd }) => {
try {
const main = await loadMainConfig({ configDir: configPath, cwd });
return main;
} catch {
console.error('Error loading main config, trying fallback');
}
const mainPathTs = path.resolve(cwd, configPath, `main.ts`);
const mainPathJs = path.resolve(cwd, configPath, `main.js`);
if (fs.existsSync(mainPathTs)) {
return interopRequireDefault(mainPathTs);
} else if (fs.existsSync(mainPathJs)) {
return interopRequireDefault(mainPathJs);
} else {
throw new Error(`Main config file not found at ${mainPathTs} or ${mainPathJs}`);
}
};
/**
* Get the local IP address of the machine.
* @returns The local IP address of the machine.
*/
function getLocalIPAddress() {
const nets = networkInterfaces();
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4;
if (net.family === familyV4Value && !net.internal) {
return net.address;
}
}
}
return '0.0.0.0';
}
async function generate({
configPath,
useJs = false,
docTools = true,
host = undefined,
port = 7007,
}) {
// here we want to get the ip address and pass it to rn storybook so that devices can connect over lan easily
const channelHost = host === 'auto' ? getLocalIPAddress() : host;
const storybookRequiresLocation = path.resolve(
cwd,
configPath,
`storybook.requires.${useJs ? 'js' : 'ts'}`
);
const main = await loadMain({ configPath, cwd });
const storiesSpecifiers = normalizeStories(main.stories, {
configDir: configPath,
workingDir: cwd,
});
const normalizedStories = storiesSpecifiers.map((specifier) => {
// TODO why????
const reg = globToRegexp(`./${specifier.files}`);
const { path: p, recursive: r, match: m } = toRequireContext(specifier);
const pathToStory = ensureRelativePathHasDot(path.posix.relative(configPath, p));
return `{
titlePrefix: "${specifier.titlePrefix}",
directory: "${specifier.directory}",
files: "${specifier.files}",
importPathMatcher: /${reg.source}/,
${useJs ? '' : '// @ts-ignore'}
req: require.context(
'${pathToStory}',
${r},
${m}
),
}`;
});
const registeredAddons = [];
for (const addon of main.addons) {
const registerPath = resolveAddonFile(
getAddonName(addon),
'register',
['js', 'mjs', 'jsx', 'ts', 'tsx'],
configPath
);
if (registerPath) {
registeredAddons.push(`import "${registerPath}";`);
}
}
const docToolsAnnotation = 'require("@storybook/react-native/preview")';
const enhancers = [];
if (docTools) {
enhancers.push(docToolsAnnotation);
}
for (const addon of main.addons) {
const previewPath = resolveAddonFile(
getAddonName(addon),
'preview',
['js', 'mjs', 'jsx', 'ts', 'tsx'],
configPath
);
if (previewPath) {
enhancers.push(`require('${previewPath}')`);
continue;
}
}
let options = '';
let optionsVar = '';
const reactNativeOptions = main.reactNative;
if (reactNativeOptions && typeof reactNativeOptions === 'object') {
optionsVar = `const options = ${JSON.stringify(reactNativeOptions, null, 2)}`;
options = 'options';
}
const previewExists = getPreviewExists({ configPath });
if (previewExists) {
enhancers.unshift("require('./preview')");
}
const annotations = `[
${enhancers.join(',\n ')}
]`;
const globalTypes = `
declare global {
var view: View;
var STORIES: typeof normalizedStories;
var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
}
`;
const fileContent = `/* do not change this file, it is auto generated by storybook. */
import { start, updateView${useJs ? '' : ', View'} } from '@storybook/react-native';
${registeredAddons.join('\n')}
const normalizedStories = [
${normalizedStories.join(',\n ')}
];
${useJs ? '' : globalTypes}
const annotations = ${annotations};
globalThis.STORIES = normalizedStories;
${channelHost ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${channelHost}', port: ${port ?? 7007} };` : ''}
${useJs ? '' : '// @ts-ignore'}
module?.hot?.accept?.();
${optionsVar}
if (!globalThis.view) {
globalThis.view = start({
annotations,
storyEntries: normalizedStories,
${options ? ` ${options},` : ''}
});
} else {
updateView(globalThis.view, annotations, normalizedStories${options ? `, ${options}` : ''});
}
export const view${useJs ? '' : ': View'} = globalThis.view;
`;
fs.writeFileSync(storybookRequiresLocation, fileContent, {
encoding: 'utf8',
flag: 'w',
});
}
module.exports = {
generate,
};