Skip to content

Commit 2ffd8db

Browse files
author
Artem Sapegin
committed
Fix: Mixed named/default imports in examples
import a, {b} from 'a'
1 parent 9312ecb commit 2ffd8db

File tree

6 files changed

+135
-22
lines changed

6 files changed

+135
-22
lines changed

src/client/utils/__tests__/transpileImports.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
import transpileImports from '../transpileImports';
22

33
describe('transpileImports', () => {
4+
test('transpile default imports', () => {
5+
const result = transpileImports(`import B from 'cat'`);
6+
expect(result).toMatchInlineSnapshot(`"const B = require('cat');"`);
7+
});
8+
9+
test('transpile named imports', () => {
10+
const result = transpileImports(`import {B} from 'cat'`);
11+
expect(result).toMatchInlineSnapshot(`
12+
"const cat$0 = require('cat');
13+
const B = cat$0.B;"
14+
`);
15+
});
16+
17+
test('transpile mixed imports', () => {
18+
const result = transpileImports(`import A, {B} from 'cat'`);
19+
expect(result).toMatchInlineSnapshot(`
20+
"const A = require('cat');
21+
const B = A.B;"
22+
`);
23+
});
24+
425
test('transpile multiple import statements', () => {
526
const result = transpileImports(`/**
627
* Some important comment

src/client/utils/getComponent.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
// @flow
2+
3+
type Module = { [name: string]: any } | (() => any);
4+
15
/**
26
* Given a component module and a name,
37
* return the appropriate export.
48
* See /docs/Components.md
5-
*
6-
* @param {object} module
7-
* @param {string} name
8-
* @return {function|object}
99
*/
10-
export default function getComponent(module, name) {
10+
export default function getComponent(module: Module, name: string): Module {
1111
//
1212
// If the module defines a default export, return that
1313
// e.g.
@@ -31,7 +31,6 @@ export default function getComponent(module, name) {
3131
if (!module.__esModule && typeof module === 'function') {
3232
return module;
3333
}
34-
const moduleKeys = Object.keys(module);
3534

3635
// If the module exports just one named export, return that
3736
// e.g.
@@ -40,8 +39,9 @@ export default function getComponent(module, name) {
4039
// export function Component() { ... }
4140
// ```
4241
//
43-
if (moduleKeys.length === 1) {
44-
return module[moduleKeys[0]];
42+
const namedExports = Object.keys(module);
43+
if (namedExports.length === 1) {
44+
return module[namedExports[0]];
4545
}
4646

4747
// If the module exports a named export with the same name as the

src/client/utils/rewriteImports.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Temporary copy to fix
2+
// https://github.com/lukeed/rewrite-imports/issues/10
3+
4+
const UNNAMED = /import\s*['"]([^'"]+)['"];?/gi;
5+
const NAMED = /import\s*(\*\s*as)?\s*(\w*?)\s*,?\s*(?:\{([\s\S]*?)\})?\s*from\s*['"]([^'"]+)['"];?/gi;
6+
7+
function alias(key) {
8+
key = key.trim();
9+
const name = key.split(' as ');
10+
if (name.length > 1) {
11+
key = name.shift();
12+
}
13+
return { key, name: name[0] };
14+
}
15+
16+
function generate(keys, dep, base, fn) {
17+
const tmp =
18+
base ||
19+
dep
20+
.split('/')
21+
.pop()
22+
.replace(/\W/g, '_') +
23+
'$' +
24+
num++; // uniqueness
25+
const name = base || alias(tmp).name;
26+
27+
dep = `${fn}('${dep}')`;
28+
29+
let obj;
30+
let out = `const ${name} = ${dep};`;
31+
32+
keys.forEach(key => {
33+
obj = alias(key);
34+
out += `\nconst ${obj.name} = ${tmp}.${obj.key};`;
35+
});
36+
37+
return out;
38+
}
39+
40+
let num;
41+
export default function(str, fn = 'require') {
42+
num = 0;
43+
return str
44+
.replace(NAMED, (_, asterisk, base, req, dep) =>
45+
generate(req ? req.split(',') : [], dep, base, fn)
46+
)
47+
.replace(UNNAMED, (_, dep) => `${fn}('${dep}');`);
48+
}

src/client/utils/transpileImports.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22
import walkes from 'walkes';
3-
import rewriteImports from 'rewrite-imports';
3+
import rewriteImports from './rewriteImports';
44
import getAst from './getAst';
55

66
const hasImports = (code: string): boolean => !!code.match(/import[\S\s]+?['"]([^'"]+)['"];?/m);
Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
11
import requireInRuntime from '../requireInRuntime';
22

33
const map = {
4-
a: 42,
5-
b: 43,
4+
a: () => 'a',
5+
b: () => 'b',
6+
c: {
7+
default: () => 'c.default',
8+
},
9+
d: {
10+
named: () => 'd.named',
11+
},
12+
e: {
13+
default: () => 'e.default',
14+
named: () => 'e.named',
15+
},
616
};
717

8-
it('should return a module from the map', () => {
18+
test('return a module from the map', () => {
919
const result = requireInRuntime(map, 'a');
10-
expect(result).toBe(map.a);
20+
expect(result).toBeDefined();
21+
expect(result()).toBe('a');
1122
});
1223

13-
it('should throw if module is not in the map', () => {
14-
const fn = () => requireInRuntime(map, 'c');
24+
test('return a default export', () => {
25+
const result = requireInRuntime(map, 'c');
26+
expect(result).toBeDefined();
27+
expect(result()).toBe('c.default');
28+
});
29+
30+
test('return a named export', () => {
31+
const result = requireInRuntime(map, 'd');
32+
expect(result).toBeDefined();
33+
expect(result.named).toBeDefined();
34+
expect(result.named()).toBe('d.named');
35+
});
36+
37+
test('return both a default and a named exports', () => {
38+
const result = requireInRuntime(map, 'e');
39+
expect(result).toBeDefined();
40+
expect(result.named).toBeDefined();
41+
expect(result()).toBe('e.default');
42+
expect(result.named()).toBe('e.named');
43+
});
44+
45+
test('throw if module is not in the map', () => {
46+
const fn = () => requireInRuntime(map, 'pizza');
1547
expect(fn).toThrowError('require() statements can be added');
1648
});
Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
1+
// @flow
2+
3+
type Module = { [name: string]: any } | (() => any);
4+
type RequireMap = { [filepath: string]: Module };
5+
6+
const getModule = (mod: Module): Module => {
7+
if (!mod.default) {
8+
return mod;
9+
}
10+
11+
// Merge named exports with default export to allow requiring like this:
12+
// const a, {b} = requireInRuntime('a')
13+
const merged = mod.default;
14+
Object.assign(merged, mod);
15+
return merged;
16+
};
17+
118
/**
219
* Return module from a given map (like {react: require('react')}) or throw.
320
* We alllow to require modules only from Markdown examples (won’t work dinamically becasue we need to know all required
421
* modules in advance to be able to bundle them with the code).
5-
*
6-
* @param {object} requireMap
7-
* @param {string} filepath
8-
* @return {object}
922
*/
10-
export default function requireInRuntime(requireMap, filepath) {
23+
export default function requireInRuntime(requireMap: RequireMap, filepath: string): Module {
1124
if (!(filepath in requireMap)) {
1225
throw new Error(
1326
`import or require() statements can be added only by editing a Markdown example file: ${filepath}`
1427
);
1528
}
16-
const mod = requireMap[filepath];
17-
return mod.default || mod;
29+
return getModule(requireMap[filepath]);
1830
}

0 commit comments

Comments
 (0)