Skip to content

feat(eval): auto-detect CJS vs ESM in deno eval#32472

Merged
bartlomieju merged 9 commits intomainfrom
copilot/add-eval-flag-to-deno
Mar 17, 2026
Merged

feat(eval): auto-detect CJS vs ESM in deno eval#32472
bartlomieju merged 9 commits intomainfrom
copilot/add-eval-flag-to-deno

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 4, 2026

Working on Node compat with AI agents it's often frustrating, because they try to reproduce issue by using deno eval 'const fs = require("fs"); ...'. Also it would be helpful in most situations to be able to use ESM/CJS interchangeably. So this PR adds auto-detect whether code passed to deno eval` is CommonJS or ESM.

Uses deno_ast::parse_program + compute_is_script() to check if the code contains import/export declarations. If it does, treat as ESM (.mjs); otherwise treat as CJS (.cjs).

This means deno eval "require('fs')" now works without needing --ext=cjs, and deno eval "import { ok } from 'node:assert'; ..." continues to work as ESM.

Copilot AI and others added 2 commits March 4, 2026 23:57
Add `-e/--eval` flag to the top-level `deno` command so that
`deno -e "require(...)"` evaluates the code as CommonJS.
This defaults to `--ext=cjs` unless explicitly overridden.

Co-authored-by: bartlomieju <13602871+bartlomieju@users.noreply.github.com>
Co-authored-by: bartlomieju <13602871+bartlomieju@users.noreply.github.com>
Copilot AI changed the title [WIP] Add -e/--eval flag for CommonJS module evaluation feat: add -e/--eval top-level flag for CommonJS evaluation Mar 5, 2026
Instead of defaulting to CJS, use deno_ast's compute_is_script() to
detect whether eval code is a script (CJS) or module (ESM). This
applies to both `deno -e` and `deno eval`. The --ext flag still
overrides auto-detection when explicitly provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bartlomieju bartlomieju changed the title feat: add -e/--eval top-level flag for CommonJS evaluation feat: add -e/--eval top-level flag with CJS/ESM auto-detection Mar 5, 2026
@bartlomieju bartlomieju marked this pull request as ready for review March 5, 2026 08:33
bartlomieju and others added 4 commits March 5, 2026 10:03
The CJS/ESM auto-detection was running for both `deno -e` and
`deno eval`, causing `deno eval` to treat code without import/export
as CJS. This broke unit tests that use Deno APIs (e.g. Deno.unrefTimer)
because CJS mode makes setTimeout return a Timeout object instead of
a number.

Now auto-detection only applies to the new `-e/--eval` top-level flag.
The existing `deno eval` subcommand retains its ESM default behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the `-e/--eval` top-level flag (to be added in a separate PR).
Apply CJS/ESM auto-detection to `deno eval` itself — when `--ext` is
not explicitly provided, the code is parsed with deno_ast to determine
if it contains import/export declarations (ESM) or not (CJS).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bartlomieju bartlomieju changed the title feat: add -e/--eval top-level flag with CJS/ESM auto-detection feat(eval): auto-detect CJS vs ESM in deno eval Mar 17, 2026
The previous auto-detection treated all code without import/export as
CJS, which broke existing `deno eval` usage (e.g. timer unit tests).
Now we only switch to CJS when the code is a script AND contains
CJS-specific patterns (require, module.exports, __dirname, etc.).
Code without these patterns defaults to ESM as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment on lines +248 to +257
// Only treat as CJS if it parses as a script AND contains CJS patterns.
// This preserves backward compatibility: code without imports/exports
// defaults to ESM (the longstanding deno eval behavior).
let has_cjs_patterns = is_script
&& (source_code.contains("require(")
|| source_code.contains("module.exports")
|| source_code.contains("exports.")
|| source_code.contains("__dirname")
|| source_code.contains("__filename"));
if has_cjs_patterns {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hack was needed because of the setTimeout global split. If we had a single setTimeout type it wouldn't be needed

Copy link
Copy Markdown
Member

@nathanwhit nathanwhit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nice to have it

@bartlomieju bartlomieju merged commit 9f327bb into main Mar 17, 2026
113 checks passed
@bartlomieju bartlomieju deleted the copilot/add-eval-flag-to-deno branch March 17, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants