UNPKG

babel-plugin-react-ssr

Version:

Adds the ssrWaitsFor array and ssrFetchData HOC on required components automatically for react-ssr.

172 lines (130 loc) 6.19 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global['babel-plugin-react-ssr'] = factory()); }(this, (function () { 'use strict'; var classHasRenderMethod = function classHasRenderMethod(path) { if (!path.node.body) { return false; } var members = path.node.body.body; for (var i = 0; i < members.length; i++) { if (members[i].type == 'ClassMethod' && members[i].key.name == 'render') { return true; } } return false; }; var classDeclaration = function classDeclaration(babel, path, state) { if (classHasRenderMethod(path)) { var _ref = path.node.body || {}, _ref$body = _ref.body, body = _ref$body === undefined ? [] : _ref$body; // Store the display name of the class for recusive checks state.file.set('displayName', path.node.id.name); body.forEach(function (propertyOrMethod) { if (propertyOrMethod.static && propertyOrMethod.key.name === 'fetchData') { state.file.set('hasFetchData', true); } }); } }; var transform = function transform(babel) { var t = babel.types; return { visitor: { ClassDeclaration: classDeclaration.bind(null, babel), ExportDefaultDeclaration: function ExportDefaultDeclaration(path, _ref) { var file = _ref.file; // if (!path.get('declaration').isClassDeclaration()) return if (!file.get('hasJSX') || file.get(path.node.declaration.name) || path.node.declaration.name === '_default') return; var node = path.node, scope = path.scope; var ref = node.declaration.id || path.scope.generateUidIdentifier('default'); var waitsFor = file.get('ssrWaitsFor'); var hasFetchData = file.get('hasFetchData'); if ((waitsFor || hasFetchData) && typeof node.declaration.name === 'undefined') { throw new Error('\n\n react-ssr found an export default that was not exporting with a plain variable, instead found ' + node.declaration.type + '.\n Assign that to a variable with a unique name and export that variable as the default instead.\n\n It should look like:\n const myUniqueVariable = someThingYouDo(Component)\n export default myUniqueVariable\n\n This error was found in ' + file.opts.sourceFileName + '\n\n '); } if (waitsFor) { var waitsForIdentifiers = waitsFor.filter(function (waiter) { return scope.hasBinding(waiter); }).map(function (waiter) { return t.identifier(waiter); }); var ssrWaitsFor = t.assignmentExpression('=', t.memberExpression(t.identifier(node.declaration.name), t.identifier('ssrWaitsFor')), t.arrayExpression(waitsForIdentifiers)); path.insertBefore(ssrWaitsFor); } if (hasFetchData) { var decorator = t.variableDeclaration('var', [t.variableDeclarator(ref, t.callExpression(t.identifier('ssrFetchData'), node.declaration.arguments || [node.declaration]))]); var displayName = t.assignmentExpression('=', t.memberExpression(t.identifier(node.declaration.name), t.identifier('displayName')), t.stringLiteral(node.declaration.name)); path.insertBefore(displayName); path.replaceWith(decorator); file.set(node.declaration.name, true); path.insertAfter(t.exportDefaultDeclaration(ref)); } }, JSXOpeningElement: function JSXOpeningElement(path, _ref2) { var file = _ref2.file; file.set('hasJSX', true); var element = path.node.name; var isNotDOMElement = element.name && element.name !== element.name.toLowerCase(); if (isNotDOMElement) { var parentComponentName = file.get('displayName'); var waitsFor = file.get('ssrWaitsFor') || []; var currentComponentName = element.name; // Only add in the wait for array when its not already there and its not the same component as the parent (recurisve components) if (!waitsFor.includes(currentComponentName) && currentComponentName !== parentComponentName) { waitsFor.push(currentComponentName); } file.set('ssrWaitsFor', waitsFor); } }, ExpressionStatement: function ExpressionStatement(path, _ref3) { var file = _ref3.file; var _ref4 = path.node.expression || {}, type = _ref4.type, _ref4$left = _ref4.left, left = _ref4$left === undefined ? {} : _ref4$left, _ref4$right = _ref4.right, right = _ref4$right === undefined ? {} : _ref4$right; var isAssignment = type === 'AssignmentExpression'; if (isAssignment) { var leftIsMember = left.type === 'MemberExpression'; var rightIsFunction = right.type === 'FunctionExpression' || right.type === 'ArrowFunctionExpression'; if (leftIsMember && rightIsFunction) { var _ref5 = left.property || {}, name = _ref5.name; if (name === 'fetchData') { file.set('hasFetchData', true); } } } }, Program: { enter: function enter(path, _ref6) { var file = _ref6.file; file.set('hasJSX', false); }, exit: function exit(path, _ref7) { var file = _ref7.file; var node = path.node, scope = path.scope; if (!file.get('hasFetchData')) { return; } if (!(file.get('hasJSX') && !scope.hasBinding('ssrFetchData'))) { return; } // some way of checking if react-ssr fetchData import exists // or even require... // console.info('Bindings: ', scope.getAllBindings()) var ssrImport = t.importDeclaration([t.importDefaultSpecifier(t.identifier('ssrFetchData'))], t.stringLiteral('react-ssr/lib/fetchData')); node.body.unshift(ssrImport); } } } }; }; return transform; })));