docs: Updated vanilla docs and added example (BLO-1010)#2915
docs: Updated vanilla docs and added example (BLO-1010)#2915matthewlipski wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe vanilla JavaScript guide now documents extension-based UI elements and adds a runnable custom side menu example, including editor integration, project configuration, styling, metadata, and playground registration. ChangesVanilla JS custom side menu
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant App
participant BlockNoteEditor
participant SideMenuExtension
participant SideMenuDOM
App->>BlockNoteEditor: Create and mount editor
App->>SideMenuExtension: Subscribe to store updates
SideMenuExtension->>SideMenuDOM: Apply visibility and position
SideMenuDOM->>SideMenuExtension: Trigger add or drag actions
App->>BlockNoteEditor: Insert paragraph or open suggestion menu
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/mantine
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-email-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
commit: |
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
examples/vanilla-js/vanilla-custom-side-menu/src/App.tsx (1)
66-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor duplication:
openSuggestionMenu("/")call repeated in both branches.Both the empty-block and non-empty-block paths end with
editor.getExtension(SuggestionMenu)?.openSuggestionMenu("/");. Could be hoisted after the if/else by computing the target block once.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/vanilla-js/vanilla-custom-side-menu/src/App.tsx` around lines 66 - 90, The addBtn callback duplicates the SuggestionMenu opening logic across both branches. In the block handling around isBlockEmpty, compute the target block by reusing block for empty content or the result of insertBlocks for non-empty content, set the cursor to that target, and call openSuggestionMenu("/") once after the conditional.examples/vanilla-js/vanilla-custom-side-menu/package.json (1)
14-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPin the BlockNote package versions emitted by the generator.
Using
latestallows fresh installs to resolve a different API than the version this example was authored and tested against. Use the repository’s catalog, workspace, or release version consistently; update the generator rather than this generated file. npm documentslatestas a mutable dist-tag. (docs.npmjs.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/vanilla-js/vanilla-custom-side-menu/package.json` around lines 14 - 18, Update the generator responsible for the vanilla custom side-menu package manifest so all `@blocknote` package entries use the repository’s pinned catalog, workspace, or release version instead of the mutable "latest" tag. Apply the change at the generator source, not the generated package.json, and keep the versions consistent across the listed packages.Source: MCP tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/vanilla-js/vanilla-custom-side-menu/index.html`:
- Around line 1-9: Add the HTML5 doctype declaration at the beginning of the
document, before the html element in the custom side menu page. Leave the
existing head metadata and generated-file marker unchanged.
In `@examples/vanilla-js/vanilla-custom-side-menu/src/App.tsx`:
- Around line 32-42: Update initEditor’s returned cleanup to remove both
dynamically appended DOM nodes from rootElement after unsubscribing and
unmounting the editor: the editorElement created in initEditor and the side-menu
element created in the side menu setup. Guard removal as needed so cleanup
remains safe when the side menu was never shown, while preserving the existing
unsubscribe and editor.unmount behavior.
In `@examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts`:
- Around line 15-29: Update the source-path existence check and the
`@blocknote/core/`@blocknote/react aliases in the Vite configuration to resolve
the monorepo packages from the repository root rather than examples/packages.
Keep the build-mode bypass and live-source alias behavior unchanged.
- Line 16: Update the Vite configuration path handling to derive a reusable
configDir from import.meta.url, then use configDir in the three path expressions
currently based on __dirname. Remove reliance on Vite/VitePlus __dirname
injection while preserving the existing resolved paths.
---
Nitpick comments:
In `@examples/vanilla-js/vanilla-custom-side-menu/package.json`:
- Around line 14-18: Update the generator responsible for the vanilla custom
side-menu package manifest so all `@blocknote` package entries use the
repository’s pinned catalog, workspace, or release version instead of the
mutable "latest" tag. Apply the change at the generator source, not the
generated package.json, and keep the versions consistent across the listed
packages.
In `@examples/vanilla-js/vanilla-custom-side-menu/src/App.tsx`:
- Around line 66-90: The addBtn callback duplicates the SuggestionMenu opening
logic across both branches. In the block handling around isBlockEmpty, compute
the target block by reusing block for empty content or the result of
insertBlocks for non-empty content, set the cursor to that target, and call
openSuggestionMenu("/") once after the conditional.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ff6f895c-448a-4236-9017-507a414516b1
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (14)
docs/content/docs/getting-started/vanilla-js.mdxdocs/content/examples/meta.jsonexamples/vanilla-js/vanilla-custom-side-menu/.bnexample.jsonexamples/vanilla-js/vanilla-custom-side-menu/README.mdexamples/vanilla-js/vanilla-custom-side-menu/index.htmlexamples/vanilla-js/vanilla-custom-side-menu/main.tsxexamples/vanilla-js/vanilla-custom-side-menu/package.jsonexamples/vanilla-js/vanilla-custom-side-menu/src/App.tsxexamples/vanilla-js/vanilla-custom-side-menu/src/styles.cssexamples/vanilla-js/vanilla-custom-side-menu/tsconfig.jsonexamples/vanilla-js/vanilla-custom-side-menu/vite-env.d.tsexamples/vanilla-js/vanilla-custom-side-menu/vite.config.tspackages/dev-scripts/examples/util.tsplayground/src/examples.gen.tsx
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Custom Side Menu (Vanilla JS)</title> | ||
| <script> | ||
| <!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY --> | ||
| </script> | ||
| </head> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Add <!DOCTYPE html>.
Static analysis (HTMLHint) flags the missing doctype declaration, which causes the page to render in quirks mode.
🐛 Proposed fix
+<!DOCTYPE html>
<html lang="en">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Custom Side Menu (Vanilla JS)</title> | |
| <script> | |
| <!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY --> | |
| </script> | |
| </head> | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Custom Side Menu (Vanilla JS)</title> | |
| <script> | |
| <!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY --> | |
| </script> | |
| </head> |
🧰 Tools
🪛 HTMLHint (1.9.2)
[error] 1-1: Doctype must be declared before any non-comment content.
(doctype-first)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/vanilla-js/vanilla-custom-side-menu/index.html` around lines 1 - 9,
Add the HTML5 doctype declaration at the beginning of the document, before the
html element in the custom side menu page. Leave the existing head metadata and
generated-file marker unchanged.
Source: Linters/SAST tools
| function initEditor(rootElement: HTMLElement) { | ||
| const editor = BlockNoteEditor.create(); | ||
|
|
||
| // `editor.mount` turns the element it's given into the editable area, so we | ||
| // mount into a dedicated child element. This keeps our custom UI elements | ||
| // (like the Side Menu below) separate from the editor's content. | ||
| const editorElement = document.createElement("div"); | ||
| rootElement.appendChild(editorElement); | ||
| editor.mount(editorElement); | ||
|
|
||
| const sideMenu = editor.getExtension(SideMenuExtension)!; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Cleanup doesn't remove the appended DOM nodes, leaking elements on remount/StrictMode double-invoke.
initEditor appends editorElement (line 39) and the custom side menu element (line 110) as children of rootElement, but the returned cleanup (lines 124-127) only calls unsubscribe() and editor.unmount() — it never removes these nodes from rootElement. Since main.tsx wraps <App /> in <React.StrictMode>, React will mount→cleanup→mount the effect once in development; without removing the appended nodes, a stale editorElement and (if the side menu was ever shown) a stale, detached-but-still-in-DOM side menu accumulate under the same root <div> every time the effect reruns.
🐛 Proposed fix
return () => {
unsubscribe();
editor.unmount();
+ editorElement.remove();
+ element?.remove();
};Also applies to: 108-128, 130-139
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/vanilla-js/vanilla-custom-side-menu/src/App.tsx` around lines 32 -
42, Update initEditor’s returned cleanup to remove both dynamically appended DOM
nodes from rootElement after unsubscribing and unmounting the editor: the
editorElement created in initEditor and the side-menu element created in the
side menu setup. Guard removal as needed so cleanup remains safe when the side
menu was never shown, while preserving the existing unsubscribe and
editor.unmount behavior.
| conf.command === "build" || | ||
| !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) | ||
| ? {} | ||
| : ({ | ||
| // Comment out the lines below to load a built version of blocknote | ||
| // or, keep as is to load live from sources with live reload working | ||
| "@blocknote/core": path.resolve( | ||
| __dirname, | ||
| "../../packages/core/src/", | ||
| ), | ||
| "@blocknote/react": path.resolve( | ||
| __dirname, | ||
| "../../packages/react/src/", | ||
| ), | ||
| } as any), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Fix the monorepo source path.
Even if __dirname points to the config directory, ../../packages/core/src resolves to examples/packages/core/src, not the repository’s packages/core/src. The existence check therefore disables the local-source aliases during development.
Proposed fix
- !fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
+ !fs.existsSync(path.resolve(__dirname, "../../../packages/core/src"))
...
- "../../packages/core/src/",
+ "../../../packages/core/src/",
...
- "../../packages/react/src/",
+ "../../../packages/react/src/",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| conf.command === "build" || | |
| !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) | |
| ? {} | |
| : ({ | |
| // Comment out the lines below to load a built version of blocknote | |
| // or, keep as is to load live from sources with live reload working | |
| "@blocknote/core": path.resolve( | |
| __dirname, | |
| "../../packages/core/src/", | |
| ), | |
| "@blocknote/react": path.resolve( | |
| __dirname, | |
| "../../packages/react/src/", | |
| ), | |
| } as any), | |
| conf.command === "build" || | |
| !fs.existsSync(path.resolve(__dirname, "../../../packages/core/src")) | |
| ? {} | |
| : ({ | |
| // Comment out the lines below to load a built version of blocknote | |
| // or, keep as is to load live from sources with live reload working | |
| "`@blocknote/core`": path.resolve( | |
| __dirname, | |
| "../../../packages/core/src/", | |
| ), | |
| "`@blocknote/react`": path.resolve( | |
| __dirname, | |
| "../../../packages/react/src/", | |
| ), | |
| } as any), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts` around lines 15
- 29, Update the source-path existence check and the
`@blocknote/core/`@blocknote/react aliases in the Vite configuration to resolve
the monorepo packages from the repository root rather than examples/packages.
Keep the build-mode bypass and live-source alias behavior unchanged.
| resolve: { | ||
| alias: | ||
| conf.command === "build" || | ||
| !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python - <<'PY'
from pathlib import Path
config_dir = Path("examples/vanilla-js/vanilla-custom-side-menu").resolve()
actual = (config_dir / "../../../packages/core/src").resolve()
incorrect = (config_dir / "../../packages/core/src").resolve()
assert actual != incorrect
assert actual.is_dir()
print(f"expected source path: {actual}")
print(f"configured source path: {incorrect}")
PY
node --input-type=module -e \
'if (typeof __dirname !== "undefined") process.exit(1); console.log("__dirname unavailable in ESM")'Repository: TypeCellOS/BlockNote
Length of output: 316
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## Candidate files"
git ls-files | rg '(^|/)package\.json$|examples/vanilla-js/vanilla-custom-side-menu/vite\.config\.ts$|examples/vanilla-js/vanilla-custom-side-menu/package\.json$'
echo
echo "## package files"
for f in examples/vanilla-js/vanilla-custom-side-menu/package.json package.json; do
if [ -f "$f" ]; then
echo "--- $f"
sed -n '1,220p' "$f"
fi
done
echo
echo "## vite config"
if [ -f examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts ]; then
cat -n examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts
fi
echo
echo "## root package.json type field"
python3 - <<'PY'
import json
from pathlib import Path
p=Path("package.json")
print(p.exists())
if p.exists():
data=json.loads(p.read_text())
print(data.get("type"))
PYRepository: TypeCellOS/BlockNote
Length of output: 11191
🌐 Web query:
Vite Plus config loads as ESM commonjs shim __dirname __filename
💡 Result:
In Vite configuration files, dirname and filename are natively unavailable when using ES modules (ESM) [1], which is the standard behavior in modern Node.js environments (especially when "type": "module" is set in package.json) [1][2]. To address this, Vite automatically injects these variables into your config files and their dependencies during the loading process [3][4]. Key behaviors regarding this injection: - Automatic Injection: Vite pre-processes config files, injecting definitions for dirname and filename (along with import.meta.url) so they behave as they would in CommonJS [3][4]. - Global Scope Usage: Because Vite injects these as global-like variables in the context of the config file, you can use them directly at the top level [4][5]. - Shadowing Restrictions: You cannot declare your own variables named dirname or filename at the top level of your config file [4][6]. Doing so will result in a SyntaxError (e.g., Identifier has already been declared) because the injected variable is already present in that scope [4]. You can, however, shadow these variables inside local function scopes [4][5]. - Best Practice: While the injected variables are provided for compatibility, modern Node.js environments now support import.meta.dirname and import.meta.url directly [7]. Using import.meta.dirname is considered more idiomatic and "proper" for ESM files as it avoids relying on Vite-specific injection mechanisms [7]. If you encounter issues where these variables are not working as expected, ensure you are not attempting to manually declare them or use them in ways that conflict with the injected definitions [4][6]. For complex path resolution needs, using the built-in path module (e.g., import path from 'node:path') in combination with import.meta.url is the standard approach [7].
Citations:
- 1: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 2: https://github.com/vitejs/vite/blob/main/docs/config/index.md
- 3: https://github.com/vitejs/vite/blob/52e64eb43287d241f3fd547c332e16bd9e301e95/packages/vite/src/node/config.ts
- 4: vitejs/vite@51e9195
- 5: feat: better config
__dirnamesupport vitejs/vite#8442 - 6: No more can define
__filename,__dirnamevariables vitejs/vite#6422 - 7: refactor: use
import.meta.dirnameeverywhere vitejs/vite#21509
🌐 Web query:
Vite Plus vite-plus config import __dirname commonjs shim
💡 Result:
Vite+ (vite-plus) is a unified toolchain built on top of Vite and other tools (like Vitest, Rolldown, and Oxc) that simplifies the development workflow into a single configuration and CLI [1][2][3]. Because Vite+ is a superset of Vite, it follows Vite's core configuration principles [2][3]. Regarding the use of dirname and CommonJS modules in your configuration: 1. dirname in ESM: In modern Node.js environments and Vite projects configured as ES modules ("type": "module" in package.json), dirname is not defined by default [4][5]. Vite performs automatic replacement of dirname and filename in config files, but this behavior can conflict if your project explicitly defines these or uses "type": "module" [6][4][7]. 2. Recommended Approach: To avoid "ReferenceError: dirname is not defined" or similar issues, the recommended practice is to use import.meta.dirname (supported in current Node.js versions) [5] or create your own shim. A commonly used snippet to derive dirname in ESM is [4]: import { fileURLToPath } from 'url'; import { dirname } from 'path'; const dirname = dirname(fileURLToPath(import.meta.url)); 3. CommonJS Compatibility: If you are importing CommonJS modules into your Vite or Vite+ project, you may encounter compatibility issues because Vite defaults to ESM [8]. You can resolve these by configuring the optimizeDeps and build.commonjsOptions fields in your vite.config.ts (or the Vite+ equivalent) [8]: export default defineConfig({ optimizeDeps: { include: ['your-commonjs-dependency'], }, build: { commonjsOptions: { include: [/your-commonjs-dependency/, /node_modules/], }, }, }); If you are using Vite+ specifically, you configure these settings within the same unified vite.config.ts file, ensuring that your logic handles ESM/CJS interop as shown above [9][2].
Citations:
- 1: https://viteplus.dev/
- 2: https://github.com/voidzero-dev/vite-plus
- 3: https://voidzero.dev/posts/announcing-vite-plus
- 4: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 5: refactor: use
import.meta.dirnameeverywhere vitejs/vite#21509 - 6: https://v2.vitejs.dev/config/
- 7: No more can define
__filename,__dirnamevariables vitejs/vite#6422 - 8: https://stackoverflow.com/questions/79680129/importing-commonjs-module-in-a-vite-project
- 9: https://viteplus.dev/config/
🌐 Web query:
site:voidzero.dev vite-plus config defines __dirname package.json type module __dirname unavailable
💡 Result:
In Node.js, when your package is configured with "type": "module" in package.json, the environment is strictly ES Modules (ESM). In ESM, global variables like dirname and filename are unavailable because they are specific to the CommonJS module system. To replicate the functionality of dirname in an ESM Vite configuration file (e.g., vite.config.ts), you must manually derive the path using the file URL of the current module. You can achieve this using the import.meta object and the node:url module: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const dirname = dirname(fileURLToPath(import.meta.url)); This pattern allows you to determine the directory path of your configuration file reliably within an ESM environment. Vite and associated toolchains (like Vite+ and Rolldown) maintain compatibility with standard Node.js module resolution, so this approach is the standard solution for accessing directory paths in ESM-based configuration files. {confidence: 100%, citations: [1]}
Citations:
🌐 Web query:
voidzero vite-plus config imports auto inject __dirname __filename __dirname unavailable ESM __dirname
💡 Result:
In an ESM environment, such as a project using Vite+ (which enforces strict ESM and native Node resolution), dirname and filename are not available because they are specific to CommonJS [1][2]. To handle this in your codebase or Vite configuration, you should avoid relying on global injection and instead use the standard ESM import.meta property [1][3]. Recommended Approaches: 1. Use import.meta.dirname and import.meta.filename: Modern Node.js versions (and current Vite/Vite+ environments) support import.meta.dirname and import.meta.filename directly [1]. This is the most efficient and preferred method [1]. 2. Manually define them (Legacy/Compatibility): If you must support environments where import.meta.dirname is not available, use the standard url module pattern [3]: import { fileURLToPath } from 'url'; import { dirname } from 'path'; const filename = fileURLToPath(import.meta.url); const dirname = dirname(filename); Vite+ Specific Context: - Vite+ does not automatically inject dirname or filename into your ESM files [4][5]. - While some older tools (like tsdown) provided shims to transform these references for ESM compatibility, relying on such shims is fragile and deprecated in favor of native ESM practices [4][1]. - When using Vite+ vp pack for library builds, ensure your code is ESM-compliant; if you encounter issues with legacy code, these shims are sometimes managed via the shims: true option in vp pack, though resolution of these shims can be sensitive to your specific project structure [4][6]. - For configuration files (vite.config.ts), Vite+ typically uses native loaders [7][8]. If your config fails due to missing dirname, convert the logic to use import.meta.dirname or standard path utilities rather than relying on automatic injection [1][3].
Citations:
- 1: refactor: use
import.meta.dirnameeverywhere vitejs/vite#21509 - 2: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 3: https://stackoverflow.com/questions/73947942/referenceerror-dirname-is-not-defined-in-es-module-scope-building-script
- 4: tsdown shims are not available after migration to vite+ voidzero-dev/vite-plus#999
- 5: https://github.com/voidzero-dev/vite-plus
- 6: pack: shims + unbundle emits raw ESM imports into .cjs, producing invalid CommonJS voidzero-dev/vite-plus#2090
- 7: Default to
--configLoader=nativevitejs/vite#21546 - 8: lint,fmt: Known vite.config.ts releated issues voidzero-dev/vite-plus#930
🌐 Web query:
git voidzero vite-plus packages config shim __dirname __filename ESM
💡 Result:
In the Vite+ (voidzero) ecosystem, shims are used to maintain compatibility between CommonJS and ESM, particularly for legacy Node.js variables like dirname and filename which are not available in standard ESM [1][2]. 1. Purpose of Shims: When enabled, shims allow code to access dirname and filename in ESM environments by automatically injecting the necessary logic (using fileURLToPath and import.meta.url) [2]. This is essential when migrating projects from CommonJS to ESM [2]. 2. Vite+ Configuration: In Vite+, projects often configure their builds through vite.config.ts [3]. Issues have been reported where tsdown-style shims (which Vite+ leverages for bundling) were not correctly bundled or resolved after migrating to Vite+, specifically leading to [UNRESOLVED_IMPORT] errors for esm-shims.js [4][5]. This was addressed in updates to the core package where the shim file is copied to the distribution directory during bundling [5]. 3. ESM and dirname/filename: While standard ESM does not support these variables, the Vite+ toolchain supports "native" config loading (which relies on native Node.js ESM loading) [6][7]. When using native ESM, developers are encouraged to move away from these CJS globals toward standard ESM patterns (e.g., using import.meta.url) [7]. 4. Potential Pitfalls: - Configuration Errors: If your vite.config.ts relies on these globals in an environment expecting strict ESM compatibility, it may fail unless the config loader handles the transformation [6]. - Tooling Issues: Some tools (like Oxlint or Oxfmt) that use dynamic import to load configuration files may encounter issues if the config relies on non-standard ESM features like dirname, leading to recommendations to decouple configuration into smaller, standard-compliant files [6]. If you are encountering errors related to these shims in Vite+, ensure you are on a recent version of the toolchain, as missing shim files in the distribution were a known issue [4][5]. When defining your project configuration, prioritize standard ESM syntax to avoid reliance on these shims where possible [7].
Citations:
- 1: https://tsdown.dev/options/shims
- 2: https://github.com/antfu/skills/blob/main/skills/tsdown/references/option-shims.md
- 3: https://github.com/voidzero-dev/vite-plus
- 4: tsdown shims are not available after migration to vite+ voidzero-dev/vite-plus#999
- 5: fix(core): copy esm-shims.js to dist during tsdown bundling voidzero-dev/vite-plus#1013
- 6: lint,fmt: Known vite.config.ts releated issues voidzero-dev/vite-plus#930
- 7: Default to
--configLoader=nativevitejs/vite#21546
🌐 Web query:
Vite documentation config commonjs-shims dirname filename __dirname __filename ts
💡 Result:
In modern Node.js and Vite environments, dirname and filename are CommonJS-specific global variables that are not available in ES modules (ESM) [1][2]. While Vite does not automatically provide these shims in your application code, you can resolve the issue using the standard Node.js approach or specific build-time shims [1][3][4]. 1. Recommended Approach (Node.js Standard): Instead of relying on dirname or filename, you should use the native import.meta.url [1]. In modern Node.js versions (v20.11.0+ / v21.2.0+), you can use import.meta.dirname and import.meta.filename directly [1]. For older versions, use the following pattern: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const filename = fileURLToPath(import.meta.url); const dirname = dirname(filename); 2. Vite Configuration Files (vite.config.ts/js): Vite internally transforms dirname and filename in configuration files if they are not in an ESM context (i.e., if your package.json does not have "type": "module") [5][2]. If you are using "type": "module" in your project, Vite will not provide these shims in your config file, and you must manually define them using the pattern above [2]. 3. Build-Time Shims: If your project or dependencies require dirname or filename in your ESM bundle, you can use build-time shims [6][4]. - Plugins: You can use a custom Vite plugin or build tools like tsdown that provide a shims: true option to automatically inject these variables into your ESM output [6][4]. - Library Authors: If you are encountering these errors from a third-party dependency, you may need to patch the dependency or use a plugin to polyfill the environment [3][4]. Note that Vite's build.commonjsOptions is primarily designed for Rollup's CommonJS plugin to handle CJS-to-ESM transformations and is generally not the tool used to provide global shims for your own code [7][8].
Citations:
- 1: https://nodejs.github.io/package-examples/05-cjs-esm-migration/migrating-context-local-variables/
- 2: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 3: __dirname in dependency not being handled by Vite vitejs/vite#8482
- 4: https://dev.to/rxliuli/developing-and-building-nodejs-applications-with-vite-311n
- 5: https://v2.vitejs.dev/config/
- 6: https://github.com/antfu/skills/blob/main/skills/tsdown/references/option-shims.md
- 7: https://v2.vite.dev/config/
- 8: https://github.com/vitejs/vite/blob/main/docs/guide/migration.md
🌐 Web query:
vite documentation "dirname filename" "__dirname" ES module package type module
💡 Result:
In ECMAScript modules (ESM)—which are used when "type": "module" is set in package.json or when using.mjs files—the global variables dirname and filename are not available [1][2][3]. To obtain the directory or file path in modern environments (Node.js 20.11.0 and later), you should use the built-in import.meta properties [2][4][5]: const dirname = import.meta.dirname; const filename = import.meta.filename; For older Node.js versions, you can reconstruct these values using the following approach [6][2]: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const filename = fileURLToPath(import.meta.url); const dirname = dirname(filename); Important Notes: 1. Vite Configuration: While older Vite documentation may have suggested Vite handles these variables automatically, this behavior primarily applies to CommonJS-style configurations [7][1]. If you are using native ESM (e.g., "type": "module"), you must define these variables manually as shown above [1][8]. 2. Browser Compatibility: These properties (import.meta.dirname/filename) and Node.js-specific modules (node:path, node:url) are intended for use in Node.js environments [2]. They will not work in the browser, where import.meta.dirname will return undefined [2]. 3. Dependency Issues: If you encounter errors due to dependencies that rely on dirname, this is often because the dependency is written for CommonJS [8]. You may need to patch the dependency or configure Vite to handle CommonJS transformations if necessary [9][10].
Citations:
- 1: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 2: https://www.sonarsource.com/blog/dirname-node-js-es-modules/
- 3: https://typeerror.org/docs/node/esm
- 4: https://github.com/mcollina/desm/
- 5: https://biomejs.dev/linter/rules/no-global-dirname-filename/
- 6: https://humanwhocodes.com/snippets/2023/01/mimicking-dirname-filename-nodejs-esm/
- 7: https://v2.vitejs.dev/config/
- 8: __dirname in dependency not being handled by Vite vitejs/vite#8482
- 9: Vite build error: __dirname from deeply nested dependency remix-run/remix#8847
- 10: https://vite.dev/guide/troubleshooting
🌐 Web query:
"import.meta.dirname" "vite-plus" "vite.config.ts" "__dirname"
💡 Result:
In modern Vite configurations, particularly when working with ES modules (such as in projects with "type": "module" in package.json or when using.ts config files), import.meta.dirname is the recommended way to resolve directory paths [1][2]. Historically, Vite attempted to automatically replace dirname in configuration files [3][4], but this behavior can be unreliable, especially in ESM environments where dirname is not natively available [4][5]. To safely resolve file paths in your vite.config.ts, you can use import.meta.dirname directly [1][2]: import { resolve } from 'node:path' import { defineConfig } from 'vite' export default defineConfig({ build: { rollupOptions: { input: { main: resolve(import.meta.dirname, 'index.html'), }, }, }, }) If you are working in an environment where import.meta.dirname is not supported (older Node.js versions), the standard approach is to derive it from import.meta.url [6][7]: import { dirname } from 'node:path' import { fileURLToPath } from 'node:url' const dirname = dirname(fileURLToPath(import.meta.url)) Using import.meta.dirname is now the preferred, idiomatic approach in Vite [1][2].
Citations:
- 1: vitejs/vite@7becf5f
- 2: https://vite.dev/guide/build
- 3: https://v2.vitejs.dev/config/
- 4: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 5: Config should be parsed with a target what supports import.meta vitejs/vite#1499
- 6: https://theroadtoenterprise.com/blog/how-to-set-up-path-resolving-in-vite
- 7: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules
Use ESM-compatible path resolution in vite.config.ts.
examples/vanilla-js/vanilla-custom-side-menu/package.json is "type": "module", so __dirname is not provided by Node here. Derive configDir from import.meta.url and reuse it for the three path expressions at lines 16, 22, and 26 instead of relying on Vite/VitePlus injection.
[low_effort_and high_reward]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/vanilla-js/vanilla-custom-side-menu/vite.config.ts` at line 16,
Update the Vite configuration path handling to derive a reusable configDir from
import.meta.url, then use configDir in the three path expressions currently
based on __dirname. Remove reliance on Vite/VitePlus __dirname injection while
preserving the existing resolved paths.
Source: MCP tools
Summary
This PR adds an example for instantiating an editor and adding a basic side menu in vanilla JS. The docs have been updated to use the example.
Closes #2441
Rationale
The existing vanilla JS docs were out of date and the example code was incorrect, throwing errors.
Changes
Impact
N/A
Testing
N/A
Screenshots/Video
N/A
Checklist
Additional Notes
N/A
Summary by CodeRabbit
New Features
Documentation