Skip to content

Fix markbind serve -d#2868

Merged
yihao03 merged 6 commits intoMarkBind:masterfrom
Harjun751:hotreload-hotfix
Mar 27, 2026
Merged

Fix markbind serve -d#2868
yihao03 merged 6 commits intoMarkBind:masterfrom
Harjun751:hotreload-hotfix

Conversation

@Harjun751
Copy link
Copy Markdown
Contributor

@Harjun751 Harjun751 commented Mar 23, 2026

What is the purpose of this pull request?

  • Documentation update
  • Bug fix
  • Feature addition or enhancement
  • Code maintenance
  • DevOps
  • Improve developer experience
  • Others, please explain:

Overview of changes:

Fixes regression from #2863 due to migration to ESM environment

Markbind hot reload breaks due to the old import
syntax used in updating the Markbind Vue Bundle.

Change to ESM-compatible method by injecting require from createRequire and module objects into the CJS src code so that it can evaluate and be used.

Anything you'd like to highlight/discuss:

Testing instructions:
Run markbind serve -d

Proposed commit message: (wrap lines at 72 characters)

Fix markbind serve -d regression


Checklist: ☑️

  • Updated the documentation for feature additions and enhancements
  • Added tests for bug fixes or features
  • Linked all related issues
  • No unrelated changes

Reviewer checklist:

Indicate the SEMVER impact of the PR:

  • Major (when you make incompatible API changes)
  • Minor (when you add functionality in a backward compatible manner)
  • Patch (when you make backward compatible bug fixes)

At the end of the review, please label the PR with the appropriate label: r.Major, r.Minor, r.Patch.

Breaking change release note preparation (if applicable):

  • To be included in the release note for any feature that is made obsolete/breaking

Give a brief explanation note about:

  • what was the old feature that was made obsolete
  • any replacement feature (if any), and
  • how the author should modify his website to migrate from the old feature to the replacement feature (if possible).

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes markbind serve -d hot-reload regression by updating how the MarkBind Vue bundle is loaded in an ESM environment.

Changes:

  • Replaces the previous in-memory requireFromString approach with an ESM-compatible flow that writes the bundle to a temporary .cjs file and imports it.
  • Updates the bundle hot-reload path to await the new async loader.
  • Adds a .gitignore entry for vue-module*.cjs temp artifacts.

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/core/src/Page/PageVueServerRenderer.ts Loads the regenerated Vue bundle via temp .cjs file + dynamic import to support ESM.
.gitignore Ignores possible leftover temp .cjs files created by the new hot-reload loader.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 23, 2026

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 70.96%. Comparing base (19b34fe) to head (5cb1685).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
packages/core/src/Page/PageVueServerRenderer.ts 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2868      +/-   ##
==========================================
+ Coverage   70.88%   70.96%   +0.07%     
==========================================
  Files         131      131              
  Lines        7083     7085       +2     
  Branches     1668     1661       -7     
==========================================
+ Hits         5021     5028       +7     
+ Misses       1962     1957       -5     
  Partials      100      100              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Member

@gerteck gerteck left a comment

Choose a reason for hiding this comment

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

LGTM thanks for the quick fix. Tested on my machine and the fix works well.

Understanding:

  • Previous implementation relied on Node's CJS runtime internals, module.constructor to create a new module instance, module.paths to inherit the resolution paths, and _compile() to execute the source. None of these exist in ESM.

  • New implementation: As bundle being loaded is still CJS format (core-web bundle), which expects require, module, and exports to exist. Provide these by using createRequire(import.meta.url) for require function anchored to the current ESM file's location, so dependencies resolve correctly, a plain mod object to stand in for module/exports and wrapping source in a new Function(...) call to execute it in the global scope, injecting the fake CJS environment as parameters

The mod.exports.default ?? mod.exports at the end also handles both CJS and ESM-compiled-to-CJS export shape.

Can add testcases, seem meaningful as it would catch such regressions and would probably be the first few testcases to cover -d command, but up to you since this is a hotfix

Markbind hot reload breaks due to the old import
syntax used in updating the Markbind Vue Bundle.

Change to ESM-compatible method of writing src
to a temporary .cjs file that is imported
using the file path.

ESM only supports similar require functionality
when using its experimental vm.Module object
that can only be accessed when ran with a flag.
Implementation currently writes to a
temp file, which is placed in the directory
of the source code so that it can crawl
the require tree.

This may not work in production environments,
especially if markbind-cli was installed globally
as a root user and used as a normal user, as
it wouldn't be able to write into the directory
that markbind lives in. (additionally, we
can't write anywhere else as it then wouldn't
be able to crawl the dep tree)

Use createRequire with an eval workaround
to import it instead.

This prevents the need of writing to a temp
directory, similar to before. However,
ESM still does not expose the `module` object,
so the same method as before wouldn't work.
Therefore, use Function() to import it,
passing in the constructed require object.
requireFromString is untested and private

Due to changes in build output, this method
broke silently, highlighting importance
of testing this section.

Add unit tests checking import functionality.
Copy link
Copy Markdown
Contributor

@yihao03 yihao03 left a comment

Choose a reason for hiding this comment

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

LGTM!

function requireFromString(src: string) {
// Use createRequire since bundle is CJS. This allows require() calls within the bundle
// to be resolved relative to this file.
const require = createRequire(import.meta.url);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor comment, would it be better to move this to the module scope? i.e. instantiate it outside of this function. A brief conversation with claude says:

The practical gains are modest since createRequire is cheap, but there are a few concrete reasons to prefer this:
Correctness — the binding URL (import.meta.url) is the same value every single call, so there's no reason to re-evaluate it. Hoisting makes that intent explicit.
Allocation — each call currently allocates a new require function object. If requireFromString is called frequently (e.g. per-page during SSR), this creates unnecessary GC pressure.
Clarity — a module-level require signals to readers that this is a stable, shared resolver, not something that varies per invocation. The current placement inside the function implies it might differ per call, which is misleading.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair point. I'll do that real quick 👍

// load dependencies.
try {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
new Function('require', 'module', 'exports', src)(require, mod, mod.exports);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is really cool. u surprise me everyday with something new

@yihao03 yihao03 merged commit 8f368b3 into MarkBind:master Mar 27, 2026
10 checks passed
@github-actions github-actions bot added the r.Patch Version resolver: increment by 0.0.1 label Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

r.Patch Version resolver: increment by 0.0.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants