Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix HMR server when WebSocket proxy is running on the same port #9432

Open
wants to merge 4 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 13 additions & 11 deletions packages/core/integration-tests/test/hmr.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ async function nextWSMessage(ws: WebSocket) {
return json5.parse(await new Promise(resolve => ws.once('message', resolve)));
}

const HMR_ENDPOINT = '/__parcel_hmr';

describe('hmr', function () {
let subscription, ws;

Expand Down Expand Up @@ -77,7 +79,7 @@ describe('hmr', function () {

subscription = await b.watch();
let {bundleGraph} = await getNextBuildSuccess(b);
ws = await openSocket('ws://localhost:' + port);
ws = await openSocket('ws://localhost:' + port + HMR_ENDPOINT);

let outputs = [];
let reloaded = false;
Expand Down Expand Up @@ -147,7 +149,7 @@ describe('hmr', function () {
subscription = await b.watch();
await getNextBuild(b);

ws = await openSocket('ws://localhost:' + port);
ws = await openSocket('ws://localhost:' + port + HMR_ENDPOINT);

outputFS.writeFile(
path.join(__dirname, '/input/local.js'),
Expand Down Expand Up @@ -178,7 +180,7 @@ describe('hmr', function () {
subscription = await b.watch();
await getNextBuild(b);

ws = await openSocket('ws://localhost:' + port);
ws = await openSocket('ws://localhost:' + port + HMR_ENDPOINT);

outputFS.writeFile(
path.join(__dirname, '/input/local.js'),
Expand All @@ -203,7 +205,7 @@ describe('hmr', function () {
subscription = await b.watch();
await getNextBuild(b);

ws = await openSocket('ws://localhost:' + port);
ws = await openSocket('ws://localhost:' + port + HMR_ENDPOINT);

outputFS.writeFile(
path.join(__dirname, '/input/local.js'),
Expand Down Expand Up @@ -235,7 +237,7 @@ describe('hmr', function () {
'require("fs"; exports.a = 5; exports.b = 5;',
);

ws = await openSocket('ws://localhost:' + port);
ws = await openSocket('ws://localhost:' + port + HMR_ENDPOINT);
let message = await nextWSMessage(ws);

assert.equal(message.type, 'error');
Expand All @@ -252,7 +254,7 @@ describe('hmr', function () {
subscription = await b.watch();
await getNextBuild(b);

ws = await openSocket('ws://localhost:' + port);
ws = await openSocket('ws://localhost:' + port + HMR_ENDPOINT);

await outputFS.writeFile(
path.join(__dirname, '/input/local.js'),
Expand Down Expand Up @@ -289,7 +291,7 @@ describe('hmr', function () {
subscription = await b.watch();
await getNextBuild(b);

ws = await openSocket('wss://localhost:' + port, {
ws = await openSocket('wss://localhost:' + port + HMR_ENDPOINT, {
rejectUnauthorized: false,
});

Expand Down Expand Up @@ -322,7 +324,7 @@ describe('hmr', function () {
subscription = await b.watch();
await getNextBuild(b);

ws = await openSocket('wss://localhost:' + port, {
ws = await openSocket('wss://localhost:' + port + HMR_ENDPOINT, {
rejectUnauthorized: false,
});

Expand Down Expand Up @@ -681,7 +683,7 @@ module.hot.dispose((data) => {

assert.deepEqual(outputs, [3]);

let ws = new WebSocket('ws://localhost:' + port);
let ws = new WebSocket('ws://localhost:' + port + HMR_ENDPOINT);

await sleep(50);
fs.writeFile(
Expand Down Expand Up @@ -771,7 +773,7 @@ module.hot.dispose((data) => {
);

let spy = sinon.spy(ctx.document.body, 'appendChild');
let ws = new WebSocket('ws://localhost:' + port);
let ws = new WebSocket('ws://localhost:' + port + HMR_ENDPOINT);

await sleep(50);
fs.writeFile(
Expand Down Expand Up @@ -827,7 +829,7 @@ module.hot.dispose((data) => {

let appendSpy = sinon.spy(ctx.document.body, 'appendChild');
let removeSpy = sinon.spy(ctx.document.getElementById('tmp'), 'remove');
let ws = new WebSocket('ws://localhost:' + port);
let ws = new WebSocket('ws://localhost:' + port + HMR_ENDPOINT);

await sleep(50);
fs.writeFile(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"/api": {
"target": "http://localhost:9753",
"ws": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function () {
return 'Hello, Parcel.js!';
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


72 changes: 72 additions & 0 deletions packages/core/integration-tests/test/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,63 @@ import path from 'path';
import {bundler, getNextBuild, inputFS} from '@parcel/test-utils';
import http from 'http';
import getPort from 'get-port';
import WebSocket from 'ws';

const config = path.join(
__dirname,
'./integration/custom-configs/.parcelrc-dev-server',
);

function apiServer() {
const wss = new WebSocket.Server({noServer: true});
const server = http
.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Request URL: ' + req.url);
res.end();
})
.on('upgrade', (req, ws, head) => {
// Note: A real server should avoid handling the upgrade if we receive
// a connection to `/__parcel_hmr`
wss.handleUpgrade(req, ws, head, ws => {
ws.send('Request URL: ' + req.url, () => ws.close());
});
})
.listen(9753);

return server;
}

async function closeSocket(ws: WebSocket) {
ws.close();
await new Promise(resolve => {
ws.once('close', resolve);
});
}
async function assertMessage(ws: WebSocket, expectedMsg: string) {
await new Promise((resolve, reject) => {
const timer = setTimeout(() => {
ws.removeListener('message', onMessage);
reject(new Error('timeout waiting for WebSocket message'));
}, 5000);
function onMessage(msg) {
clearTimeout(timer);
ws.removeListener('message', onMessage); // just in case
try {
assert.equal(msg, expectedMsg);
resolve();
} catch (err) {
reject(err);
}
}
ws.once('message', onMessage);
ws.once('error', reject);
ws.once('close', () => {
reject(new Error('WebSocket closed before message received'));
});
});
}

function get(file, port, client = http) {
return new Promise((resolve, reject) => {
client.get(
Expand Down Expand Up @@ -196,4 +235,37 @@ describe('proxy', function () {
data = await get('/api/get', port);
assert.equal(data, 'Request URL: /get');
});

it('should handle WebSocket proxy', async function () {
let dir = path.join(__dirname, 'integration/proxyrc-websocket');
inputFS.chdir(dir);

let port = await getPort();
let b = bundler(path.join(dir, 'index.js'), {
config,
serveOptions: {
https: false,
port: port,
host: 'localhost',
},
hmrOptions: {
port: port,
},
});

subscription = await b.watch();
await getNextBuild(b);

server = apiServer();

let data = await get('/index.js', port);
assert.notEqual(data, 'Request URL: /index.js');

data = await get('/api/get', port);
assert.equal(data, 'Request URL: /api/get');

const ws = new WebSocket('ws://localhost:' + port + '/api/test');
await assertMessage(ws, 'Request URL: /api/test');
await closeSocket(ws);
});
});
10 changes: 9 additions & 1 deletion packages/reporters/dev-server/src/HMRServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ export default class HMRServer {
} else {
this.options.addMiddleware?.((req, res) => this.handle(req, res));
}
this.wss = new WebSocket.Server({server});
this.wss = new WebSocket.Server({noServer: true});
server.on('upgrade', (req, ws, head) => {
let {pathname} = url.parse(req.originalUrl || req.url);
if (pathname != null && pathname.startsWith(HMR_ENDPOINT)) {
this.wss.handleUpgrade(req, ws, head, (...args) =>
this.wss.emit('connection', ...args),
);
}
});

this.wss.on('connection', ws => {
if (this.unresolvedError) {
Expand Down
22 changes: 11 additions & 11 deletions packages/runtimes/hmr/src/loaders/hmr-runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ declare var globalThis: typeof self;
declare var ServiceWorkerGlobalScope: Object;
*/

var HMR_ENDPOINT = '/__parcel_hmr';
var OVERLAY_ID = '__parcel__error__overlay__';

var OldModule = module.bundle.Module;
Expand Down Expand Up @@ -98,19 +99,18 @@ if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
? 'wss'
: 'ws';

var ws;
if (HMR_USE_SSE) {
ws = new EventSource('/__parcel_hmr');
} else {
try {
var ws = {};
try {
if (HMR_USE_SSE) {
ws = new EventSource(HMR_ENDPOINT);
} else {
ws = new WebSocket(
protocol + '://' + hostname + (port ? ':' + port : '') + '/',
protocol + '://' + hostname + (port ? ':' + port : '') + HMR_ENDPOINT,
);
} catch (err) {
if (err.message) {
console.error(err.message);
}
ws = {};
}
} catch (err) {
if (err.message) {
console.error(err.message);
}
}

Expand Down