use std::path::Path;
use std::time::{Duration, Instant};
use crate::App;
const INDEX: &str = r#"<!doctype html><html><head></head><body><script type="module" src="/main.js"></script></body></html>"#;
async fn first_manifest(root: &Path) -> serde_json::Value {
let path = oj_cache::cache_root(root).join("deps/manifest.json");
let deadline = Instant::now() + Duration::from_secs(30);
loop {
if let Some(v) = std::fs::read_to_string(&path)
.ok()
.and_then(|raw| serde_json::from_str(&raw).ok())
{
return v;
}
assert!(Instant::now() < deadline, "no deps manifest was written");
tokio::time::sleep(Duration::from_millis(20)).await;
}
}
#[tokio::test(flavor = "multi_thread")]
async fn an_alias_to_a_dep_in_node_modules_keeps_one_copy_of_it() {
crate::require_fixture!("vite");
let app = App::new(&[
(
"package.json",
r#"{"name":"alias-dedupe","private":true,"type":"module"}"#,
),
(
"vite.config.js",
"import path from \"node:path\";\n\
const dir = path.dirname(new URL(import.meta.url).pathname);\n\
export default { resolve: { alias: { \"core-lib\": path.resolve(dir, \"node_modules/core-lib\") } } };\n",
),
("index.html", INDEX),
(
"main.js",
"import { core } from \"core-lib\";\n\
import { userCore } from \"user-lib\";\n\
window.__SAME = core === userCore;\n",
),
(
"node_modules/core-lib/package.json",
r#"{"name":"core-lib","version":"1.0.0","type":"module","main":"index.js"}"#,
),
(
"node_modules/core-lib/index.js",
"export const core = { marker: \"core-lib-singleton\" };\n",
),
(
"node_modules/user-lib/package.json",
r#"{"name":"user-lib","version":"1.0.0","type":"module","main":"index.js"}"#,
),
(
"node_modules/user-lib/index.js",
"import { core } from \"core-lib\";\nexport const userCore = core;\n",
),
]);
app.link_fixture("vite");
let server = app.serve().await;
server.text("/main.js").await;
let manifest = first_manifest(&app.root).await;
let meta = &manifest["metadata"];
assert!(
meta.get("core-lib").is_some() && meta.get("user-lib").is_some(),
"the aliased dep is a pre-bundle entry from the first scan: {manifest:#}"
);
let deps = oj_cache::cache_root(&app.root).join("deps");
let user_file = meta["user-lib"]["file"].as_str().unwrap();
let user = std::fs::read_to_string(deps.join(user_file)).unwrap();
assert!(
!user.contains("core-lib-singleton"),
"user-lib inlined its own copy of the aliased dep:\n{user}"
);
let copies = std::fs::read_dir(&deps)
.unwrap()
.flatten()
.filter(|e| {
std::fs::read_to_string(e.path()).is_ok_and(|code| code.contains("core-lib-singleton"))
})
.count();
assert_eq!(
copies, 1,
"one copy of the aliased dep across the pre-bundle"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn an_alias_into_source_is_crawled_for_its_deps() {
crate::require_fixture!("vite");
let app = App::new(&[
(
"package.json",
r#"{"name":"alias-src","private":true,"type":"module"}"#,
),
(
"vite.config.js",
"import path from \"node:path\";\n\
const dir = path.dirname(new URL(import.meta.url).pathname);\n\
export default { resolve: { alias: { \"@\": path.resolve(dir, \"src\") } } };\n",
),
("index.html", INDEX),
(
"main.js",
"import { hi } from \"@/util.js\";\nwindow.__R = hi();\n",
),
(
"src/util.js",
"import { hi as inner } from \"deep-lib\";\nexport const hi = () => inner();\n",
),
(
"node_modules/deep-lib/package.json",
r#"{"name":"deep-lib","version":"1.0.0","type":"module","main":"index.js"}"#,
),
(
"node_modules/deep-lib/index.js",
"export const hi = () => 1;\n",
),
]);
app.link_fixture("vite");
let server = app.serve().await;
server.text("/main.js").await;
let manifest = first_manifest(&app.root).await;
assert!(
manifest["metadata"].get("deep-lib").is_some(),
"the dep behind the source alias joined the first pre-bundle: {manifest:#}"
);
assert!(
manifest["metadata"].get("@/util.js").is_none(),
"app source is never a dep: {manifest:#}"
);
}