Skip to content

Commit 35b2af6

Browse files
lambdalisueclaude
andcommitted
docs: Update to recommend workspace + deno.jsonc for dependency management
This commit updates the Denops documentation to reflect the new recommended approach for managing dependencies using workspace configuration and import maps. Key changes: - Explain workspace + deno.jsonc pattern as the recommended approach - Clarify separation between development config (root) and runtime deps (plugin) - Update all examples to use deno.jsonc instead of direct URL imports - Add import map support to all tutorials (Hello World and Maze) - Document support for import_map.json(c) as an alternative - Explain why plugin-specific config prevents conflicts in merged runtime paths The documentation now properly guides users to: 1. Use root deno.jsonc for development tools (lint, fmt, test) 2. Use plugin-specific deno.jsonc for runtime dependencies 3. Understand that both deno.json and import_map.json formats are supported 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 13390fa commit 35b2af6

13 files changed

+401
-51
lines changed

src/getting-started/README.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,57 @@ Let's start by creating a simple plugin to learn how to develop Denops plugins.
88

99
## Create a Plugin
1010

11-
Create a directory named `denops-getting-started` in your home directory and a
12-
file named `main.ts` within it, under `denops/denops-getting-started/`:
11+
Create a directory named `denops-getting-started` in your home directory with the following structure:
1312

1413
```
1514
$HOME
1615
└── denops-getting-started
16+
├── deno.jsonc
1717
└── denops
1818
└── denops-getting-started
19+
├── deno.jsonc
1920
└── main.ts
2021
```
2122

23+
First, create a root `deno.jsonc` file for development configuration:
24+
25+
```json,title=deno.jsonc
26+
{
27+
"workspace": [
28+
"./denops/denops-getting-started"
29+
]
30+
}
31+
```
32+
33+
> [!TIP]
34+
>
35+
> The root `deno.jsonc` is for development purposes. It enables commands like
36+
> `deno fmt`, `deno lint`, and `deno test` to work from your project root.
37+
> You can add development-specific settings here without affecting runtime.
38+
39+
Then, create a `deno.jsonc` file inside `denops/denops-getting-started/` for runtime dependencies:
40+
41+
```json,title=denops/denops-getting-started/deno.jsonc
42+
{
43+
"imports": {
44+
"@denops/std": "jsr:@denops/std@^7.0.0"
45+
}
46+
}
47+
```
48+
49+
> [!NOTE]
50+
>
51+
> Denops supports multiple configuration formats:
52+
> - `deno.json` and `deno.jsonc` (recommended)
53+
> - `import_map.json` and `import_map.jsonc` (legacy support)
54+
>
55+
> We use `deno.jsonc` in examples as it allows comments and requires less
56+
> verbose configuration than import maps.
57+
2258
Next, write the following TypeScript code in `main.ts`:
2359

2460
```typescript,title=denops/denops-getting-started/main.ts
25-
import type { Entrypoint } from "jsr:@denops/std@^7.0.0";
61+
import type { Entrypoint } from "@denops/std";
2662

2763
export const main: Entrypoint = (denops) => {
2864
denops.dispatcher = {
@@ -33,6 +69,16 @@ export const main: Entrypoint = (denops) => {
3369
};
3470
```
3571

72+
> [!IMPORTANT]
73+
>
74+
> We separate development configuration from runtime dependencies:
75+
> - **Root `deno.jsonc`**: Contains workspace configuration for development tools
76+
> - **Plugin `deno.jsonc`**: Contains import maps for runtime dependencies
77+
>
78+
> This separation is crucial because the root `deno.jsonc` is only used during
79+
> development, while the plugin-specific `deno.jsonc` is what Denops uses at
80+
> runtime to resolve imports.
81+
3682
## Activate the Plugin
3783

3884
Add the following line to your Vim or Neovim configuration file (e.g.,

src/getting-started/explanation.md

Lines changed: 215 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,22 @@ Let's break down this code step by step.
106106

107107
### About Imports
108108

109+
In the Getting Started example, we used direct URL imports:
110+
109111
```typescript
110112
import type { Entrypoint } from "jsr:@denops/std@^7.0.0";
111113
```
112114

113-
The first line imports the `Entrypoint` type from the [@denops/std] standard
114-
library. You can find detailed information about the library by checking the
115-
URL: `https://jsr.io/@denops/[email protected]` (replace `jsr:` to `https://jsr.io/`).
116-
We fixed the version in the import URL, so it's recommended to check for details
117-
and update to the latest version URL.
115+
However, with the recommended `deno.jsonc` configuration approach, this becomes:
116+
117+
```typescript
118+
import type { Entrypoint } from "@denops/std";
119+
```
120+
121+
The import is resolved through the import map defined in `deno.jsonc`. The
122+
[@denops/std] standard library provides essential types and utilities for Denops
123+
plugin development. You can find detailed information about the library at
124+
`https://jsr.io/@denops/std`.
118125

119126
Note that we use `import type` syntax, which is part of TypeScript's
120127
[Type-Only Imports and Export](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html).
@@ -215,7 +222,7 @@ For example, use
215222
Vim's function instead of `denops.call` like:
216223

217224
```typescript
218-
import * as fn from "jsr:@denops/std@^7.0.0/function";
225+
import * as fn from "@denops/std/function";
219226

220227
// Bad (result1 is `unknown`)
221228
const result1 = await denops.call("expand", "%");
@@ -227,6 +234,208 @@ const result2 = await fn.expand(denops, "%");
227234
If developers use `function` module instead, they can benefit from features like
228235
auto-completion and type checking provided by LSP (Language Server Protocol).
229236

237+
## Managing Dependencies with Workspace and Import Maps
238+
239+
While the examples above use direct URL imports, the recommended approach for Denops plugins is to separate development configuration from runtime dependencies:
240+
241+
- **Development configuration**: The root `deno.jsonc` with workspace settings enables development tools (`deno lint`, `deno fmt`, etc.) to work from the project root
242+
- **Runtime dependencies**: The plugin-specific `deno.jsonc` contains only the import map needed at runtime
243+
- **No conflicts**: Since each Denops plugin has a unique directory under `denops/`, their configuration files never conflict when installed
244+
- **Clean separation**: Development tools configuration stays in the repository root, while runtime dependencies are isolated per plugin
245+
- **Flexible format**: Both `deno.json` and `deno.jsonc` are supported, but `deno.jsonc` is recommended for its comment support
246+
247+
### Separation of Development and Runtime Configuration
248+
249+
The workspace pattern serves two distinct purposes:
250+
251+
1. **Development Time**: The root `deno.jsonc` enables development commands to work properly:
252+
```json
253+
{
254+
"workspace": [
255+
"./denops/your-plugin-name"
256+
],
257+
"lint": {
258+
"rules": {
259+
"tags": ["recommended"]
260+
}
261+
},
262+
"fmt": {
263+
"indentWidth": 2
264+
}
265+
}
266+
```
267+
268+
2. **Runtime**: The plugin's `deno.jsonc` contains only what's needed for execution:
269+
```json
270+
{
271+
"imports": {
272+
"@denops/std": "jsr:@denops/std@^7.0.0"
273+
}
274+
}
275+
```
276+
277+
### Why This Separation Matters
278+
279+
When Vim/Neovim loads plugins, it merges all plugin directories into the runtime path. If runtime dependencies were in the root configuration file, they would conflict:
280+
281+
```
282+
# After installation, plugins are merged:
283+
~/.vim/pack/plugins/start/
284+
├── plugin-a/
285+
│ ├── deno.jsonc # Development config (not used at runtime)
286+
│ └── denops/
287+
│ └── plugin-a/
288+
│ └── deno.jsonc # Runtime dependencies (unique path)
289+
└── plugin-b/
290+
├── deno.jsonc # Development config (not used at runtime)
291+
└── denops/
292+
└── plugin-b/
293+
└── deno.jsonc # Runtime dependencies (unique path)
294+
```
295+
296+
### Setting up Your Plugin
297+
298+
1. Create a `deno.jsonc` in your repository root for development:
299+
300+
```json
301+
{
302+
"workspace": [
303+
"./denops/your-plugin-name"
304+
],
305+
// Development-specific configuration
306+
"lint": {
307+
"rules": {
308+
"tags": ["recommended"]
309+
}
310+
},
311+
"fmt": {
312+
"indentWidth": 2,
313+
"lineWidth": 80
314+
},
315+
"test": {
316+
"include": ["denops/**/*_test.ts"]
317+
}
318+
}
319+
```
320+
321+
2. Create a `deno.jsonc` inside your plugin directory for runtime dependencies:
322+
323+
```json
324+
// denops/your-plugin-name/deno.jsonc
325+
{
326+
"imports": {
327+
"@denops/std": "jsr:@denops/std@^7.0.0",
328+
"@denops/core": "jsr:@denops/core@^7.0.0",
329+
"@core/unknownutil": "jsr:@core/unknownutil@^4.3.0"
330+
}
331+
}
332+
```
333+
334+
This separation ensures:
335+
- Development tools like `deno fmt` and `deno lint` work from your project root
336+
- Runtime dependencies are properly resolved when the plugin is installed
337+
- No conflicts occur between multiple installed Denops plugins
338+
339+
### Alternative: Using import_map.json(c)
340+
341+
While the examples above use `deno.jsonc`, Denops also supports `import_map.json(c)` files in the plugin directory:
342+
343+
```
344+
denops/your-plugin-name/
345+
├── import_map.jsonc # Alternative to deno.jsonc
346+
└── main.ts
347+
```
348+
349+
However, there are important differences:
350+
351+
1. **Import Maps Standard** (import_map.json) requires both entries for submodules:
352+
```json
353+
{
354+
"imports": {
355+
"@denops/std": "jsr:@denops/std@^7.0.0",
356+
"@denops/std/": "jsr:@denops/std@^7.0.0/" // Required for submodules
357+
}
358+
}
359+
```
360+
361+
2. **deno.json Format** (recommended) needs only one entry:
362+
```json
363+
{
364+
"imports": {
365+
"@denops/std": "jsr:@denops/std@^7.0.0" // Handles both cases
366+
}
367+
}
368+
```
369+
370+
> [!IMPORTANT]
371+
>
372+
> We recommend using `deno.jsonc` over `import_map.jsonc` because:
373+
> - It requires less verbose configuration
374+
> - It's the standard Deno configuration format
375+
> - It supports additional configuration options beyond imports
376+
> - Tools like `deno add` and `deno remove` work seamlessly with it
377+
378+
With this configuration, you can update your imports from:
379+
380+
```typescript
381+
import type { Entrypoint } from "jsr:@denops/std@^7.0.0";
382+
import { assert, is } from "jsr:@core/unknownutil@^4.3.0";
383+
```
384+
385+
To cleaner import statements:
386+
387+
```typescript
388+
import type { Entrypoint } from "@denops/std";
389+
import { assert, is } from "@core/unknownutil";
390+
```
391+
392+
### Using npm packages
393+
394+
Import maps also simplify npm package usage. Instead of:
395+
396+
```typescript
397+
import { Maze } from "npm:@thewizardbear/maze_generator@^0.4.0";
398+
```
399+
400+
Add it to your import map:
401+
402+
```json
403+
{
404+
"imports": {
405+
"@denops/std": "jsr:@denops/std@^7.0.0",
406+
"maze_generator": "npm:@thewizardbear/maze_generator@^0.4.0"
407+
}
408+
}
409+
```
410+
411+
Then import it as:
412+
413+
```typescript
414+
import { Maze } from "maze_generator";
415+
```
416+
417+
### Import Map Structure
418+
419+
The `deno.json` file in your Denops plugin directory should contain all the dependencies your plugin needs. This keeps dependency management isolated to each plugin while avoiding conflicts in the merged runtime directory.
420+
421+
For example, if your plugin uses additional npm packages:
422+
423+
```json
424+
{
425+
"imports": {
426+
"@denops/std": "jsr:@denops/std@^7.0.0",
427+
"@denops/std/": "jsr:@denops/std@^7.0.0/",
428+
"@core/unknownutil": "jsr:@core/unknownutil@^4.3.0",
429+
"your-npm-lib": "npm:some-package@^1.0.0"
430+
}
431+
}
432+
```
433+
434+
> [!NOTE]
435+
>
436+
> Including both `@denops/std` and `@denops/std/` allows you to import both the
437+
> main module and submodules (e.g., `@denops/std/buffer`).
438+
230439
## Next Steps
231440

232441
In the next step, follow the tutorial to learn how to develop a minimum Denops

src/introduction.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ features:
1818
- **Unified codebase for Vim and Neovim**:<br>Denops provides a unified API for
1919
both Vim and Neovim. You can write a plugin that functions on both Vim and
2020
Neovim with a single codebase.
21-
- **No worries about dependency management**:<br>Deno includes a built-in
22-
dependency management system, allowing developers to write plugins with
23-
third-party libraries without concerns about dependency management.
21+
- **Modern dependency management**:<br>Deno's built-in dependency system with
22+
import maps provides clean, maintainable dependency management. The workspace
23+
configuration ensures each plugin's dependencies are isolated, preventing
24+
conflicts when multiple Denops plugins are installed together.
2425
- **Simple and efficient code**:<br>Deno utilizes the V8 engine, significantly
2526
faster than Vim script. You can write a plugin with straightforward code,
2627
without the need for complex optimizations solely for performance.

src/tutorial/helloworld/adding-an-api.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@
33
In the previous section, we created a minimal Denops plugin. In this section, we
44
will enhance the plugin by adding an API.
55

6-
Open `denops/denops-helloworld/main.ts` and rewrite the content with the
6+
First, update your `denops/denops-helloworld/deno.jsonc` to include the unknownutil dependency:
7+
8+
```json,title=denops/denops-helloworld/deno.jsonc
9+
{
10+
"imports": {
11+
"@denops/std": "jsr:@denops/std@^7.0.0",
12+
"@core/unknownutil": "jsr:@core/unknownutil@^4.3.0"
13+
}
14+
}
15+
```
16+
17+
Then open `denops/denops-helloworld/main.ts` and rewrite the content with the
718
following code:
819

920
```typescript,title=denops/denops-helloworld/main.ts
10-
import type { Entrypoint } from "jsr:@denops/std@^7.0.0";
11-
import { assert, is } from "jsr:@core/unknownutil@^4.3.0";
21+
import type { Entrypoint } from "@denops/std";
22+
import { assert, is } from "@core/unknownutil";
1223

1324
export const main: Entrypoint = (denops) => {
1425
denops.dispatcher = {

src/tutorial/helloworld/calling-vim-features.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ the `denops` instance passed to the plugin's `main` function. You can rewrite
55
`main.ts` as follows to register the `DenopsHello` as a Vim command:
66

77
```typescript,title=denops/denops-helloworld/main.ts
8-
import type { Entrypoint } from "jsr:@denops/std@^7.0.0";
9-
import { assert, is } from "jsr:@core/unknownutil@^4.3.0";
8+
import type { Entrypoint } from "@denops/std";
9+
import { assert, is } from "@core/unknownutil";
1010

1111
export const main: Entrypoint = (denops) => {
1212
denops.dispatcher = {

0 commit comments

Comments
 (0)