-
Notifications
You must be signed in to change notification settings - Fork 5
/
macro.js
116 lines (106 loc) · 3.67 KB
/
macro.js
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
110
111
112
113
114
115
116
const path = require('path');
const child_process = require('child_process');
const { createMacro } = require("babel-plugin-macros");
/*
USAGE:
const loadRoutes = require("meta-router/macro");
app.use(require('meta-router/middleware').match(loadRoutes("/path/to/routes.json")));
*/
module.exports = createMacro(loadRoutesMacro);
function loadRoutesMacro({ references, state, babel }) {
const filename = state.file.opts.filename;
const dirname = path.dirname(filename);
const t = babel.types;
references.default.forEach(referencePath => {
if (referencePath.parentPath.type === "CallExpression") {
const callExpressionPath = referencePath.parentPath;
const routesPath = getArguments(callExpressionPath)[0];
if (routesPath === undefined) {
throw new Error(`There was no path passed to the meta-router/macro: ${callExpressionPath.getSource()}.`);
}
const resolvedRoutesPath = path.resolve(dirname, routesPath);
const replacementAST = loadRoutesAsAST(resolvedRoutesPath, dirname, t);
referencePath.hub.file.path.pushContainer(
'body',
t.expressionStatement(
t.callExpression(
t.identifier('require'),
[t.stringLiteral(routesPath)]
)
)
);
callExpressionPath.replaceWith(replacementAST);
} else {
throw new Error(
`The meta-router/macro must be used as a function: \`${referencePath
.findParent(t.isExpression)
.getSource()}\`.`,
);
}
});
}
function getArguments(callExpressionPath) {
let args;
try {
args = callExpressionPath.get("arguments").map(arg => arg.evaluate().value);
} catch (err) {
// swallow error, print better error below
}
if (args === undefined) {
throw new Error(
`There was a problem evaluating the arguments for the code: ${callExpressionPath.getSource()}. ` +
`If the arguments are dynamic, please make sure that their values are statically deterministic.`,
);
}
return args;
}
function loadRoutesAsAST(routesPath, dirname, types) {
const config = evalSync(cb => `require("./lib/routes-loader").loadWithoutRequire(${JSON.stringify(routesPath)}, ${cb})`);
return toASTWithRequires(config, dirname, types);
}
function evalSync(getCode) {
const cb = "(err, value) => process.stdout.write(JSON.stringify(value))";
const code = getCode(cb);
const json = child_process.spawnSync(process.argv[0], ['-e', code], { cwd:__dirname }).stdout.toString();
return JSON.parse(json);
}
function toASTWithRequires(value, dirname, t) {
if (value === null) {
return t.nullLiteral();
}
switch (typeof value) {
case 'number':
return t.numericLiteral(value);
case 'string':
return t.stringLiteral(value);
case 'boolean':
return t.booleanLiteral(value);
case 'undefined':
return t.unaryExpression('void', t.numericLiteral(0), true);
default:
if (Array.isArray(value)) {
return t.arrayExpression(value.map(v => toASTWithRequires(v, dirname, t)));
}
if (value.require && value.path) {
value.path = path.relative(dirname, value.path);
if (value.path !== '.') {
value.path = './' + value.path;
}
const requireCall = t.callExpression(t.identifier('require'), [t.stringLiteral(value.path)]);
if (value.methodName) {
return t.memberExpression(requireCall, t.identifier(value.methodName));
}
return requireCall;
}
return t.objectExpression(Object.keys(value)
.filter((k) => {
return typeof value[k] !== 'undefined';
})
.map((k) => {
return t.objectProperty(
t.stringLiteral(k),
toASTWithRequires(value[k], dirname, t)
);
}));
}
}