Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

For v11:

- csf factories
- backgrounds with globals
- addons into core
- make lite ui the default and remove dependencies from controls

stretch goals:

- simple docs implementation
- dev tooling like vscode extension and rn dev tools integration
4 changes: 1 addition & 3 deletions examples/expo-example/.rnstorybook/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ const StorybookUIRoot = view.getStorybookUI({
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
enableWebsockets: false,
host: 'localhost',
port: 7007,
enableWebsockets: true,

CustomUIComponent: isScreenshotTesting
? ({ children, story }) => {
Expand Down
12 changes: 7 additions & 5 deletions examples/expo-example/.rnstorybook/storybook.requires.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const normalizedStories = [
declare global {
var view: View;
var STORIES: typeof normalizedStories;
var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
}


Expand All @@ -61,7 +62,8 @@ const annotations = [
require('./local-addon-example/preview')
];

global.STORIES = normalizedStories;
globalThis.STORIES = normalizedStories;
globalThis.STORYBOOK_WEBSOCKET = { host: '192.168.1.170', port: 7007 };

// @ts-ignore
module?.hot?.accept?.();
Expand All @@ -70,14 +72,14 @@ const options = {
"playFn": false
}

if (!global.view) {
global.view = start({
if (!globalThis.view) {
globalThis.view = start({
annotations,
storyEntries: normalizedStories,
options,
});
} else {
updateView(global.view, annotations, normalizedStories, options);
updateView(globalThis.view, annotations, normalizedStories, options);
}

export const view: View = global.view;
export const view: View = globalThis.view;
1 change: 1 addition & 0 deletions examples/expo-example/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const { withStorybook } = require('@storybook/react-native/metro/withStorybook')

module.exports = withStorybook(defaultConfig, {
enabled: process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true',
websockets: 'auto',
});

/* , {
Expand Down
2 changes: 1 addition & 1 deletion examples/expo-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"android": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start --android",
"ios": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start --ios",
"web": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start --web",
"storybook": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start -c",
"storybook": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start",
"storybook:lite": "EXPO_PUBLIC_STORYBOOK_ENABLED=true EXPO_PUBLIC_LITE_UI=true expo start -c",
"storybook:test": "EXPO_PUBLIC_SCREENSHOT_TESTING=true EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start -c",
"storybook:web": "storybook dev -p 6006",
Expand Down
53 changes: 53 additions & 0 deletions examples/expo-example/scripts/test-ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env node

import WebSocket from 'ws';
import fs from 'fs';

const host = process.argv[2] || 'localhost';
const port = process.argv[3] || 7007;
const url = `ws://${host}:${port}`;

console.log(`Connecting to ${url}...`);

const ws = new WebSocket(url);

ws.on('open', () => {
console.log('Connected!');

const message = JSON.stringify({
type: 'RN_GET_INDEX',
args: [],
from: 'test-script',
});

console.log('Sending:', message);
ws.send(message);
});

ws.on('message', (data) => {
const raw = data.toString();
try {
const parsed = JSON.parse(raw);
if (parsed.from === 'test-script' || parsed.type !== 'RN_GET_INDEX_RESPONSE') {
return;
}
fs.writeFileSync('index.json', JSON.stringify(parsed.args[0].index, null, 2));
process.exit(0);
} catch {
console.log('Received (raw):', raw);
}
});

ws.on('error', (err) => {
console.error('Error:', err.message);
});

ws.on('close', () => {
console.log('Connection closed');
});

// Close after 10 seconds
setTimeout(() => {
console.log('Timeout, closing...');
ws.close();
}, 10000);
20 changes: 14 additions & 6 deletions packages/react-native/scripts/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ const path = require('path');

const cwd = process.cwd();

async function generate({ configPath, /* absolute = false, */ useJs = false, docTools = true }) {
async function generate({
configPath,
/* absolute = false, */ useJs = false,
docTools = true,
host,
port,
}) {
const storybookRequiresLocation = path.resolve(
cwd,
configPath,
Expand Down Expand Up @@ -109,6 +115,7 @@ async function generate({ configPath, /* absolute = false, */ useJs = false, doc
declare global {
var view: View;
var STORIES: typeof normalizedStories;
var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;
}
`;

Expand All @@ -125,24 +132,25 @@ ${useJs ? '' : globalTypes}

const annotations = ${annotations};

global.STORIES = normalizedStories;
globalThis.STORIES = normalizedStories;
${host ? `globalThis.STORYBOOK_WEBSOCKET = { host: '${host}', port: ${port ?? 7007} };` : ''}

${useJs ? '' : '// @ts-ignore'}
module?.hot?.accept?.();

${optionsVar}

if (!global.view) {
global.view = start({
if (!globalThis.view) {
globalThis.view = start({
annotations,
storyEntries: normalizedStories,
${options ? ` ${options},` : ''}
});
} else {
updateView(global.view, annotations, normalizedStories${options ? `, ${options}` : ''});
updateView(globalThis.view, annotations, normalizedStories${options ? `, ${options}` : ''});
}

export const view${useJs ? '' : ': View'} = global.view;
export const view${useJs ? '' : ': View'} = globalThis.view;
`;

fs.writeFileSync(storybookRequiresLocation, fileContent, {
Expand Down
10 changes: 5 additions & 5 deletions packages/react-native/scripts/generate.test.js.snapshot
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
exports[`loader > writeRequires > when there are different file extensions > writes the story imports 1`] = `
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/file-extensions\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n}\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobal.STORIES = normalizedStories;\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!global.view) {\\n global.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(global.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = global.view;\\n"
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/file-extensions\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;\\n}\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobalThis.STORIES = normalizedStories;\\n\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!globalThis.view) {\\n globalThis.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(globalThis.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = globalThis.view;\\n"
`;

exports[`loader > writeRequires > when there is a configuration object > writes the story imports 1`] = `
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"ComponentsPrefix\\",\\n directory: \\"./scripts/mocks/configuration-objects/components\\",\\n files: \\"**/*.stories.tsx\\",\\n importPathMatcher: /^\\\\.(?:(?:^|\\\\/|(?:(?:(?!(?:^|\\\\/)\\\\.).)*?)\\\\/)(?!\\\\.)(?=.)[^/]*?\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './components',\\n true,\\n /^\\\\.(?:(?:^|\\\\/|(?:(?:(?!(?:^|\\\\/)\\\\.).)*?)\\\\/)(?!\\\\.)(?=.)[^/]*?\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n}\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobal.STORIES = normalizedStories;\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!global.view) {\\n global.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(global.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = global.view;\\n"
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"ComponentsPrefix\\",\\n directory: \\"./scripts/mocks/configuration-objects/components\\",\\n files: \\"**/*.stories.tsx\\",\\n importPathMatcher: /^\\\\.(?:(?:^|\\\\/|(?:(?:(?!(?:^|\\\\/)\\\\.).)*?)\\\\/)(?!\\\\.)(?=.)[^/]*?\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './components',\\n true,\\n /^\\\\.(?:(?:^|\\\\/|(?:(?:(?!(?:^|\\\\/)\\\\.).)*?)\\\\/)(?!\\\\.)(?=.)[^/]*?\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;\\n}\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobalThis.STORIES = normalizedStories;\\n\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!globalThis.view) {\\n globalThis.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(globalThis.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = globalThis.view;\\n"
`;

exports[`loader > writeRequires > when there is a story glob > writes the story imports 1`] = `
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/all-config-files\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n}\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobal.STORIES = normalizedStories;\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!global.view) {\\n global.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(global.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = global.view;\\n"
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/all-config-files\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;\\n}\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobalThis.STORIES = normalizedStories;\\n\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!globalThis.view) {\\n globalThis.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(globalThis.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = globalThis.view;\\n"
`;

exports[`loader > writeRequires > when there is no preview > does not add preview related stuff 1`] = `
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/no-preview\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n}\\n\\n\\nconst annotations = [\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobal.STORIES = normalizedStories;\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!global.view) {\\n global.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(global.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = global.view;\\n"
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView, View } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/no-preview\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n // @ts-ignore\\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\ndeclare global {\\n var view: View;\\n var STORIES: typeof normalizedStories;\\n var STORYBOOK_WEBSOCKET: { host: string; port: number } | undefined;\\n}\\n\\n\\nconst annotations = [\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobalThis.STORIES = normalizedStories;\\n\\n\\n// @ts-ignore\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!globalThis.view) {\\n globalThis.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(globalThis.view, annotations, normalizedStories);\\n}\\n\\nexport const view: View = globalThis.view;\\n"
`;

exports[`loader > writeRequires > when using js > writes the story imports without types 1`] = `
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/all-config-files\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n \\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobal.STORIES = normalizedStories;\\n\\n\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!global.view) {\\n global.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(global.view, annotations, normalizedStories);\\n}\\n\\nexport const view = global.view;\\n"
"/* do not change this file, it is auto generated by storybook. */\\nimport { start, updateView } from '@storybook/react-native';\\n\\nimport \\"@storybook/addon-ondevice-notes/register\\";\\nimport \\"@storybook/addon-ondevice-controls/register\\";\\nimport \\"@storybook/addon-ondevice-backgrounds/register\\";\\nimport \\"@storybook/addon-ondevice-actions/register\\";\\n\\nconst normalizedStories = [\\n {\\n titlePrefix: \\"\\",\\n directory: \\"./scripts/mocks/all-config-files\\",\\n files: \\"FakeStory.stories.tsx\\",\\n importPathMatcher: /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/,\\n \\n req: require.context(\\n './',\\n false,\\n /^\\\\.[\\\\\\\\/](?:FakeStory\\\\.stories\\\\.tsx)$/\\n ),\\n }\\n];\\n\\n\\n\\nconst annotations = [\\n require('./preview'),\\n require(\\"@storybook/react-native/preview\\")\\n];\\n\\nglobalThis.STORIES = normalizedStories;\\n\\n\\n\\nmodule?.hot?.accept?.();\\n\\n\\n\\nif (!globalThis.view) {\\n globalThis.view = start({\\n annotations,\\n storyEntries: normalizedStories,\\n\\n });\\n} else {\\n updateView(globalThis.view, annotations, normalizedStories);\\n}\\n\\nexport const view = globalThis.view;\\n"
`;
4 changes: 3 additions & 1 deletion packages/react-native/scripts/handle-args.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ function getArguments() {
)
.option('-j, --use-js', 'Use a js file for storybook.requires')
.option('-D, --no-doc-tools', 'Do not include doc tools in the storybook.requires file')
.option('-a, --absolute', 'Use absolute paths for story imports');
.option('-a, --absolute', 'Use absolute paths for story imports')
.option('-w, --host <host>', 'Host for websockets')
.option('-p, --port <port>', 'Port for websockets');

program.parse();

Expand Down
Loading