Skip to content

fix: revert cjs transform for dev ssr deps#274

Merged
rturnq merged 1 commit intomainfrom
fix-revert-cjs-transform
Apr 24, 2026
Merged

fix: revert cjs transform for dev ssr deps#274
rturnq merged 1 commit intomainfrom
fix-revert-cjs-transform

Conversation

@rturnq
Copy link
Copy Markdown
Contributor

@rturnq rturnq commented Apr 24, 2026

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

🦋 Changeset detected

Latest commit: 77db41b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@marko/vite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Walkthrough

This change replaces the CommonJS-to-ESM transformation implementation to use @chialab/estransform instead of the previous lexer and string-mutation approach. Dependencies are updated to add @chialab/estransform and remove cjs-module-lexer, es-module-lexer, and magic-string. The new transformer uses AST walking to identify require() calls (excluding those in try blocks) and converts them to ESM imports, while handling CommonJS exports through a parseCommonjs utility. Test coverage is expanded with a comprehensive test suite validating transformation behavior across various scenarios. A new test fixture for isomorphic CommonJS transpilation is introduced, including a development server configuration and HTTP server setup for SSR testing.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether any description exists or relates to the changeset. Add a description explaining the purpose of reverting the CJS transform approach and any context about why @chialab/estransform is the preferred solution.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: revert cjs transform for dev ssr deps' directly matches the main change: reverting CJS transformation by replacing the lexer-based implementation with @chialab/estransform for SSR dependencies.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-revert-cjs-transform

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/cjs-to-esm.ts (1)

167-177: Dedup map uses inconsistent keys, so dedup never triggers.

Line [167] reads by specifier string, but Line [176] writes by AST node object. This guarantees duplicate entries for repeated require("same-module").

Suggested fix
-  const specs = new Map();
+  const specs = new Map<string, { id: string; specifier: string }>();
...
-        let spec = specs.get(specifier.value);
+        let spec = specs.get(specifier.value);
         if (!spec) {
           let id = `$cjs$${specifier.value.replace(/[^\w_$]+/g, "_")}`;
           const count = (ns.get(id) || 0) + 1;
           ns.set(id, count);
           if (count > 1) {
             id += count;
           }
           spec = { id, specifier: specifier.value };
-          specs.set(specifier, spec);
+          specs.set(specifier.value, spec);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cjs-to-esm.ts` around lines 167 - 177, The dedup map bug is caused by
storing the spec under the AST node object while reading by specifier.value,
preventing duplicates from being found; update the assignment to use the same
string key as the lookup (use specs.set(specifier.value, spec) instead of
specs.set(specifier, spec)) so reads via specs.get(specifier.value) and writes
are consistent (leave id generation and ns/count logic unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/spicy-zebras-buy.md:
- Line 5: The changeset note contains a package scope typo: replace the
incorrect package name "@chialabs/estransform" with the correct
"@chialab/estransform" in the .changeset/spicy-zebras-buy.md file so the
dependency reference is accurate (look for the string "@chialabs/estransform" in
the file and update it to "@chialab/estransform").

In `@src/__tests__/cjs-to-esm.test.ts`:
- Around line 53-60: The test title is misleading: it says "ignores require()
inside arrow functions" but the assertions expect the require to be rewritten;
update the test name to reflect that behavior (e.g., "rewrites require() inside
arrow functions") so it matches the assertions around transformCjsToEsm, the
local variable source, and the result.code check for 'from \"dep\"'. Ensure you
only change the test description string in the it(...) call.

In `@src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/src/index.js`:
- Around line 3-9: The handler function currently only handles req.url === "/"
and returns for all other requests, leaving the response open; update the
handler (export function handler) to explicitly handle non-root requests by
setting an appropriate status (e.g., 404), writing a response body or calling
res.end(), or delegating to a next/middleware handler if available, so that for
any req.url other than "/" you always end the response (rather than just
returning) and avoid hanging requests when called from middleware like
dev-server.mjs.

In `@src/cjs-to-esm.ts`:
- Around line 128-140: The TryStatement visitor currently walks only node.block,
so require() calls in catch (node.handler) and finally (node.finalizer) are not
ignored; update the TryStatement handling in the walker to also walk
node.handler.body (if node.handler exists) and node.finalizer (if present) with
the same inner CallExpression visitor that pushes require() nodes into
ignoredExpressions (reuse the existing CallExpression check logic) so require()
calls inside catch and finally are excluded the same way as those in try.
- Around line 157-160: The code assumes node.arguments[0] exists when checking
CommonJS require() calls; update the guard around the specifier extraction so
you first verify node.arguments.length > 0 (and that specifier is defined)
before checking specifier.type === "StringLiteral" to avoid crashes on bare
require() calls—locate the block that defines const specifier =
node.arguments[0] and the subsequent if (specifier.type === "StringLiteral") so
you can replace it with a safe check that only pushes into callExpressions when
an argument exists and is a StringLiteral.
- Around line 109-116: The try/catch around parseEsm() swallows the explicit
mixed-module Error, so mixed ESM/CJS files are not rejected; update the catch in
the parse/validation block (the try that calls parseEsm(code) and throws new
Error("Cannot convert mixed modules")) to capture the error (e.g., catch (err))
and rethrow when it is the mixed-module rejection (check err.message === "Cannot
convert mixed modules" or instanceof a specific error), otherwise only suppress
or handle parseEsm-specific recoverable errors; ensure you adjust the catch
logic in that block so the thrown mixed-module error is not neutralized.

---

Nitpick comments:
In `@src/cjs-to-esm.ts`:
- Around line 167-177: The dedup map bug is caused by storing the spec under the
AST node object while reading by specifier.value, preventing duplicates from
being found; update the assignment to use the same string key as the lookup (use
specs.set(specifier.value, spec) instead of specs.set(specifier, spec)) so reads
via specs.get(specifier.value) and writes are consistent (leave id generation
and ns/count logic unchanged).
🪄 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

Run ID: a17ce5b6-be36-43e4-a3fb-54d369e009ef

📥 Commits

Reviewing files that changed from the base of the PR and between c3a5eb8 and 77db41b.

⛔ Files ignored due to path filters (7)
  • package-lock.json is excluded by !**/package-lock.json and included by **
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/__snapshots__/build.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/__snapshots__/dev.expected.md is excluded by !**/__snapshots__/** and included by **
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/node_modules/dep/index.js is excluded by !**/node_modules/** and included by **
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/node_modules/dep/package.json is excluded by !**/node_modules/** and included by **
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/node_modules/transient-dep/index.js is excluded by !**/node_modules/** and included by **
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/node_modules/transient-dep/package.json is excluded by !**/node_modules/** and included by **
📒 Files selected for processing (11)
  • .changeset/spicy-zebras-buy.md
  • package.json
  • src/__tests__/cjs-to-esm.test.ts
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/dev-server.mjs
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/server.mjs
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/src/index.js
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/src/template.marko
  • src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/test.config.ts
  • src/__tests__/main.test.ts
  • src/cjs-to-esm.ts
  • src/index.ts

Comment thread .changeset/spicy-zebras-buy.md
Comment thread src/__tests__/cjs-to-esm.test.ts
Comment on lines +3 to +9
export function handler(req, res) {
if (req.url === "/") {
res.statusCode = 200;
res.setHeader("Content-Type", "text/html; charset=utf-8");
template.render({}, res);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Non-root requests can hang because fallback is missing.

On Line 4, only / is handled. For other URLs, the function returns without ending the response or delegating. When called from middleware (e.g., dev-server.mjs), this can leave requests unresolved.

🔧 Proposed fix
-export function handler(req, res) {
+export function handler(req, res, next) {
   if (req.url === "/") {
     res.statusCode = 200;
     res.setHeader("Content-Type", "text/html; charset=utf-8");
-    template.render({}, res);
+    return template.render({}, res);
   }
+  return next?.();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/fixtures/isomorphic-commonjs-transpiled-default/src/index.js`
around lines 3 - 9, The handler function currently only handles req.url === "/"
and returns for all other requests, leaving the response open; update the
handler (export function handler) to explicitly handle non-root requests by
setting an appropriate status (e.g., 404), writing a response body or calling
res.end(), or delegating to a next/middleware handler if available, so that for
any req.url other than "/" you always end the response (rather than just
returning) and avoid hanging requests when called from middleware like
dev-server.mjs.

Comment thread src/cjs-to-esm.ts
Comment thread src/cjs-to-esm.ts
Comment on lines +128 to +140
TryStatement(node) {
walk(node.block, {
CallExpression(node) {
if (
node.callee.type !== "Identifier" ||
node.callee.name !== "require"
) {
return;
}
ignoredExpressions.push(node);
},
});
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

try/catch exclusion only covers the try block.

Current logic ignores require() in try { ... } only. Calls inside catch/finally are still transformed, which breaks the stated exclusion behavior.

Suggested fix
     walk(ast, {
       TryStatement(node) {
-        walk(node.block, {
-          CallExpression(node) {
-            if (
-              node.callee.type !== "Identifier" ||
-              node.callee.name !== "require"
-            ) {
-              return;
-            }
-            ignoredExpressions.push(node);
-          },
-        });
+        const regions = [node.block, node.handler?.body, node.finalizer].filter(
+          Boolean,
+        ) as Node[];
+        for (const region of regions) {
+          walk(region, {
+            CallExpression(node) {
+              if (
+                node.callee.type !== "Identifier" ||
+                node.callee.name !== "require"
+              ) {
+                return;
+              }
+              ignoredExpressions.push(node);
+            },
+          });
+        }
       },
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cjs-to-esm.ts` around lines 128 - 140, The TryStatement visitor currently
walks only node.block, so require() calls in catch (node.handler) and finally
(node.finalizer) are not ignored; update the TryStatement handling in the walker
to also walk node.handler.body (if node.handler exists) and node.finalizer (if
present) with the same inner CallExpression visitor that pushes require() nodes
into ignoredExpressions (reuse the existing CallExpression check logic) so
require() calls inside catch and finally are excluded the same way as those in
try.

Comment thread src/cjs-to-esm.ts
@rturnq rturnq merged commit aca09c6 into main Apr 24, 2026
9 checks passed
@rturnq rturnq deleted the fix-revert-cjs-transform branch April 24, 2026 18:49
@github-actions github-actions Bot mentioned this pull request Apr 24, 2026
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.

1 participant