All files / src load-modules.js

100% Statements 29/29
100% Branches 14/14
100% Functions 7/7
100% Lines 29/29
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109      1x     7x     7x           5x 3x     5x   2x         1x 3x                         5x   5x 4x   4x   1x   3x       5x                                                   5x   5x 4x 4x   4x 3x   3x   1x   2x             4x 2x     4x     5x    
/**
 * Normalize exported value of ES6/CommonJS modules
 */
const importModule = (fn, moduleName) => {
  // Make sure imported modules that crash on load are omitted and not spoil the
  // rest of the build.
  try {
    // Modules are require calls wrapped in functions instead of plain paths
    // in order to help bundlers like Webpack know which files to bundle.
    let module = fn();
 
    // This is an implementation detail of Babel:
    // https://medium.com/@kentcdodds/misunderstanding-es6-modules-upgrading-babel-tears-and-a-solution-ad2d5ab93ce0#.skvldbg39
    // It looks like to be a "standard": https://github.com/esnext/es6-module-transpiler/issues/86 **for now**.
    // eslint-disable-next-line no-underscore-dangle
    if (module.__esModule) {
      module = module[moduleName] || module.default;
    }
 
    return module;
  } catch (e) {
    return null;
  }
};
 
// TODO: Improve ReactClass check!
const isReactClass = component =>
  typeof component === 'string' || typeof component === 'function';
 
/**
 * Input example:
 * {
 *   'Comment': () => require('/path/to/project/src/components/Comment.js'),
 * }
 * Output example:
 * {
 *   'Comment': [ReactClass],
 * }
 */
export function loadComponents(components) {
  const result = {};
 
  Object.keys(components).forEach(name => {
    const component = importModule(components[name], name);
 
    if (!component || !isReactClass(component)) {
      // eslint-disable-next-line no-console
      console.warn(`Could not load component '${name}'`);
    } else {
      result[name] = component;
    }
  });
 
  return result;
}
 
/**
 * Input example:
 * {
 *   'Comment': {
 *     'short': () => require('/path/to/project/src/components/__fixtures__/Comment/short.js'),
 *     'long': () => require('/path/to/project/src/components/__fixtures__/Comment/long.js'),
 *   },
 * }
 * Output example:
 * {
 *   'Comment': {
 *     'short': {
 *       'author': 'Sarcastic Sue'
 *       'body': ':)'
 *     },
 *     'long': {
 *       'author': 'Loud Larry'
 *       'body': 'Don't get me started on JavaScript...'
 *     },
 *   },
 * }
 */
export function loadFixtures(fixtures) {
  const result = {};
 
  Object.keys(fixtures).forEach(componentName => {
    const componentFixtures = fixtures[componentName];
    const componentResult = {};
 
    Object.keys(componentFixtures).forEach(name => {
      const fixture = importModule(componentFixtures[name], name);
 
      if (!fixture) {
        // eslint-disable-next-line no-console
        console.warn(`Could not load fixture '${name}' of '${componentName}'`);
      } else {
        componentResult[name] = fixture;
      }
    });
 
    // Allow users to browse components before creating fixtures
    // TODO: Create more than empty defaults. Alongside a more metadata-rich
    // input, we could generate default fixtures to match PropTypes.
    if (!Object.keys(componentResult).length) {
      componentResult['no props (auto)'] = {};
    }
 
    result[componentName] = componentResult;
  });
 
  return result;
}