UNPKG

4.14 kBJavaScriptView Raw
1"format cjs";
2var esprima = require("esprima"),
3 optionsNormalize = require("./options_normalize"),
4 getAst = require("./get_ast"),
5 types = require("ast-types"),
6 n = types.namedTypes;
7
8var dirnameExp = /__dirname/;
9var globalExp = /global/;
10
11module.exports = function(load, options) {
12 var ast = getAst(load);
13
14 var source = load.source;
15 var cjsOptions = {
16 hasDirname: dirnameExp.test(source),
17 hasGlobal: globalExp.test(source),
18 duplicateCjsDependencies: options.duplicateCjsDependencies
19 };
20 cjsOptions.needsFunctionWrapper =
21 cjsOptions.hasDirname || cjsOptions.hasGlobal;
22
23 if (options && (options.normalizeMap || options.normalize)) {
24 normalizeModuleIdentifiers(ast, load, options);
25 }
26
27 var normalizedName;
28 if (options.namedDefines) {
29 normalizedName = optionsNormalize(
30 options,
31 load.name,
32 load.name,
33 load.address
34 );
35 }
36 ast = defineInsert(normalizedName, ast.body, cjsOptions);
37
38 return ast;
39};
40
41/**
42 * Given an ast, calls the visitor with the first argument of each `require` call
43 * @param {Object} ast - The ast to walk
44 * @param {Function} cb - The visitor function called on each match
45 */
46function visitRequireArgument(ast, cb) {
47 types.visit(ast, {
48 visitCallExpression: function(path) {
49 if (this.isRequireExpression(path.node)) {
50 var arg = path.getValueProperty("arguments")[0];
51
52 if (n.Literal.check(arg)) {
53 cb(arg);
54 }
55 }
56
57 this.traverse(path);
58 },
59
60 isRequireExpression(node) {
61 return n.Identifier.check(node.callee) && node.callee.name === "require";
62 }
63 });
64}
65
66/**
67 * Traverses the AST (CJS) and normalizes the module identifiers
68 */
69function normalizeModuleIdentifiers(ast, load, options) {
70 visitRequireArgument(ast, function(argument) {
71 var normalized = optionsNormalize(
72 options,
73 argument.value,
74 load.name,
75 load.address
76 );
77
78 argument.value = normalized;
79 argument.raw = `"${normalized}"`;
80 });
81}
82
83/**
84 * Given an AST, returns the module identifiers passed to `require` calls
85 * @param {Object} ast - The ast to walk
86 * @return {Array.<String>} The module identifiers
87 */
88function collectDependenciesIds(ast) {
89 var ids = [];
90
91 visitRequireArgument(ast, function(argument) {
92 ids.push(argument.value);
93 });
94
95 return ids;
96}
97
98function defineInsert(name, body, options) {
99 // Add in the function wrapper.
100 var wrapper = defineWrapper(options);
101
102 var code = makeDefineFunctionCode(
103 name,
104 options.duplicateCjsDependencies ? collectDependenciesIds(body) : [],
105 wrapper
106 );
107
108 var ast = esprima.parse(code);
109 var astBody = body || [];
110
111 var innerFunctions = 0;
112 var expectedFunctions = options.needsFunctionWrapper ? 2 : 1;
113
114 types.visit(ast, {
115 visitFunctionExpression: function(path) {
116 if (this.isModuleFactory()) {
117 var functionBody = path.getValueProperty("body").body;
118
119 astBody.forEach(function(part) {
120 functionBody.push(part);
121 });
122
123 // stop traversing the tree
124 this.abort();
125 }
126
127 // keep traversing the tree
128 this.traverse(path);
129 },
130
131 isModuleFactory: function() {
132 innerFunctions += 1;
133 return innerFunctions === expectedFunctions;
134 }
135 });
136
137 return ast;
138}
139
140function makeDefineFunctionCode(name, cjsDeps, body) {
141 var deps = "";
142
143 if (cjsDeps.length) {
144 deps = ["require", "exports", "module"]
145 .concat(cjsDeps)
146 .map(function(x) {
147 return `"${x}"`;
148 })
149 .join(", ")
150 }
151
152 var defineArgs = [
153 name ? `"${name}"` : null,
154 deps ? `[ ${deps} ]` : null,
155 `function(require, exports, module) { ${body} }`
156 ];
157
158 return `define(${defineArgs.filter(isTruthy).join(", ")});`;
159}
160
161function isTruthy(x) {
162 return Boolean(x);
163}
164
165function defineWrapper(options) {
166 // Add in the function wrapper.
167 var wrapper = "";
168 if (options.needsFunctionWrapper) {
169 wrapper += "(function(";
170 if (options.hasGlobal) {
171 wrapper += "global, ";
172 }
173 if (options.hasDirname) {
174 wrapper += "__dirname, ";
175 }
176 wrapper += "require, exports, module";
177 wrapper += "){\n";
178 wrapper += "})(";
179
180 if (options.hasGlobal) {
181 wrapper += "function() { return this; }(), ";
182 }
183 if (options.hasDirname) {
184 wrapper += '"/", ';
185 }
186 wrapper += "require, exports, module";
187 wrapper += ");";
188 }
189
190 return wrapper;
191}