Specifying peer dependency on NPM package does not work
Hello
I noticed that NPM imports inside deno.json cannot be specified as a peer dependency using @deno/dnt and always end up inside dependencies. When specifying the import using esm.sh/ it works, however.
Minimal example using Blockly as dependency:
deno.json:
{
"exports": {
".": "./mod.ts"
},
"imports": {
"@deno/dnt": "jsr:@deno/dnt@^0.41.3",
"blockly": "npm:blockly@^11.1.1"
// "blockly": "https://esm.sh/blockly@^11.1.1"
}
}
mod.ts:
import "blockly";
build_npm.ts:
import { build, emptyDir } from "@deno/dnt";
await emptyDir("./npm");
await build({
entryPoints: ["./mod.ts"],
outDir: "./npm",
shims: {
deno: true,
},
typeCheck: "both",
mappings: {
"npm:blockly@^11.1.1": {
// "https://esm.sh/blockly@^11.1.1": {
name: "blockly",
version: "^11.1.1",
peerDependency: true,
},
},
package: {
name: "pure-deno",
version: "0.0.1",
},
importMap: "./deno.json",
});
Replacing the import with the esm.sh/ version correctly transforms blockly as peer dependency while using the npm: version keeps it as direct dependency.
As a workaround one has to keep a separate import map or manually specify dependencies inside package in the build script.
If anyone is running into the same problem right now, my current workaround is the following code snippet to (dirtily) cut out peer dependencies:
const importMap = JSON.parse(Deno.readTextFileSync("./deno.json"));
const blocklyVersion = (<string>importMap["imports"]["blockly"]).split("@")[1];
delete importMap["imports"]["blockly"];
Deno.writeTextFileSync("./deno-npm.json", JSON.stringify(importMap, null, 2));
/// ...
await build({
// ...
package: {
// ...
peerDependencies: {
blockly: blocklyVersion,
},
},
importMap: "./deno-npm.json",
});
I'm running into the same problem. For now solving it with a different approach. I overwrite the generated package.json with the unintentional dependencies as dev dependencies.
import type { SpecifierMappings } from "@deno/dnt/transform"
const mappings: SpecifierMappings = {
"npm:@anthropic-ai/sdk@^0.32.1": {
name: "@anthropic-ai",
version: "^0.32.1",
peerDependency: true,
},
"npm:openai@^4.76.0": {
name: "openai",
version: "^4.76.0",
peerDependency: true,
},
}
await build({
// ...
mappings,
})
const packageJsonPath = path.join(outDir, "package.json")
await Deno.readTextFile(packageJsonPath).then(async (v) => {
const initial = JSON.parse(v)
const { "@anthropic-ai/sdk": anthropic, openai } = initial.dependencies
delete initial.dependencies
initial.peerDependencies = { "@anthropic-ai/sdk": anthropic, openai }
await Deno.writeTextFile(packageJsonPath, JSON.stringify(initial, null, 2))
})
I've also just encountered this issue.
This is how I'm working around it:
- No dependencies in
deno.json. - Import dependencies in source files using
npm:${package_name}format. - Build as normal.
- Patch the generated
package.json.
My build.ts file looks like this:
import { build } from "@deno/dnt";
import { SpecifierMappings } from "@deno/dnt/transform";
const mappings: SpecifierMappings = {
"npm:${name}": {
name: "${name}",
version: "${version}",
subPath: "${sub_path}",
peerDependency: ${peer_dependency},
},
// more dep mappings...
};
await build({
// build config...
});
// other post-build steps...
// function to patch the generated package.json
async function updatePackageJson() {
const packageJsonPath = path.join(DIST_DIR, "package.json");
const packageJson = JSON.parse(await Deno.readTextFile(packageJsonPath));
const dependencies = packageJson.dependencies || {};
const peerDependencies = packageJson.peerDependencies || {};
for (const [_key, value] of Object.entries(mappings)) {
if (typeof value === "string") continue;
const { name, version, peerDependency } = value;
if (peerDependency) {
peerDependencies[name] = version;
delete dependencies[name];
} else {
dependencies[name] = version;
}
}
packageJson.dependencies = dependencies;
packageJson.peerDependencies = peerDependencies;
await Deno.writeTextFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
);
}
await updatePackageJson();