Prose Mirror, buildless! (ESM support)

Hi there!

Browsers have improved a lot over the past years and it is now possible to do web development without requiring any build tools. That’s what I do by serving es modules with es-dev-server on my dev environment, buildless! Here is a nice article introducing the benefits:

https://dev.to/open-wc/developing-without-a-build-1-introduction-26ao

Recently, I was comparing solutions for creating/integrating a rich text editor and Prose Mirror (PM) looks really interesting. Unfortunately, PM published NPM packages do not work buildless with modern web apps.

I am not the first to raise a similar need:

Going buildless, especially in dev environments is more and more frequent and the trend is not going to decrease with all major browsers supporting ES modules natively.

In order to assess what changes are required to make PM work buildless, I made a Proof of Concept locally for the basic example. As a result, the changes that are required are quite minor and are non-breaking! but they involve multiple packages.

Briefly speaking, it requires adding the non-standard but commonly accepted “module” field to all PM package.json packages. All dependencies must also provide an ES6 version of the code (no problems since the source code is already packaged). Besides, PM has only a few external dependencies (and most are managed by @marijnh), it is easily feasible. From what I experienced there are only 4 external dependencies for the basic example:

  • prosemirror-history -> rope-sequence

  • prosemirror-keymap -> w3c-keyname

  • prosemirror-menu -> crel

  • prosemirror-model -> ordered-map

Once these are updated to provide an ES6 version of the code (I adapted them locally), then all is working, buildless!

My question is twofold: are there other people who would benefit from such an improvement? @marijn (Marijn Haverbeke), would you accept the required changes after review if I contribute the PRs?

PS: the module field looks like a simple, transition solution to a better solution that was introduced in NodeJS 12 and still experimental in NodeJS 13: https://nodejs.org/api/esm.html#esm_ecmascript_modules

2 Likes

I have created a dedicated organization, forked all required repositories (24 repositories including transitive dependencies) and applied changes:

https://github.com/prosemirror-esm

As mentioned previously, the changes are quite minor but are spread over several repositories.

I decided to publish packages on Github Package Registry to prevent polluting npmjs if the changes are eventually merged with upstream. To import dependencies, you need to create a .npmrc file in your project folder with the following content:

@prosemirror-esm:registry=https://npm.pkg.github.com

Then, you can import dependencies using common methods:

"@prosemirror-esm/prosemirror-example-setup": "^1.0.1",
"@prosemirror-esm/prosemirror-model": "^1.7.4",
"@prosemirror-esm/prosemirror-schema-basic": "^1.0.1",
"@prosemirror-esm/prosemirror-state": "^1.2.4",
"@prosemirror-esm/prosemirror-view": "^1.12.2"

The forked repositories are synchronized automatically but conflicts and package publications require manual intervention, so in the long term it will be hard to maintain. @marijn your thoughts are welcome. I would be happy to contribute the changes to upstream repositories.

Sure, let’s start with pull request to one of the cjs-only packages—if you port the source to ES6 modules and add a build step (using some small, quick, tool, I’m not sure what the options are at the moment, but I’d like to avoid Babel’s bloat for trivial packages like this), I’ll review your PR.

Does a "type": "module" field in package.json allow a package to expose both cjs and esm files? Or is that not what you’re suggesting? (Switching to esm entirely doesn’t seem like an option for the time being.)

Great! Thanks for your reply.

I will start with a PR to orderedmap so that we can agree on the method.

Does a “type”: “module” field in package.json allow a package to expose both cjs and esm files?

No. This field is still in the experimental part of the doc and would imply to switch entirely to ESM. I agree with you this is not an option.

Based on the recommendations, it seems the best would be to provide an ESM version of the code using the dedicated mjs extension, ideally, next to the cjs version of the files. Also, due to a large ecosystem that supports the module field (e.g. "module": "index.mjs"), it worths defining this field. I think it will be more clear in the PR.

I have sent 2 PRs to give an idea about what the changes are:

Looking forward to your feedback before going further.

@marijn PRs have been submitted for all submodules with the exception of prosemirror-schema-table that is deprecated. Do you plan to perform a release soon after the PRs are merged?

Running the release script right now. They should all be on npm in the coming minutes.

Thanks for your time and the release.

I noticed the PR for prosemirror_tables was not merged and thus no release was performed. Is there a reason?

Following your comment on prosemirror-menu, I added a suggestion:

https://github.com/ProseMirror/prosemirror-menu/pull/23#issuecomment-551996839

I don’t maintain prosemirror-tables, so I can’t help there.

And this is how the result can be used, I guess: https://pfiver.github.io/hello-prosemirror/

This is just a prove of concept. The he modules are loaded via unpkg.com, which is abysmally slow and unreliable.

It took me some time to figure out how it’s done. Thus I am posting this link to the result, in hopes that it might save the next tinkerer some time.

For local development, performance is much better when using es-dev-server, for which I use this package.json:

Anyway, here it goes:

  1. npm install
  2. npm run start
{
  "name": "hello-prosemirror",
  "version": "0.0.1",
  "dependencies": {
    "prosemirror-model": "*",
    "prosemirror-state": "*",
    "prosemirror-view": "*",
    "prosemirror-schema-basic": "*",
    "prosemirror-schema-list": "*",
    "prosemirror-example-setup": "*"
  },
  "devDependencies": {
    "es-dev-server": "*"
  },
  "scripts": {
    "start": "es-dev-server --node-resolve --watch"
  }
}

Nice! I recently moved my CodeMirror 6 dev setup over to something similar (using esmoduleserve, a lightweight alternative to es-dev-server), and it really made the demo load faster. I’ll probably at some point do the same for the main ProseMirror demo.

1 Like

what’s the benefit dev-server vs. just loading the src files directly? with http/2 es native seems super fast and there is no util and config dependencies

Have you tried that? Because it doesn’t work. Browsers can’t resolve dependencies like "prosemirror-model", only precise URLs.

yes, as I wrote here: Pure ESM module loading in browser? - it’s not “zero” config “yet” but with a few lines in the shim it could be (see my link to the git issue) - but it only takes a single mapping like this per module "prosemirror-model": "/node_modules/prosemirror-model/src/index.js"

disclaimer: I might be missing something here as I am new at this

1 Like

This example emits an error when pressing the enter key at the end of a line. Please explain why this is occurring. The error does not occur when running the example bundled.

Uncaught RangeError: Can not convert <> to a Fragment (looks like multiple versions of prosemirror-model were loaded)

https://pfiver.github.io/hello-prosemirror/

2 Likes