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 reload with invalid link url #402

Merged
merged 1 commit into from
May 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/hmr/hotModuleReplacement.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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\\">"`;