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

feat: hmr supports linked npm packages changes #864

Merged
merged 5 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion crates/mako/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl DevServer {
// let mut watcher = RecommendedWatcher::new(tx, notify::Config::default())?;
let mut debouncer = new_debouncer(Duration::from_millis(10), None, tx).unwrap();
let watcher = debouncer.watcher();
Watch::watch(&root, watcher)?;
Watch::watch(&root, watcher, &compiler)?;

let initial_hash = compiler.full_hash();
let mut last_cache_hash = Box::new(initial_hash);
Expand Down
1 change: 1 addition & 0 deletions crates/mako/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![feature(box_patterns)]
#![feature(hasher_prefixfree_extras)]
#![feature(let_chains)]
#![feature(result_option_inspect)]

mod analyze_deps;
mod ast;
Expand Down
1 change: 1 addition & 0 deletions crates/mako/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(box_patterns)]
#![feature(let_chains)]
#![feature(result_option_inspect)]

use std::sync::Arc;

Expand Down
31 changes: 29 additions & 2 deletions crates/mako/src/watch.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::mpsc::Sender;
use std::sync::Arc;

use mako_core::anyhow;
use mako_core::anyhow::{self, Ok};
use mako_core::notify::{self, EventKind, Watcher};
use mako_core::notify_debouncer_full::DebouncedEvent;

use crate::compiler::Compiler;
use crate::resolve::ResolverResource;

pub struct Watch {
pub root: PathBuf,
pub delay: u64,
Expand All @@ -14,7 +18,11 @@ pub struct Watch {

impl Watch {
// pub fn watch(root: &PathBuf, watcher: &mut notify::RecommendedWatcher) -> anyhow::Result<()> {
pub fn watch(root: &PathBuf, watcher: &mut notify::RecommendedWatcher) -> anyhow::Result<()> {
pub fn watch(
root: &PathBuf,
watcher: &mut notify::RecommendedWatcher,
compiler: &Arc<Compiler>,
) -> anyhow::Result<()> {
let items = std::fs::read_dir(root)?;
items
.into_iter()
Expand All @@ -32,6 +40,25 @@ impl Watch {
}
Ok(())
})?;

let module_graph = compiler.context.module_graph.read().unwrap();
let (dependencies, _) = module_graph.toposort();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

topsort 是必要的吗? 是不是只要遍历所有模块就够了。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

哦哦是的,我改一下

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已更新

dependencies
.iter()
.try_for_each(|module_id| -> anyhow::Result<()> {
if let Some(module) = module_graph.get_module(module_id) {
if let Some(ResolverResource::Resolved(resource)) = module
.info
.as_ref()
.and_then(|info| info.resolved_resource.as_ref())
{
let path = &resource.0.path;
watcher.watch(Path::new(&path), notify::RecursiveMode::NonRecursive)?;
}
}
Ok(())
})?;

Ok(())
}

Expand Down
114 changes: 112 additions & 2 deletions scripts/test-hmr.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import assert from 'assert';
import { execSync } from 'child_process';
import { chromium, devices } from 'playwright';
import 'zx/globals';

function skip() {}

const root = process.cwd();
const tmp = path.join(root, 'tmp', 'hmr');
const tmpPackages = path.join(root, 'tmp', 'packages');
if (!fs.existsSync(tmp)) {
fs.mkdirSync(tmp, { recursive: true });
}
Expand Down Expand Up @@ -1067,6 +1069,104 @@ runTest('issue: 861', async () => {
await cleanup({ process, browser });
});

runTest('link npm packages', async () => {
write(
normalizeFiles({
'/src/index.tsx': `
import React from 'react';
import ReactDOM from "react-dom/client";
import { foo } from "mako-test-package-link";
function App() {
return <div>{foo}<section>{Math.random()}</section></div>;
}
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
`,
}),
);
writePackage(
'mako-test-package-link',
normalizeFiles({
'package.json': `
{
"name": "mako-test-package-link",
"version": "1.0.0",
"main": "index.js"
}
`,
'index.js': `
export * from './src/index';
`,
'src/index.js': `
const foo = 'foo';
export { foo };
`,
}),
);
execSync('cd ./tmp/packages/mako-test-package-link && pnpm link --global');
execSync('pnpm link --global mako-test-package-link');
await startMakoDevServer();
await delay(DELAY_TIME);
const { browser, page } = await startBrowser();
let lastResult;
let thisResult;
let isReload;
lastResult = normalizeHtml(await getRootHtml(page));
assert.equal(lastResult.html, '<div>foo</div>', 'Initial render');

// modify package file content
writePackage(
'mako-test-package-link',
normalizeFiles({
'src/index.js': `
const foo = 'foo2';
export { foo };
`,
}),
);
await delay(DELAY_TIME);
thisResult = normalizeHtml(await getRootHtml(page));
assert.equal(thisResult.html, '<div>foo2</div>', 'Second render');
isReload = lastResult.random !== thisResult.random;
assert.equal(isReload, true, 'should reload');
lastResult = thisResult;

// // add files
// writePackage(
// 'mako-test-package-link',
// normalizeFiles({
// 'src/index2.js': `
// const bar = 'bar';
// export { bar };
// `,
// 'index.js': `
// export * from './src/index';
// export * from './src/index2';
// `,
// }),
// );
// write(
// normalizeFiles({
// '/src/index.tsx': `
// import React from 'react';
// import ReactDOM from "react-dom/client";
// import { foo, bar } from "mako-test-package-link";
// function App() {
// return <div>{foo}{bar}<section>{Math.random()}</section></div>;
// }
// ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
// `,
// }),
// );
// await delay(DELAY_TIME);
// thisResult = normalizeHtml(await getRootHtml(page));
// assert.equal(thisResult.html, '<div>foo2bar</div>', 'Third render');
// isReload = lastResult.random !== thisResult.random;
// assert.equal(isReload, true, 'should reload');

await cleanup({ process, browser });
fs.unlinkSync('./node_modules/mako-test-package-link');
});

function normalizeFiles(files, makoConfig = {}) {
return {
'/public/index.html': `
Expand Down Expand Up @@ -1106,6 +1206,14 @@ function write(files) {
}
}

function writePackage(packageName, files) {
for (const [file, content] of Object.entries(files)) {
const p = path.join(tmpPackages, packageName, file);
fs.mkdirSync(path.dirname(p), { recursive: true });
fs.writeFileSync(p, content, 'utf-8');
}
}

function remove(file) {
const p = path.join(tmp, file);
fs.unlinkSync(p);
Expand Down Expand Up @@ -1162,13 +1270,15 @@ function normalizeHtml(html) {
}

async function commonTest(
files = {},
initFilesOrFunc = {},
lastResultCallback = () => {},
modifyFilesOrCallback = () => {},
thisResultCallback = () => {},
shouldReload = false,
) {
write(normalizeFiles(files));
typeof initFilesOrFunc === 'function'
? await initFilesOrFunc()
: write(normalizeFiles(files));
await startMakoDevServer();
await delay(DELAY_TIME);
const { browser, page } = await startBrowser();
Expand Down