Skip to content

Commit

Permalink
fix: hmr reload with invalid link url (#402)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi committed May 27, 2019
1 parent ee9df43 commit 30a19b0
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 13 deletions.
21 changes: 8 additions & 13 deletions src/hmr/hotModuleReplacement.js
Expand Up @@ -120,7 +120,11 @@ function updateCss(el, url) {

newEl.href = `${url}?${Date.now()}`;

el.parentNode.appendChild(newEl);
if (el.nextSibling) {
el.parentNode.insertBefore(newEl, el.nextSibling);
} else {
el.parentNode.appendChild(newEl);
}
}

function getReloadUrl(href, src) {
Expand Down Expand Up @@ -160,6 +164,7 @@ function reloadStyle(src) {

if (url) {
updateCss(el, url);

loaded = true;
}
});
Expand All @@ -182,18 +187,8 @@ function reloadAll() {
function isUrlRequest(url) {
// An URL is not an request if

// 1. It's an absolute url
if (/^[a-z][a-z0-9+.-]*:/i.test(url)) {
return false;
}

// 2. It's a protocol-relative
if (/^\/\//.test(url)) {
return false;
}

// 3. Its a `#` link
if (/^#/.test(url)) {
// It is not http or https
if (!/^https?:/i.test(url)) {
return false;
}

Expand Down
284 changes: 284 additions & 0 deletions test/HMR.test.js
@@ -0,0 +1,284 @@
/* eslint-env browser */
/* eslint-disable no-console */

import hotModuleReplacement from '../src/hmr/hotModuleReplacement';

function getLoadEvent() {
const event = document.createEvent('Event');

event.initEvent('load', false, false);

return event;
}

function getErrorEvent() {
const event = document.createEvent('Event');

event.initEvent('error', false, false);

return event;
}

describe('HMR', () => {
let consoleMock = null;

beforeEach(() => {
consoleMock = jest.spyOn(console, 'log').mockImplementation(() => () => {});

jest.spyOn(Date, 'now').mockImplementation(() => 1479427200000);

document.head.innerHTML = '<link rel="stylesheet" href="/dist/main.css" />';
document.body.innerHTML = '<script src="/dist/main.js"></script>';
});

afterEach(() => {
consoleMock.mockClear();
});

it('should works', (done) => {
const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);

done();
}, 100);
});

it('should works with multiple updates', (done) => {
const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);

jest.spyOn(Date, 'now').mockImplementation(() => 1479427200001);

const update2 = hotModuleReplacement('./src/style.css', {});

update2();

setTimeout(() => {
const links2 = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links2[0].visited).toBe(true);
expect(links2[0].isLoaded).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links2[1].dispatchEvent(getLoadEvent());

expect(links2[1].isLoaded).toBe(true);

done();
}, 100);
}, 100);
});

it('should reloads with locals', (done) => {
const update = hotModuleReplacement('./src/style.css', {
locals: { foo: 'bar' },
});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);

done();
}, 100);
});

it('should reloads with reloadAll option', (done) => {
const update = hotModuleReplacement('./src/style.css', {
reloadAll: true,
});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);

done();
}, 100);
});

it('should reloads with non http/https link href', (done) => {
document.head.innerHTML =
'<link rel="stylesheet" href="/dist/main.css" /><link rel="shortcut icon" href="data:;base64,=" />';

const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);
expect(links[2].visited).toBeUndefined();

done();
}, 100);
});

it('should reloads with # link href', (done) => {
document.head.innerHTML =
'<link rel="stylesheet" href="/dist/main.css" /><link rel="shortcut icon" href="#href" />';

const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);
expect(links[2].visited).toBeUndefined();

done();
}, 100);
});

it('should reloads with link without href', (done) => {
document.head.innerHTML =
'<link rel="stylesheet" href="/dist/main.css" /><link rel="shortcut icon" />';

const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);
expect(links[2].visited).toBeUndefined();

done();
}, 100);
});

it('should reloads with absolute remove url', (done) => {
document.head.innerHTML =
'<link rel="stylesheet" href="/dist/main.css" /><link rel="stylesheet" href="http://dev.com/dist/main.css" />';

const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getLoadEvent());

expect(links[1].isLoaded).toBe(true);
expect(links[2].visited).toBeUndefined();

done();
}, 100);
});

it('should handle error event', (done) => {
const update = hotModuleReplacement('./src/style.css', {});

update();

setTimeout(() => {
expect(console.log.mock.calls[0][0]).toMatchSnapshot();

const links = Array.prototype.slice.call(
document.querySelectorAll('link')
);

expect(links[0].visited).toBe(true);
expect(document.head.innerHTML).toMatchSnapshot();

links[1].dispatchEvent(getErrorEvent());

expect(links[1].isLoaded).toBe(true);

done();
}, 100);
});
});
39 changes: 39 additions & 0 deletions test/__snapshots__/HMR.test.js.snap
@@ -0,0 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`HMR should handle error event 1`] = `"[HMR] css reload %s"`;

exports[`HMR should handle error event 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should reloads with # link href 1`] = `"[HMR] css reload %s"`;
exports[`HMR should reloads with # link href 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"shortcut icon\\" href=\\"#href\\">"`;
exports[`HMR should reloads with absolute remove url 1`] = `"[HMR] css reload %s"`;
exports[`HMR should reloads with absolute remove url 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"stylesheet\\" href=\\"http://dev.com/dist/main.css\\">"`;
exports[`HMR should reloads with link without href 1`] = `"[HMR] css reload %s"`;
exports[`HMR should reloads with link without href 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"shortcut icon\\">"`;
exports[`HMR should reloads with locals 1`] = `"[HMR] Detected local css modules. Reload all css"`;
exports[`HMR should reloads with locals 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should reloads with non http/https link href 1`] = `"[HMR] css reload %s"`;
exports[`HMR should reloads with non http/https link href 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"shortcut icon\\" href=\\"data:;base64,=\\">"`;
exports[`HMR should reloads with reloadAll option 1`] = `"[HMR] Reload all css"`;
exports[`HMR should reloads with reloadAll option 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should works 1`] = `"[HMR] css reload %s"`;
exports[`HMR should works 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should works with multiple updates 1`] = `"[HMR] css reload %s"`;
exports[`HMR should works with multiple updates 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should works with multiple updates 3`] = `"<link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200001\\">"`;

0 comments on commit 30a19b0

Please sign in to comment.