Skip to content

Commit

Permalink
(wmr) - Fix non js hmr (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock authored Feb 26, 2021
1 parent bb224ab commit c740ac0
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/fifty-wolves-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'wmr': patch
---

Fix non-js hmr
5 changes: 5 additions & 0 deletions .changeset/light-pens-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'wmr': patch
---

Fix support for scss/sass hmr
23 changes: 22 additions & 1 deletion packages/wmr/src/plugins/wmr/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function handleMessage(e) {
url = resolve(url);

if (!mods.get(url)) {
if (/\.css$/.test(url)) {
if (/\.(css|s[ac]ss)$/.test(url)) {
if (mods.has(url + '.js')) {
url += '.js';
} else {
Expand Down Expand Up @@ -164,14 +164,35 @@ export function style(filename, id) {
}
}

function traverseSheet(sheet, target) {
for (let i = 0; i < sheet.rules.length; i++) {
if (sheet.rules[i].href && resolve(strip(sheet.rules[i].href)) === strip(target)) {
return sheet.rules[i];
} else if (sheet.rules[i].styleSheet) {
return traverseSheet(sheet.rules[i].styleSheet, target);
}
}
}

// Update a non-imported stylesheet
function updateStyleSheet(url) {
const sheets = document.styleSheets;

for (let i = 0; i < sheets.length; i++) {
if (sheets[i].href && strip(sheets[i].href) === url) {
// @ts-ignore
sheets[i].ownerNode.href = strip(url) + '?t=' + Date.now();
return true;
}

const found = traverseSheet(sheets[i], url);
if (found) {
const index = [].indexOf.call(found.parentStyleSheet.rules, found);
const urlStr = JSON.stringify(strip(url) + '?t=' + Date.now());
const css = found.cssText.replace(/^(@import|@use)\s*(?:url\([^)]*\)|(['"]).*?\2)/, '$1 ' + urlStr);
found.parentStyleSheet.insertRule(css, index);
found.parentStyleSheet.deleteRule(index + 1);
return true;
}
}
}
36 changes: 24 additions & 12 deletions packages/wmr/src/wmr-middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,16 @@ export default function wmrMiddleware({
});
const pendingChanges = new Set();

let timeout = null;
function flushChanges() {
timeout = null;
onChange({ changes: Array.from(pendingChanges), duration: 0 });
pendingChanges.clear();
}

function bubbleUpdates(filename) {
function bubbleUpdates(filename, visited = new Set()) {
if (visited.has(filename)) return true;
visited.add(filename);
// Delete file from the in-memory cache:
WRITE_CACHE.delete(filename);

Expand All @@ -133,17 +137,15 @@ export default function wmrMiddleware({

if (mod.acceptingUpdates) {
pendingChanges.add(filename);
return true;
} else if (mod.dependents.size) {
mod.dependents.forEach(function (value) {
return mod.dependents.every(function (value) {
mod.stale = true;
bubbleUpdates(value);
return bubbleUpdates(value, visited);
});
} else {
// We need a full-reload signal
return false;
}

return true;
// We need a full-reload signal
return false;
}

watcher.on('change', filename => {
Expand All @@ -154,10 +156,20 @@ export default function wmrMiddleware({
// Delete any generated CSS Modules mapping modules:
if (/\.module\.css$/.test(filename)) WRITE_CACHE.delete(filename + '.js');

if (!pendingChanges.size) setTimeout(flushChanges, 60);
const result = bubbleUpdates(filename);
if (!result) {
onChange({ type: 'reload' });
if (!pendingChanges.size) timeout = setTimeout(flushChanges, 60);

if (/\.(css|s[ac]ss)$/.test(filename)) {
WRITE_CACHE.delete(filename);
pendingChanges.add('/' + filename);
} else if (/\.(mjs|[tj]sx?)$/.test(filename)) {
if (!bubbleUpdates(filename)) {
clearTimeout(timeout);
onChange({ reload: true });
}
} else {
WRITE_CACHE.delete(filename);
clearTimeout(timeout);
onChange({ reload: true });
}
});

Expand Down
119 changes: 119 additions & 0 deletions packages/wmr/test/fixtures.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,125 @@ describe('fixtures', () => {
});
});

describe('hmr', () => {
async function updateFile(tempDir, file, replacer) {
const compPath = path.join(tempDir, file);
const content = await fs.readFile(compPath, 'utf-8');
await fs.writeFile(compPath, replacer(content));
}

const timeout = n => new Promise(r => setTimeout(r, n));

it('should hot reload the child-file', async () => {
await loadFixture('hmr', env);
instance = await runWmrFast(env.tmp.path);
await getOutput(env, instance);

const home = await env.page.$('.home');
let text = home ? await home.evaluate(el => el.textContent) : null;
expect(text).toEqual('Home');

await updateFile(env.tmp.path, 'home.js', content =>
content.replace('<p class="home">Home</p>', '<p class="home">Away</p>')
);

await timeout(1000);

text = home ? await home.evaluate(el => el.textContent) : null;
expect(text).toEqual('Away');
});

it('should hot reload for a newly created file', async () => {
await loadFixture('hmr', env);
instance = await runWmrFast(env.tmp.path);
await getOutput(env, instance);

const compPath = path.join(env.tmp.path, 'child.js');
await fs.writeFile(compPath, `export default function Child() {return <p class="child">child</p>}`);

const home = await env.page.$('.home');
let text = home ? await home.evaluate(el => el.textContent) : null;
expect(text).toEqual('Home');

await updateFile(env.tmp.path, 'home.js', content => {
const newContent = `import Child from './child.js';\n\n${content}`;
return newContent.replace('<p class="home">Home</p>', '<Child />');
});

await timeout(1000);

const child = await env.page.$('.child');
text = child ? await child.evaluate(el => el.textContent) : null;
expect(text).toEqual('child');
});

it('should hot reload a css-file imported from index.html', async () => {
await loadFixture('hmr', env);
instance = await runWmrFast(env.tmp.path);
await getOutput(env, instance);

expect(await page.$eval('body', e => getComputedStyle(e).color)).toBe('rgb(51, 51, 51)');

await updateFile(env.tmp.path, 'index.css', content => content.replace('color: #333;', 'color: #000;'));

await timeout(1000);

expect(await page.$eval('body', e => getComputedStyle(e).color)).toBe('rgb(0, 0, 0)');
});

it('should hot reload a module css-file', async () => {
await loadFixture('hmr', env);
instance = await runWmrFast(env.tmp.path);
await getOutput(env, instance);

expect(await page.$eval('main', e => getComputedStyle(e).color)).toBe('rgb(51, 51, 51)');

await updateFile(env.tmp.path, 'style.module.css', content => content.replace('color: #333;', 'color: #000;'));

await timeout(1000);

expect(await page.$eval('main', e => getComputedStyle(e).color)).toBe('rgb(0, 0, 0)');
});
});

describe('hmr-scss', () => {
async function updateFile(tempDir, file, replacer) {
const compPath = path.join(tempDir, file);
const content = await fs.readFile(compPath, 'utf-8');
await fs.writeFile(compPath, replacer(content));
}

const timeout = n => new Promise(r => setTimeout(r, n));

it('should hot reload an scss-file imported from index.html', async () => {
await loadFixture('hmr-scss', env);
instance = await runWmrFast(env.tmp.path);
await getOutput(env, instance);

expect(await page.$eval('body', e => getComputedStyle(e).color)).toBe('rgb(51, 51, 51)');

await updateFile(env.tmp.path, 'index.scss', content => content.replace('color: #333;', 'color: #000;'));

await timeout(1000);

expect(await page.$eval('body', e => getComputedStyle(e).color)).toBe('rgb(0, 0, 0)');
});

it('should hot reload an imported scss-file from another scss-file', async () => {
await loadFixture('hmr-scss', env);
instance = await runWmrFast(env.tmp.path);
await getOutput(env, instance);

expect(await page.$eval('main', e => getComputedStyle(e).color)).toBe('rgb(51, 51, 51)');

await updateFile(env.tmp.path, 'home.scss', content => content.replace('color: #333;', 'color: #000;'));

await timeout(1000);

expect(await page.$eval('main', e => getComputedStyle(e).color)).toBe('rgb(0, 0, 0)');
});
});

describe('commonjs', () => {
it('should transpile .cjs files', async () => {
await loadFixture('commonjs', env);
Expand Down
3 changes: 3 additions & 0 deletions packages/wmr/test/fixtures/hmr-scss/home.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
main {
color: #333;
}
12 changes: 12 additions & 0 deletions packages/wmr/test/fixtures/hmr-scss/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<title>Simple</title>
<link rel="stylesheet" href="/index.scss" />
</head>
<body>
<div id="root"></div>
<script src="./index.js" type="module"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions packages/wmr/test/fixtures/hmr-scss/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { render } from 'preact';

export function App() {
return <main>Test</main>;
}

render(<App />, document.body);
5 changes: 5 additions & 0 deletions packages/wmr/test/fixtures/hmr-scss/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import 'home.scss';

body {
color: #333;
}
5 changes: 5 additions & 0 deletions packages/wmr/test/fixtures/hmr/home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function Home() {
return <p class="home">Home</p>;
}

export default Home;
3 changes: 3 additions & 0 deletions packages/wmr/test/fixtures/hmr/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
color: #333;
}
12 changes: 12 additions & 0 deletions packages/wmr/test/fixtures/hmr/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<title>Simple</title>
<link rel="stylesheet" href="/index.css" />
</head>
<body>
<div id="root"></div>
<script src="./index.js" type="module"></script>
</body>
</html>
21 changes: 21 additions & 0 deletions packages/wmr/test/fixtures/hmr/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { render } from 'preact';
import styles from './style.module.css';
import Home from './home.js';

export function App() {
return (
<main class={styles.app}>
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
<Home />
</main>
);
}

render(<App />, document.body);

// @ts-ignore
if (module.hot) module.hot.accept(u => render(<u.module.App />, document.body));
4 changes: 4 additions & 0 deletions packages/wmr/test/fixtures/hmr/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.app {
background: #f6f6f6;
color: #333;
}

0 comments on commit c740ac0

Please sign in to comment.