UNPKG

12.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
8
9var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
10
11exports.default = function (_ref) {
12 var t = _ref.types;
13 var template = _ref.template;
14
15 function matchesPatterns(path, patterns) {
16 return !!(0, _arrayFind2.default)(patterns, function (pattern) {
17 return t.isIdentifier(path.node, { name: pattern }) || path.matchesPattern(pattern);
18 });
19 }
20
21 function isReactLikeClass(node) {
22 return !!(0, _arrayFind2.default)(node.body.body, function (classMember) {
23 return t.isClassMethod(classMember) && t.isIdentifier(classMember.key, { name: 'render' });
24 });
25 }
26
27 function isReactLikeComponentObject(node) {
28 return t.isObjectExpression(node) && !!(0, _arrayFind2.default)(node.properties, function (objectMember) {
29 return (t.isObjectProperty(objectMember) || t.isObjectMethod(objectMember)) && (t.isIdentifier(objectMember.key, { name: 'render' }) || t.isStringLiteral(objectMember.key, { value: 'render' }));
30 });
31 }
32
33 // `foo({ displayName: 'NAME' });` => 'NAME'
34 function getDisplayName(node) {
35 var property = (0, _arrayFind2.default)(node.arguments[0].properties, function (node) {
36 return node.key.name === 'displayName';
37 });
38 return property && property.value.value;
39 }
40
41 function hasParentFunction(path) {
42 return !!path.findParent(function (parentPath) {
43 return parentPath.isFunction();
44 });
45 }
46
47 // wrapperFunction("componentId")(node)
48 function wrapComponent(node, componentId, wrapperFunctionId) {
49 return t.callExpression(t.callExpression(wrapperFunctionId, [t.stringLiteral(componentId)]), [node]);
50 }
51
52 // `{ name: foo }` => Node { type: "ObjectExpression", properties: [...] }
53 function toObjectExpression(object) {
54 var properties = Object.keys(object).map(function (key) {
55 return t.objectProperty(t.identifier(key), object[key]);
56 });
57
58 return t.objectExpression(properties);
59 }
60
61 var wrapperFunctionTemplate = template('\n function WRAPPER_FUNCTION_ID(ID_PARAM) {\n return function(COMPONENT_PARAM) {\n return EXPRESSION;\n };\n }\n ');
62
63 var VISITED_KEY = 'react-transform-' + Date.now();
64
65 var componentVisitor = {
66 Class: function Class(path) {
67 if (path.node[VISITED_KEY] || !matchesPatterns(path.get('superClass'), this.superClasses) || !isReactLikeClass(path.node)) {
68 return;
69 }
70
71 path.node[VISITED_KEY] = true;
72
73 var componentName = path.node.id && path.node.id.name || null;
74 var componentId = componentName || path.scope.generateUid('component');
75 var isInFunction = hasParentFunction(path);
76
77 this.components.push({
78 id: componentId,
79 name: componentName,
80 isInFunction: isInFunction
81 });
82
83 // Can't wrap ClassDeclarations
84 var isStatement = t.isStatement(path.node);
85 var expression = t.toExpression(path.node);
86
87 // wrapperFunction("componentId")(node)
88 var wrapped = wrapComponent(expression, componentId, this.wrapperFunctionId);
89 var constId = undefined;
90
91 if (isStatement) {
92 // wrapperFunction("componentId")(class Foo ...) => const Foo = wrapperFunction("componentId")(class Foo ...)
93 constId = t.identifier(componentName || componentId);
94 wrapped = t.variableDeclaration('const', [t.variableDeclarator(constId, wrapped)]);
95 }
96
97 if (t.isExportDefaultDeclaration(path.parent)) {
98 path.parentPath.insertBefore(wrapped);
99 path.parent.declaration = constId;
100 } else {
101 path.replaceWith(wrapped);
102 }
103 },
104 CallExpression: function CallExpression(path) {
105 if (path.node[VISITED_KEY] || !matchesPatterns(path.get('callee'), this.factoryMethods) || !isReactLikeComponentObject(path.node.arguments[0])) {
106 return;
107 }
108
109 path.node[VISITED_KEY] = true;
110
111 // `foo({ displayName: 'NAME' });` => 'NAME'
112 var componentName = getDisplayName(path.node);
113 var componentId = componentName || path.scope.generateUid('component');
114 var isInFunction = hasParentFunction(path);
115
116 this.components.push({
117 id: componentId,
118 name: componentName,
119 isInFunction: isInFunction
120 });
121
122 path.replaceWith(wrapComponent(path.node, componentId, this.wrapperFunctionId));
123 }
124 };
125
126 var ReactTransformBuilder = function () {
127 function ReactTransformBuilder(file, options) {
128 _classCallCheck(this, ReactTransformBuilder);
129
130 this.file = file;
131 this.program = file.path;
132 this.options = this.normalizeOptions(options);
133
134 // @todo: clean this shit up
135 this.configuredTransformsIds = [];
136 }
137
138 _createClass(ReactTransformBuilder, [{
139 key: 'normalizeOptions',
140 value: function normalizeOptions(options) {
141 return {
142 factoryMethods: options.factoryMethods || ['React.createClass'],
143 superClasses: options.superClasses || ['React.Component', 'Component'],
144 transforms: options.transforms.map(function (opts) {
145 return {
146 transform: opts.transform,
147 locals: opts.locals || [],
148 imports: opts.imports || []
149 };
150 })
151 };
152 }
153 }, {
154 key: 'build',
155 value: function build() {
156 var componentsDeclarationId = this.file.scope.generateUidIdentifier('components');
157 var wrapperFunctionId = this.file.scope.generateUidIdentifier('wrapComponent');
158
159 var components = this.collectAndWrapComponents(wrapperFunctionId);
160
161 if (!components.length) {
162 return;
163 }
164
165 var componentsDeclaration = this.initComponentsDeclaration(componentsDeclarationId, components);
166 var configuredTransforms = this.initTransformers(componentsDeclarationId);
167 var wrapperFunction = this.initWrapperFunction(wrapperFunctionId);
168
169 var body = this.program.node.body;
170
171 body.unshift(wrapperFunction);
172 configuredTransforms.reverse().forEach(function (node) {
173 return body.unshift(node);
174 });
175 body.unshift(componentsDeclaration);
176 }
177
178 /**
179 * const Foo = _wrapComponent('Foo')(class Foo extends React.Component {});
180 * ...
181 * const Bar = _wrapComponent('Bar')(React.createClass({
182 * displayName: 'Bar'
183 * }));
184 */
185
186 }, {
187 key: 'collectAndWrapComponents',
188 value: function collectAndWrapComponents(wrapperFunctionId) {
189 var components = [];
190
191 this.file.path.traverse(componentVisitor, {
192 wrapperFunctionId: wrapperFunctionId,
193 components: components,
194 factoryMethods: this.options.factoryMethods,
195 superClasses: this.options.superClasses,
196 currentlyInFunction: false
197 });
198
199 return components;
200 }
201
202 /**
203 * const _components = {
204 * Foo: {
205 * displayName: "Foo"
206 * }
207 * };
208 */
209
210 }, {
211 key: 'initComponentsDeclaration',
212 value: function initComponentsDeclaration(componentsDeclarationId, components) {
213 var uniqueId = 0;
214
215 var props = components.map(function (component) {
216 var componentId = component.id;
217 var componentProps = [];
218
219 if (component.name) {
220 componentProps.push(t.objectProperty(t.identifier('displayName'), t.stringLiteral(component.name)));
221 }
222
223 if (component.isInFunction) {
224 componentProps.push(t.objectProperty(t.identifier('isInFunction'), t.booleanLiteral(true)));
225 }
226
227 var objectKey = undefined;
228
229 if (t.isValidIdentifier(componentId)) {
230 objectKey = t.identifier(componentId);
231 } else {
232 objectKey = t.stringLiteral(componentId);
233 }
234
235 return t.objectProperty(objectKey, t.objectExpression(componentProps));
236 });
237
238 return t.variableDeclaration('const', [t.variableDeclarator(componentsDeclarationId, t.objectExpression(props))]);
239 }
240
241 /**
242 * import _transformLib from "transform-lib";
243 * ...
244 * const _transformLib2 = _transformLib({
245 * filename: "filename",
246 * components: _components,
247 * locals: [],
248 * imports: []
249 * });
250 */
251
252 }, {
253 key: 'initTransformers',
254 value: function initTransformers(componentsDeclarationId) {
255 var _this = this;
256
257 return this.options.transforms.map(function (transform) {
258 var transformName = transform.transform;
259 var transformImportId = _this.file.addImport(transformName, 'default', transformName);
260
261 var transformLocals = transform.locals.map(function (local) {
262 return t.identifier(local);
263 });
264
265 var transformImports = transform.imports.map(function (importName) {
266 return _this.file.addImport(importName, 'default', importName);
267 });
268
269 var configuredTransformId = _this.file.scope.generateUidIdentifier(transformName);
270 var configuredTransform = t.variableDeclaration('const', [t.variableDeclarator(configuredTransformId, t.callExpression(transformImportId, [toObjectExpression({
271 filename: t.stringLiteral(_this.file.opts.filename),
272 components: componentsDeclarationId,
273 locals: t.arrayExpression(transformLocals),
274 imports: t.arrayExpression(transformImports)
275 })]))]);
276
277 _this.configuredTransformsIds.push(configuredTransformId);
278
279 return configuredTransform;
280 });
281 }
282
283 /**
284 * function _wrapComponent(id) {
285 * return function (Component) {
286 * return _transformLib2(Component, id);
287 * };
288 * }
289 */
290
291 }, {
292 key: 'initWrapperFunction',
293 value: function initWrapperFunction(wrapperFunctionId) {
294 var idParam = t.identifier('id');
295 var componentParam = t.identifier('Component');
296
297 var expression = this.configuredTransformsIds.reverse().reduce(function (memo, transformId) {
298 return t.callExpression(transformId, [memo, idParam]);
299 }, componentParam);
300
301 return wrapperFunctionTemplate({
302 WRAPPER_FUNCTION_ID: wrapperFunctionId,
303 ID_PARAM: idParam,
304 COMPONENT_PARAM: componentParam,
305 EXPRESSION: expression
306 });
307 }
308 }], [{
309 key: 'validateOptions',
310 value: function validateOptions(options) {
311 return (typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object' && Array.isArray(options.transforms);
312 }
313 }, {
314 key: 'assertValidOptions',
315 value: function assertValidOptions(options) {
316 if (!ReactTransformBuilder.validateOptions(options)) {
317 throw new Error('babel-plugin-react-transform requires that you specify options ' + 'in .babelrc or from the Babel Node API, and that it is an object ' + 'with a transforms property which is an array.');
318 }
319 }
320 }]);
321
322 return ReactTransformBuilder;
323 }();
324
325 return {
326 visitor: {
327 Program: function Program(path, _ref2) {
328 var file = _ref2.file;
329 var opts = _ref2.opts;
330
331 ReactTransformBuilder.assertValidOptions(opts);
332 var builder = new ReactTransformBuilder(file, opts);
333 builder.build();
334 }
335 }
336 };
337};
338
339var _arrayFind = require('array-find');
340
341var _arrayFind2 = _interopRequireDefault(_arrayFind);
342
343function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
344
345function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
\No newline at end of file