Pure ESM module loading in browser?

Hi, I’m a n00b when it comes to loading js files - so can anyone tell me how I do pure ESM/ES6/ES2020 (?) module loading in latest Chrome browser? I can load e.g. import { EditorState } from "../node_modules/prosemirror-state/dist/index.es.js" (using latest TypeScript) but from there it goes import OrderedMap from 'orderedmap'; - and that cannot be resolved to anything. Same if loading from “src”.

What am I doing wrong? As primarily a backender - module loading in ecmascript seems very “noisy” and have given me a small headache in the last couple of hours :smiley: - so sry if this is not related to Prosemirror per say. Are there any boilerplate sample of getting started with Prosemirror out there? Pref. with dynamic loading.

You’re not doing anything wrong. This is a giant gap left in the (initial) implementation of ES modules in browsers. There’s work in progress to make it (a little) easier with import maps, but that’s only available on Chrome, behind a flag, and still requires you to create such a map.

You should be able to use a server like esmoduleserve to rewrite non-precise imports to precise ones on the fly, and load a ProseMirror-based app that way. I haven’t tried yet with ProseMirror, but that’s how I’m doing development on CodeMirror 6 now, which has a very similar module structure.

Thank you! That lifted a dark cloud from my head! Trying to find a missing puzzle piece that never existed… :exploding_head:

I got it to work like this - pure ESM using a tiny shimmer - tried to make a indexOf('prosemirror-') smartenheiner to avoid the bloated map for when there’s logic - but that’s not in the shim and also - it’s not in the “not-so-standard-yet” ESM:

<script defer src="/node_modules/es-module-shims/dist/es-module-shims.js"></script>
<script type="importmap-shim">{ "imports": {
	"orderedmap": "/node_modules/orderedmap/index.es.js",
	"rope-sequence": "/node_modules/rope-sequence/dist/index.es.js",
	"crelt": "/node_modules/crelt/index.es.js",
	"w3c-keyname": "/node_modules/w3c-keyname/index.es.js",
	"prosemirror-state": "/node_modules/prosemirror-state/dist/index.es.js",
	"prosemirror-view": "/node_modules/prosemirror-view/dist/index.es.js",
	"prosemirror-model": "/node_modules/prosemirror-model/dist/index.es.js",
	"prosemirror-schema-basic": "/node_modules/prosemirror-schema-basic/dist/index.es.js",
	"prosemirror-schema-list": "/node_modules/prosemirror-schema-list/dist/index.es.js",
	"prosemirror-transform": "/node_modules/prosemirror-transform/dist/index.es.js",
	"prosemirror-history": "/node_modules/prosemirror-history/dist/index.es.js",
	"prosemirror-commands": "/node_modules/prosemirror-commands/dist/index.es.js",
	"prosemirror-dropcursor": "/node_modules/prosemirror-dropcursor/dist/index.es.js",
	"prosemirror-gapcursor": "/node_modules/prosemirror-gapcursor/dist/index.es.js",
	"prosemirror-menu": "/node_modules/prosemirror-menu/dist/index.es.js",
	"prosemirror-inputrules": "/node_modules/prosemirror-inputrules/dist/index.es.js",
	"prosemirror-keymap": "/node_modules/prosemirror-keymap/dist/index.es.js"
}}</script>
<script type="module-shim">
	importShim.fetch = async (url) => await fetch(url.indexOf('.js') < 0 ? url + ".js" : url);
</script>
....
<script type="module-shim">
	import { Fancy } from "./prosemirror/typescript-prosemirror.js"
	const pants  = new Fancy();
</script>

learned a lot about javascript loaders today - and I don’t like it at all! :smiley: but there you go if anyone needs to get started with prosemirror using a super simple setup as non-longhaired-and-slow-build-and-bundle-script-and-runners thingies… this "es-module-shims": "^0.4.6", goes into package.json

EDIT: You can drop the “importShim.fetch” line if not pointing to /src/ stuff or import like “./stuff” - then we are back to the ESM “std.” i spose…

1 Like

FYI on the ESM topic - I suggested this for the shim: https://github.com/guybedford/es-module-shims/issues/80 (Pre-url map lookup handling)

Thanks for this. I’m very much a noobie to the world of JavaScript and imports, exports, require, npm, etc. I only understand 50% of this conversation. I’d appreciate it if one of you (andersmad or marijn) could spell it out for me at a more basic level.

Basically, all I want is to be able to use ProseMirror in a static html page on my local computer.

I want to be able to load a JavaScript file in that page where I can use all the ProseMirror classes, for instance, call the Schema constructor to build a new schema:

const mySchema = new Schema( {...} )

I don’t know how to do this. I’ve tried various methods.

I puzzled my way through the require-pm.js file that Marijn uses on his webpage to enable the require syntax. Then I was able to include the following line in my HTML page,

<script src="https://prosemirror.net/examples/prosemirror.js"></script>

which would then magically create a PM global object containing lots of pre-loaded things, like a state, a view, a keymap. But it didn’t give me the ability to call the Schema class constructor directly. So now I am stuck.

You will need to run a bundler to do that. I recommend Rollup. You’d write your code import-ing ProseMirror components, then run rollup on it to produce a single big file that has everything you use in it, and include that with a <script> tag.

Schema should be under PM.model.Schema in that bundle.

Thanks for your answer. I’ve looked at Rollup, because you mentioned it before. I am 90% there now. But there is still a conceptual difficulty I have. Namely, I don’t understand the workflow of creating a JavaScript-based webpage that uses modules. It seems to place the chicken before the egg. Namely, you must -first- write all your code blindly, importing the ProseMirror library with import statements. You are not able to run it yet since the browser doesn’t understand what you are talking about! And then, when you’re finished this incredible task of writing a webpage without once being able to “run” your code, you magically “rollup” your code into a “bundle.js” file which you can finally include in the html, and then for the first time you can actually load your webpage! This is very confusing to me. I am missing something important…!

@bruce: rollup or webpack goes through the jungle of js module loading and makes one big file to be used from the browser… that’s good if you don’t need async module loading, using slow http1 and it can tree shake it so it leaves out unused stuff etc…

however i like the dynamic loading - and I do think i posted all the code needed for that above here… this way you don’t have to build anything (tiny clean fast devel. setup) - the browser will load it as it needs it… its using a “shim” meaning that its features to come to the browser - they are just not there yet, so a clever dude made it happen using that shim that tries to follow the coming standard…

if your not experimenting for next gen stuff as I am - go with the rollup/webpack/pre-build stuff… or grab the big prosemirror.js from the sample/doc site… you make e.g. rollup execute each time a file changes - then if you use typescript (then you don’t code “blindly”) you save, it transpiles and then it packs the files and then you F5 to see what’s what :slight_smile:

There’s nothing stopping you from running rollup during development to test your current work. It even has a -w command line option that makes it constantly rebuild your project as you change files.

Ok - thanks a lot to you both for your help.

I refreshed the script I used to setup PM basic example so maybe it will help you to quick test the editor in browser.

@andersmad It is unbelievably awesome that you posted the import-shim setup. Thank you so much for taking the time to figure this out. I’ll aim to create a new starter project for Prosemirror with this setup plus the content of prosemirror-example-setup builtin and hackable inside the starter app. Bravo!

1 Like