babel-plugin-dva-hmr
Version:
Hmr plugin for dva.
215 lines (200 loc) • 6.45 kB
JavaScript
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));
}
},
},
};
}