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

Rework npm specifiers #120

Merged
merged 12 commits into from
Sep 18, 2023
5 changes: 5 additions & 0 deletions runtime/manual/getting_started/configuration_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ Used to specify a different file name for the lockfile. By default deno will use

Used to enable or disable the `node_modules` directory when using npm packages.

## `npmRegistry`

Used to specify a custom npm registry for npm specifiers.

## `compilerOptions`

`deno.json` can also act as a TypeScript configuration file and supports
Expand Down Expand Up @@ -151,6 +155,7 @@ See also
},
"lock": false,
"nodeModulesDir": true,
"npmRegistry": "https://mycompany.net/artifactory/api/npm/virtual-npm",
"test": {
"include": ["src/"],
"exclude": ["src/testdata/", "data/fixtures/**/*.ts"]
Expand Down
137 changes: 76 additions & 61 deletions runtime/manual/node/npm_specifiers.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,110 @@
# `npm:` specifiers

Since version 1.28, Deno has native support for importing npm packages. This is
done by importing using `npm:` specifiers.
done by importing using `npm:` specifiers. For example the following code:

The way these work is best described with an example that you can run with
`deno run --allow-env`:
```ts
import { emojify } from "npm:node-emoji@2";

```ts, ignore
import chalk from "npm:chalk@5";
console.log(emojify(":t-rex: :heart: NPM"));
```

console.log(chalk.green("Hello!"));
Can be run with:

```sh
$ deno run main.js
🦖 ❤️ NPM
```

These npm specifiers have the following format:
When doing this, no `npm install` is necessary and no `node_modules` folder is
created. These packages are also subject to the same
[permissions](../basics/permissions.md) as other code in Deno.

npm specifiers have the following format:

```ts, ignore
```
npm:<package-name>[@<version-requirement>][/<sub-path>]
```

Another example with express:
For examples with popular libraries, please refer to our
[tutorial section](/runtime/tutorials).

```js, ignore
// main.js
import express from "npm:express@^4.17";
const app = express();
## TypeScript types

Many packages ship with types out of the box, you can import those and use them
with types easily:

```ts
import chalk from "npm:chalk@5";
```

app.get("/", (req, res) => {
res.send("Hello World");
});
Some packages do not though, but you can specify their types with a
[`@deno-types`](../advanced/typescript/types.md) directive. For example, using a
[`@types`](https://www.typescriptlang.org/docs/handbook/2/type-declarations.html#definitelytyped--types)
package:

app.listen(3000);
console.log("listening on http://localhost:3000/");
```ts
// @deno-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
```

Then doing the following will start a simple express server:
### Module resolution

The official TypeScript compiler `tsc` supports different
[moduleResolution](https://www.typescriptlang.org/tsconfig#moduleResolution)
settings. Deno only supports the modern `node16` resolution. Unfortunately many
NPM packages fail to correctly provide types under node16 module resolution,
which can result in `deno check` reporting type errors, that `tsc` does not
report.

If a default export from an `npm:` import appears to have a wrong type (with the
right type seemingly being available under the `.default` property), it's most
likely that the package provides wrong types under node16 module resolution for
imports from ESM. You can verify this by checking if the error also occurs with
`tsc --module node16` and `"type": "module"` in `package.json` or by consulting
the [Are the types wrong?](https://arethetypeswrong.github.io/) website
(particularly the "node16 from ESM" row).

If you want to use a package that doesn't support TypeScript's node16 module
resolution, you can:

1. Open an issue at the issue tracker of the package about the problem. (And
perhaps contribute a fix :) (Although there unfortunately currently is a lack
of tooling for packages to support both ESM and CJS, since default exports
require different syntaxes, see also
[microsoft/TypeScript#54593](https://github.com/microsoft/TypeScript/issues/54593))
2. Use a [CDN](./cdns.md), that rebuilds the packages for Deno support, instead
of an `npm:` identifier.
3. Ignore the type errors you get in your code base with `// @ts-expect-error`
or `// @ts-ignore`.

```sh
$ deno run -A main.js
listening on http://localhost:3000/
### Including Node types

Node ships with many built-in types like `Buffer` that might be referenced in an
npm package's types. To load these you must add a types reference directive to
the `@types/node` package:

```ts
/// <reference types="npm:@types/node" />
```

When doing this, no `npm install` is necessary and no `node_modules` folder is
created. These packages are also subject to the same permissions as Deno
applications.
Note that it is fine to not specify a version for this in most cases because
Deno will try to keep it in sync with its internal Node code, but you can always
override the version used if necessary.

## npm executable scripts

npm packages with `bin` entries can be executed from the command line without an
`npm install` using a specifier in the following format:

```ts, ignore
```
npm:<package-name>[@<version-requirement>][/<binary-name>]
```

For example:

```sh
$ deno run --allow-env --allow-read npm:cowsay@1.5.0 Hello there!
$ deno run --allow-read npm:cowsay@1.5.0 Hello there!
______________
< Hello there! >
--------------
Expand All @@ -66,7 +114,7 @@ $ deno run --allow-env --allow-read npm:cowsay@1.5.0 Hello there!
||----w |
|| ||

$ deno run --allow-env --allow-read npm:cowsay@1.5.0/cowthink What to eat?
$ deno run --allow-read npm:cowsay@1.5.0/cowthink What to eat?
______________
( What to eat? )
--------------
Expand All @@ -77,39 +125,6 @@ $ deno run --allow-env --allow-read npm:cowsay@1.5.0/cowthink What to eat?
|| ||
```

## TypeScript types

Many packages ship with types out of the box, you can import those and use them
with types easily:

```ts, ignore
import chalk from "npm:chalk@5";
```

Some packages do not though, but you can specify their types with a
[`@deno-types`](../advanced/typescript/types.md) directive. For example, using a
[`@types`](https://www.typescriptlang.org/docs/handbook/2/type-declarations.html#definitelytyped--types)
package:

```ts, ignore
// @deno-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
```

### Including Node types

Node ships with many built-in types like `Buffer` that might be referenced in an
npm package's types. To load these you must add a types reference directive to
the `@types/node` package:

```ts, ignore
/// <reference types="npm:@types/node" />
```

Note that it is fine to not specify a version for this in most cases because
Deno will try to keep it in sync with its internal Node code, but you can always
override the version used if necessary.

## `--node-modules-dir` flag

npm specifiers resolve npm packages to a central global npm cache. This works
Expand Down