Skip to content

Commit fb1d2ec

Browse files
marcdavi-essapegin
authored andcommitted
Feat: Add limited support for named exports (#825)
Closes #820, #633.
1 parent 6ec5a08 commit fb1d2ec

File tree

6 files changed

+184
-17
lines changed

6 files changed

+184
-17
lines changed

docs/Components.md

+58
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<!-- toc -->
66

77
* [Finding components](#finding-components)
8+
* [Loading and exposing components](#loading-and-exposing-components)
89
* [Sections](#sections)
910
* [Limitations](#limitations)
1011

@@ -41,6 +42,63 @@ module.exports = {
4142
4243
> **Note:** Use [getComponentPathLine](Configuration.md#getcomponentpathline) option to change a path you see below a component name.
4344
45+
## Loading and exposing components
46+
47+
Styleguidist _loads_ your components and _exposes_ them globally for your examples to consume.
48+
49+
### Identifier
50+
51+
It will try to use the `displayName` of your component as the identifier. If it cannot understand a `displayName` (for example if it is dynamically generated), it will fall back to something it can understand.
52+
53+
In each of the following cases, the global identifier will be `Component`.
54+
55+
| Path | Code | Styleguidist understands |
56+
| ---- | ---- | ------------------------ |
57+
| /whatever.js | `export default function Component() { ... }` | displayName |
58+
| /whatever.js | `export default function SomeName() { ... }`<br>`SomeName.displayName = 'Component';` | displayName |
59+
| /whatever.js | `export default function Component() { ... }`<br>`Component.displayName = dynamicNamer();` | displayName at declaration
60+
| /component.js | `const name = 'SomeName';`<br>`const componentMap = {`<br>`[name]: function() { ... }`<br>`};`<br>`export default componentMap[name];` | File name |
61+
| /component/index.js | `const name = 'SomeName';`<br>`const componentMap = {`<br>`[name]: function() { ... }`<br>`};`<br>`export default componentMap[name];` | Folder name |
62+
63+
64+
### Default vs named exports
65+
66+
Stylegudist will use an ECMAScript module’s `default` export or CommonJS `module.exports` if they are defined.
67+
68+
```javascript
69+
// /component.js
70+
export default function Component() { ... }
71+
// will be exposed globally as Component
72+
73+
// /component.js
74+
function Component() { ... }
75+
module.exports = Component;
76+
// will be exposed globally as Component
77+
```
78+
79+
If you use only named exports, Styleguidist will expose named exports from modules as follows...
80+
81+
If there is only one named export, it will expose that.
82+
83+
```javascript
84+
// /component.js
85+
export function Component() { ... }
86+
// will be exposed globally as Component
87+
```
88+
89+
If there are several named exports, it will expose the named export which has the same name as the understood identifier.
90+
91+
```javascript
92+
// /component.js
93+
export function someUtil() { ... }
94+
// will not be exposed
95+
96+
export function Component() { ... }
97+
// will be exposed globally as Component
98+
```
99+
100+
If you export several React components as named exports from a single module, Styleguidist is likely to behave unreliably. If it cannot understand which named export to expose, you may not be able to access that export.
101+
44102
## Sections
45103

46104
Group components into sections or add extra Markdown documents to your style guide.

docs/Thirdparties.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
## How Styleguidist works
1919

20-
Styleguidist always uses the default export to _load_ your components but it uses [react-docgen](https://github.com/reactjs/react-docgen) to _generate documentation_ which may require changes in your code to work properly.
20+
Styleguidist _loads_ your components (see [Loading and exposing components](Components.md#loading-and-exposing-components) for more) but it uses [react-docgen](https://github.com/reactjs/react-docgen) to _generate documentation_ which may require changes in your code to work properly.
2121

2222
React-docgen reads your components as static text files and looks for patterns like class or function declarations that looks like React components. It does not run any JavaScript code, so, if your component is dynamically generated, is wrapped in a higher-order component, or is split into several files, then react-docgen may not understand it.
2323

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import getComponent from '../getComponent';
2+
3+
describe('getComponent', () => {
4+
describe('if there is a default export in the module', () => {
5+
it('should return that', () => {
6+
const module = { default: 'useMe' };
7+
const actual = getComponent(module);
8+
expect(actual).toBe(module.default);
9+
});
10+
});
11+
12+
describe('if it is a CommonJS module and exports a function', () => {
13+
it('should return the module', () => {
14+
const testCases = [() => {}, function() {}, class Class {}];
15+
testCases.forEach(testCase => {
16+
const actual = getComponent(testCase);
17+
expect(actual).toBe(testCase);
18+
});
19+
});
20+
});
21+
22+
describe('if there is only one named export in the module', () => {
23+
it('should return that', () => {
24+
const module = { oneNamedExport: 'isLonely' };
25+
const actual = getComponent(module);
26+
expect(actual).toBe(module.oneNamedExport);
27+
});
28+
});
29+
30+
describe('if there is a named export whose name matches the name argument', () => {
31+
it('should return that', () => {
32+
const name = 'Component';
33+
const module = { [name]: 'isNamed', OtherComponent: 'isAlsoNamed' };
34+
const actual = getComponent(module, name);
35+
expect(actual).toBe(module[name]);
36+
});
37+
});
38+
39+
describe('if there is more than one named export and no matching name', () => {
40+
it('should fall back on returning the module as a whole', () => {
41+
const name = 'Component';
42+
const module = { RandomName: 'isNamed', confusingExport: 123 };
43+
const actual = getComponent(module, name);
44+
expect(actual).toBe(module);
45+
});
46+
});
47+
});
+15-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import globalizeComponent from '../globalizeComponent';
22

3-
afterEach(() => {
4-
delete global.Foo;
5-
});
3+
const component = { module: 'someModule', name: 'SomeName' };
64

75
describe('globalizeComponent', () => {
8-
it('should set component’s module as a global variable', () => {
9-
const globalsCount = Object.keys(global).length;
10-
globalizeComponent({
11-
name: 'Foo',
12-
props: {},
13-
module: 13,
14-
});
15-
expect(Object.keys(global).length).toBe(globalsCount + 1);
16-
expect(global.Foo).toBe(13);
6+
afterEach(() => {
7+
delete global[component.name];
8+
});
9+
10+
it('should not add anything as a global variable if there is no component name', () => {
11+
expect(global[component.name]).toBeUndefined();
12+
globalizeComponent({});
13+
expect(global[component.name]).toBeUndefined();
14+
});
15+
16+
it('should set the return value of getComponent as a global variable', () => {
17+
expect(global[component.name]).toBeUndefined();
18+
globalizeComponent(component);
19+
expect(global[component.name]).toBe(component.module);
1720
});
1821
});

src/utils/getComponent.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Given a component module and a name,
3+
* return the appropriate export.
4+
* See /docs/Components.md
5+
*
6+
* @param {object} module
7+
* @param {string} name
8+
* @return {function|object}
9+
*/
10+
export default function getComponent(module, name) {
11+
//
12+
// If the module defines a default export, return that
13+
// e.g.
14+
//
15+
// ```
16+
// export default function Component() { ... }
17+
// ```
18+
//
19+
if (module.default) {
20+
return module.default;
21+
}
22+
23+
// If it is a CommonJS module which exports a function, return that
24+
// e.g.
25+
//
26+
// ```
27+
// function Component() { ... }
28+
// module.exports = Component;
29+
// ```
30+
//
31+
if (!module.__esModule && typeof module === 'function') {
32+
return module;
33+
}
34+
const moduleKeys = Object.keys(module);
35+
36+
// If the module exports just one named export, return that
37+
// e.g.
38+
//
39+
// ```
40+
// export function Component() { ... }
41+
// ```
42+
//
43+
if (moduleKeys.length === 1) {
44+
return module[moduleKeys[0]];
45+
}
46+
47+
// If the module exports a named export with the same name as the
48+
// understood Component identifier, return that
49+
// e.g.
50+
//
51+
// ```
52+
// // /component.js
53+
// export function someUtil() { ... }
54+
// export Component() { ... } // if identifier is Component, return this named export
55+
// ```
56+
//
57+
// Else return the module itself
58+
//
59+
return module[name] || module;
60+
}

src/utils/globalizeComponent.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import getComponent from './getComponent';
2+
13
/**
24
* Expose component as global variables.
35
*
@@ -8,8 +10,5 @@ export default function globalizeComponent(component) {
810
return;
911
}
1012

11-
global[component.name] =
12-
!component.props.path || component.props.path === 'default'
13-
? component.module.default || component.module
14-
: component.module[component.props.path];
13+
global[component.name] = getComponent(component.module, component.name);
1514
}

0 commit comments

Comments
 (0)