import { join } from 'path'; function getHmrString(appName, routerPath, modelPaths = [], container = '#root', enableModel = false) { const modelHot = enableModel ? modelPaths.map(modelPath => ` if (module.hot) { const modelNamespaceMap = {}; let model = require('${modelPath}'); if (model.default) model = model.default; modelNamespaceMap['${modelPath}'] = model.namespace; module.hot.accept('${modelPath}', () => { try { app.unmodel(modelNamespaceMap['${modelPath}']); let model = require('${modelPath}'); if (model.default) model = model.default; app.model(model); } catch(e) { console.error(e); } }); } `).join('\n') : ''; return ` (function() { // Generated by babel-plugin-dva-hmr console.log('[HMR] inited with babel-plugin-dva-hmr'); const router = require('${routerPath}'); ${appName}.router(router.default || router); ${appName}.use({ onHmr(render) { if (module.hot) { const renderNormally = render; const renderException = (error) => { const RedBox = require('redbox-react'); ReactDOM.render(React.createElement(RedBox, { error: error }), document.querySelector('${container}')); }; const newRender = (router) => { try { renderNormally(router); } catch (error) { console.error('error', error); renderException(error); } }; module.hot.accept('${routerPath}', () => { const router = require('${routerPath}'); newRender(router.default || router); }); } }, }); ${modelHot} })() `; } export default function ({ types:t }) { const cache = {}; const modelPaths = {}; function getImportRequirePath(identifierName, scope) { if (scope.hasBinding(identifierName)) { const binding = scope.bindings[identifierName]; if (binding) { const parent = binding.path.parent; if (t.isImportDeclaration(parent)) { return parent.source.value; } else if (t.isVariableDeclaration(parent)) { const declarator = findDeclarator(parent.declarations, identifierName); if (declarator) { if (isRequire(declarator.init)) { return getArguments0(declarator.init); } else if (isRequireDefault(declarator.init)) { return getArguments0(declarator.init.object); } } } } } return null; } function isDvaCallExpression(node, scope) { return t.isCallExpression(node) && t.isIdentifier(node.callee) && getImportRequirePath(node.callee.name, scope) === 'dva'; } function isDvaInstance(identifierName, scope) { if (scope.hasBinding(identifierName)) { const binding = scope.bindings[identifierName]; if (binding) { const parent = binding.path.parent; if (t.isVariableDeclaration(parent)) { const declarator = findDeclarator(parent.declarations, identifierName); if (declarator && isDvaCallExpression(declarator.init, scope)) { return true; } } } } return false; } function isRouterCall(node, scope) { if (!t.isMemberExpression(node)) return false; const { object, property } = node; return ( ( t.isIdentifier(property) && property.name === 'router' ) && ( t.isIdentifier(object) && isDvaInstance(object.name, scope)) ); } function isModelCall(node, scope) { if (!t.isMemberExpression(node)) return false; const { object, property } = node; return ( ( t.isIdentifier(property) && property.name === 'model' ) && ( t.isIdentifier(object) && isDvaInstance(object.name, scope)) ); } function isRequire(node) { return t.isCallExpression(node) && t.isIdentifier(node.callee) && node.callee.name === 'require'; } function isRequireDefault(node) { if (!t.isMemberExpression(node)) return false; const { object, property } = node; return isRequire(object) && t.isIdentifier(property) && property.name === 'default'; } function findDeclarator(declarations, identifier) { for (let d of declarations) { if (t.isIdentifier(d.id) && d.id.name === identifier) { return d; } } } function getArguments0(node) { if (t.isLiteral(node.arguments[0])) { return node.arguments[0].value; } } function getRequirePath(node, scope) { switch (node.type) { case 'CallExpression': { const path = getArguments0(node); if (path) { return path; } break; } case 'Identifier': { const path = getImportRequirePath(node.name, scope); if (path) { return path; } break; } case 'MemberExpression': { if (isRequireDefault(node)) { const path = getArguments0(node.object); if (path) { return path; } } break; } default: break; } } return { visitor: { Program: { enter(path) { const { filename } = path.hub.file.opts; delete cache[filename]; }, }, CallExpression(path, state) { const { opts } = state; const {filename} = (path && path.hub && path.hub.file && path.hub.file.opts) || (state && state.file); if (cache[filename]) return; const { callee, arguments: args } = path.node; if (isRouterCall(callee, path.scope)) { const routerPath = getRequirePath(args[0], path.scope); if (routerPath) { cache[filename] = true; !opts.quiet && console.info(`[babel-plugin-dva-hmr][INFO] got routerPath ${routerPath}`); path.parentPath.replaceWithSourceString(getHmrString( callee.object.name, routerPath, modelPaths[filename], opts.container, !opts.disableModel, )); } else { !opts.quiet && console.warn(`[babel-plugin-dva-hmr][WARN] can't get router path in ${filename}`); } } else if (isModelCall(callee, path.scope)) { modelPaths[filename] = modelPaths[filename] || []; modelPaths[filename].push(getRequirePath(args[0], path.scope)); } }, }, }; }