1、前言
上次的远程加载简单描述了app reload(开发时运行也是一个逻辑)和MetroServer交互的流程,现在开始讲讲HMR,对应到RN调试界面中的Enable HotLoading,HMR全名为Hot Module Replacement。它能让你在修改代码后马上在app上展现结果而且不会丢失状态。
2、HMR通讯方式
先看其创建的位置,也是在runServer中
//metro/src/index.js.flow
exports.runServer = async (
......
) => {
....
const {
attachHmrServer,
middleware,
metroServer,
end,
} = await exports.createConnectMiddleware(config);
serverApp.use(middleware);
....
let httpServer;
.....
httpServer = http.createServer(serverApp);
....
if (hmrEnabled) {
attachHmrServer(httpServer);
}
来看看attachHmrServer函数:
index.js.flow
attachHmrServer(httpServer: HttpServer | HttpsServer) {
attachWebsocketServer({
httpServer,
path: '/hot',
websocketServer: new MetroHmrServer(
metroServer.getBundler(),
metroServer.getCreateModuleId(),
config,
),
});
}
这里没有逻辑,放出attachWebsocketServer代码:
module.exports = function attachWebsocketServer<TClient: Object>({
httpServer,
websocketServer,
path,
}: HMROptions<TClient>) {
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({
server: httpServer,
path,
});
wss.on('connection', async ws => {
let connected = true;
const url = ws.upgradeReq.url;
const sendFn = (...args) => {
if (connected) {
ws.send(...args);
}
};
const client = await websocketServer.onClientConnect(url, sendFn);
...
ws.on('error', e => {
websocketServer.onClientError && websocketServer.onClientError(client, e);
});
ws.on('close', () => {
websocketServer.onClientDisconnect &&
websocketServer.onClientDisconnect(client);
connected = false;
});
ws.on('message', message => {
websocketServer.onClientMessage &&
websocketServer.onClientMessage(client, message);
});
});
};
attachWebsocketServer里面主要是创建WebSocketServer并监听连接,将逻辑全交给MetroHmrServer处理,不得不说这代码把连接和逻辑处理分得很清楚
在来看MetroHmrServer:
//HmrServer.js.flow
async onClientConnect(
clientUrl: string,
sendFn: (data: string) => void,
): Promise<?Client> {
const urlObj = nullthrows(url.parse(clientUrl, true));
const query = nullthrows(urlObj.query);
let revPromise;
if (query.bundleEntry != null) {
urlObj.pathname = query.bundleEntry.replace(/\.js$/, '.bundle');
delete query.bundleEntry;
const {options} = parseOptionsFromUrl(
url.format(urlObj),
new Set(this._config.resolver.platforms),
);
const {entryFile, trans