Skip to content

Commit

Permalink
feat: config location is now flexible and paths relative from main.js (
Browse files Browse the repository at this point in the history
…#278)

- you can now specify a config location and you can change the folder name #278
    - note that this folder should still contain all the same files
    - `—absolute` option will use absolute imports in the requires.js file
    - paths in general should work better with the watcher and get stories script
- In main.js stories are relative from the config directory and no longer add "../", this should fix absolute paths and other bugs #278
- `sbn-get-stories` and `sbn-watcher` are now `sb-rn-get-stories`  and `sb-rn-watcher`
  • Loading branch information
dannyhw authored Oct 11, 2021
1 parent 9be5673 commit fc7f666
Show file tree
Hide file tree
Showing 27 changed files with 468 additions and 135 deletions.
7 changes: 2 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,8 @@ jobs:
at: .
- run:
name: Test
command: yarn test --coverage --w2 --core
- persist_to_workspace:
root: .
paths:
- coverage
command: yarn test

workflows:
test:
jobs:
Expand Down
13 changes: 2 additions & 11 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,11 @@ node_modules
docs/public
storybook-static
built-storybooks
lib/cli/test
lib/codemod/src/transforms/__testfixtures__
scripts/storage

*.bundle.js
*.js.map
*.d.ts
examples/ember-cli/.storybook/preview-head.html
examples/official-storybook/tests/addon-jest.test.js
examples/cra-ts-kitchen-sink/*.json
examples/cra-ts-kitchen-sink/public/*
examples/cra-ts-essentials/*.json
examples/cra-ts-essentials/public/*
examples/rax-kitchen-sink/src/document/*

.yarn
!.remarkrc.js
!.babelrc.js
Expand All @@ -30,6 +22,5 @@ examples/native/e2e/init.ts
examples/native/ios
examples/native/android
examples/native/metro.config.js
examples/crna-kitchen-sink/metro.config.js
storybook.requires.js
jest.config.js
2 changes: 1 addition & 1 deletion app/react-native-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@storybook/core": "^6",
"@storybook/core-events": "^6",
"@storybook/ui": "^6",
"commander": "^4.0.1",
"commander": "^8.2.0",
"core-js": "^3.0.1",
"global": "^4.3.2",
"react": "^17.0.2",
Expand Down
3 changes: 3 additions & 0 deletions app/react-native/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};
25 changes: 21 additions & 4 deletions app/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"license": "MIT",
"main": "dist/index.js",
"bin": {
"sbn-get-stories": "./bin/get-stories.js",
"sbn-watcher": "./bin/watcher.js"
"sb-rn-get-stories": "./bin/get-stories.js",
"sb-rn-watcher": "./bin/watcher.js"
},
"files": [
"bin/**/*",
Expand All @@ -32,7 +32,19 @@
],
"scripts": {
"preprepare": "rm -rf dist/",
"prepare": "tsc"
"prepare": "tsc",
"test": "jest"
},
"jest": {
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"preset": "react-native"
},
"dependencies": {
"@emotion/core": "^10.0.20",
Expand All @@ -46,13 +58,18 @@
"@storybook/core-events": "^6",
"@storybook/csf": "0.0.1",
"chokidar": "^3.5.1",
"commander": "^8.2.0",
"emotion-theming": "^10.0.19",
"glob": "^7.1.7",
"prettier": "^2.4.1",
"react-native-swipe-gestures": "^1.0.5",
"util": "^0.12.4"
},
"devDependencies": {
"@types/react-native": "^0.65.1"
"@types/react-native": "^0.65.1",
"babel-jest": "^26.6.3",
"jest": "^26.6.3",
"react-test-renderer": "17.0.2"
},
"peerDependencies": {
"@react-native-async-storage/async-storage": ">=1",
Expand Down
83 changes: 83 additions & 0 deletions app/react-native/scripts/__snapshots__/loader.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

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. */
import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native';
import \\"@storybook/addon-ondevice-notes/register\\";
import \\"@storybook/addon-ondevice-controls/register\\";
import \\"@storybook/addon-ondevice-backgrounds/register\\";
import \\"@storybook/addon-ondevice-actions/register\\";
import { argsEnhancers } from \\"@storybook/addon-actions/dist/modern/preset/addArgs\\"
import { decorators, parameters } from './preview';
if (decorators) {
decorators.forEach((decorator) => addDecorator(decorator));
}
if (parameters) {
addParameters(parameters);
}
argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer))
const getStories=() => {
return [require(\\"./FakeStory.stories.tsx\\")];
}
configure(getStories, module, false)
"
`;

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. */
import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native';
import \\"@storybook/addon-ondevice-notes/register\\";
import \\"@storybook/addon-ondevice-controls/register\\";
import \\"@storybook/addon-ondevice-backgrounds/register\\";
import \\"@storybook/addon-ondevice-actions/register\\";
import { argsEnhancers } from \\"@storybook/addon-actions/dist/modern/preset/addArgs\\"
argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer))
const getStories=() => {
return [require(\\"./FakeStory.stories.tsx\\")];
}
configure(getStories, module, false)
"
`;

exports[`loader writeRequires when there is no story glob or addons writes no story imports or addons 1`] = `
"
/* do not change this file, it is auto generated by storybook. */
import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native';
const getStories=() => {
return [];
}
configure(getStories, module, false)
"
`;
4 changes: 3 additions & 1 deletion app/react-native/scripts/get-stories.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const { writeRequires } = require('./loader');
const { getArguments } = require('./handle-args');
const args = getArguments();

writeRequires();
writeRequires(args);
18 changes: 18 additions & 0 deletions app/react-native/scripts/handle-args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function getArguments() {
const { program } = require('commander');

program
.description('Getter and watcher for react native storybook')
.option(
'-c, --config-path <path>',
'The path to your config folder relative to your project-dir',
'./.storybook'
)
.option('-a, --absolute', 'Use absolute paths for story imports');

program.parse();

return program.opts();
}

module.exports = { getArguments };
121 changes: 55 additions & 66 deletions app/react-native/scripts/loader.js
Original file line number Diff line number Diff line change
@@ -1,107 +1,96 @@
const path = require('path');
const fs = require('fs');
const glob = require('glob');
const storybookRequiresLocation = '/.storybook/storybook.requires.js';
const prettier = require('prettier');

const cwd = process.cwd();

const previewImports = `
import { decorators, parameters } from './preview';
if (decorators) {
decorators.forEach((decorator) => addDecorator(decorator));
}
if (parameters) {
addParameters(parameters);
}
`;

function requireUncached(module) {
delete require.cache[require.resolve(module)];
return require(module);
}

function getMainPath() {
const cwd = process.cwd();
return path.join(cwd, '.storybook/main.js');
function getMain({ configPath }) {
const mainPath = path.resolve(cwd, configPath, 'main.js');
return requireUncached(mainPath);
}

function getPreviewPath() {
const cwd = process.cwd();
return path.join(cwd, '.storybook/preview.js');
}

function getPreviewExists() {
return fs.existsSync(getPreviewPath());
}

function getGlobs() {
// we need to invalidate the cache because otherwise the latest changes don't get loaded
const { stories: storyGlobs } = requireUncached(getMainPath());

return storyGlobs;
function getPreviewExists({ configPath }) {
const previewPath = path.resolve(cwd, configPath, 'preview.js');
return fs.existsSync(previewPath);
}

function getAddons() {
const { addons } = requireUncached(getMainPath());
function writeRequires({ configPath, absolute = false }) {
const storybookRequiresLocation = path.resolve(cwd, configPath, 'storybook.requires.js');

return addons;
}

function getPaths() {
return getGlobs().reduce((acc, storyGlob) => {
const paths = glob.sync(storyGlob);
return [...acc, ...paths.map((storyPath) => `../${storyPath}`)];
const main = getMain({ configPath });
const storyPaths = main.stories.reduce((acc, storyGlob) => {
const paths = glob.sync(storyGlob, { cwd: path.resolve(cwd, configPath), absolute });
return [...acc, ...paths];
}, []);
}

function writeRequires() {
const cwd = process.cwd();
fs.writeFileSync(storybookRequiresLocation, '');

const storyPaths = getPaths();
const addons = getAddons();
const previewExists = getPreviewExists({ configPath });

fs.writeFileSync(path.join(cwd, storybookRequiresLocation), '');

const previewExists = getPreviewExists();
let previewJs = previewExists
? `
import { decorators, parameters } from './preview';
if (decorators) {
decorators.forEach((decorator) => addDecorator(decorator));
}
if (parameters) {
addParameters(parameters);
}`
: '';
let previewJs = previewExists ? previewImports : '';

const storyRequires = storyPaths.map((storyPath) => `\t\trequire("${storyPath}")`).join(', \n');
const path_array_str = `[\n${storyRequires}\n\t]`;
const storyRequires = storyPaths.map((storyPath) => `require("${storyPath}")`).join(',');
const path_array_str = `[${storyRequires}]`;

const registerAddons = addons.map((addon) => `import "${addon}/register";`).join('\n');
const registerAddons = main.addons.map((addon) => `import "${addon}/register";`).join('\n');
let enhancersImport = '';
let enhancers = '';

// TODO: implement presets or something similar
if (addons.includes('@storybook/addon-ondevice-actions')) {
enhancers = `import { argsEnhancers } from '@storybook/addon-actions/dist/modern/preset/addArgs';
argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer))`;
if (main.addons.includes('@storybook/addon-ondevice-actions')) {
enhancersImport =
'import { argsEnhancers } from "@storybook/addon-actions/dist/modern/preset/addArgs"';
enhancers = 'argsEnhancers.forEach(enhancer => addArgsEnhancer(enhancer))';
}

const fileContent = `
/*
do not change this file, it is auto generated by storybook.
*/
import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native';
${registerAddons}
/* do not change this file, it is auto generated by storybook. */
${enhancers}
import { configure, addDecorator, addParameters, addArgsEnhancer } from '@storybook/react-native';
${previewJs}
${registerAddons}
const getStories=() => {
return ${path_array_str};
}
configure(getStories, module, false)
${enhancersImport}
${previewJs}
${enhancers}
const getStories=() => {
return ${path_array_str};
}
configure(getStories, module, false)
`;

fs.writeFileSync(path.join(cwd, storybookRequiresLocation), fileContent, {
const formattedFileContent = prettier.format(fileContent, { parser: 'babel' });

fs.writeFileSync(storybookRequiresLocation, formattedFileContent, {
encoding: 'utf8',
flag: 'w',
});
}

module.exports = {
writeRequires,
getGlobs,
getMainPath,
getMain,
getPreviewExists,
getPreviewPath,
};
Loading

0 comments on commit fc7f666

Please sign in to comment.