RFC: npm package improvements #2815
Replies: 10 comments 24 replies
-
I just saw that this is also related to: #1908. Of course I can open a pull request, but these are major and breaking changes, so I think it's better to discuss about the integration first. Also note that my demo (https://github.com/WolfgangDrescher/verovio-npm-improvements) seems to break currently. I get a JS error |
Beta Was this translation helpful? Give feedback.
-
Thank you for looking into this. Let's move it to a discussion and then have issues for each topic separately whenever needed. |
Beta Was this translation helpful? Give feedback.
-
After further research, the decision for ESM support, or rather the way in which it should be implemented, should be made carefully. Further thoughts and options on this topic:
From my understanding deciding for ESM should also be a decision for a bundler that handles all the different versions for the verovio npm package. esbuild looks like an interesting tool. But Rollup.js, Webpack or another bundler should also do the trick. Some sources:
|
Beta Was this translation helpful? Give feedback.
-
I have set up a repository to test some functions with different ways to include Verovio: https://github.com/WolfgangDrescher/verovio-npm-examples. Implementing this and with further research, I found out a bunch of interesting things.
import {dirname} from 'path';
globalThis.__dirname = dirname(import.meta.url);
import Module from 'verovio/wasm/verovio-toolkit-wasm-hum.js'; This fix will work if you run the node script with ESM (e.g. the Somehow running this Emscripten module with frameworks like Vue.js or React their bundlers don't seems to bother about this.
const { VerovioToolkit } = require('verovio');
const fs = require('fs');
import('verovio/wasm/verovio-toolkit-wasm-hum.js').then(Module => {
console.log(Module.default);
Module.default().then((VerovioModule) => {
const verovioToolkit = new VerovioToolkit(VerovioModule);
console.log(verovioToolkit.getVersion());
const score = fs.readFileSync('demo.krn');
verovioToolkit.loadData(score);
const data = verovioToolkit.renderToSVG(1, {});
console.log(data);
});
}); But this again will lead to the
Direct browser support could look like this: <script type="module" src="verovio/wasm/verovio-toolkit-wasm-hum.js"></script> <!-- this exposes some kind of global variable for the Module like window.VerovioWasmFactory -->
<script type="module">
import { VerovioToolkit } from './verovio/index.mjs';
VerovioWasmFactory().then((VerovioModule) => {
const verovioToolkit = new VerovioToolkit(VerovioModule);
console.log(verovioToolkit.getVersion());
verovioToolkit.loadData(score);
const data = verovioToolkit.renderToSVG(1, {});
});
</script>
Click to expand logs
Please note that all demos currently fail because of the already mentioned |
Beta Was this translation helpful? Give feedback.
-
How are you building the Verovio toolkit? Are you using the |
Beta Was this translation helpful? Give feedback.
-
Would it be possible to define a "Proxy module" for an ES6 environment that abstracted away the need to make changes to the Verovio build directly? I'm thinking that you could define a module ( Then you could interact with the proxy in an ES6 environment, but the underlying library does not need to change. |
Beta Was this translation helpful? Give feedback.
-
I am not a big fan of submodules. Because the JS build part is pretty small it is OK to have it in the main repository in my opinion.
Yes, that is a good idea. |
Beta Was this translation helpful? Give feedback.
-
I managed to fix the issue with I updated https://github.com/WolfgangDrescher/verovio-npm-examples. On the Now I can experiment further with a backward compatible package. Some new thoughts:
Anything I need go consider before I continue with the development?
Okay, I also try to avoid submodules whenever possible. Still I think having a dedicated repository for the npm package would be easier for beginners to get started with verovio so they don't need to understand the full verovio repo structure with emscripten, c++ and all if they just want to use the JS build. And it could be better to find examples in the repo with a good Fabien Potencier, the creator fo Symfony, has a talk on YouTube about this: https://youtu.be/4w3-f6Xhvu8. In fact they are using split.sh, written in Go and libgit2, that is much faster than |
Beta Was this translation helpful? Give feedback.
-
I have a version ready that passes all tests and that I'm happy with for now. Check out the Comments and explanation
{
"main": "dist/verovio-toolkit-wasm.js",
"exports": {
".": "./dist/verovio-toolkit-wasm.js",
"./next": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./wasm": "./modules/verovio-toolkit-wasm-modularize.js"
}
}
const createVerovioModule = require('verovio/wasm');
const { VerovioToolkit } = require('verovio/next');
createVerovioModule().then(VerovioModule => {
const verovioToolkit = new VerovioToolkit(VerovioModule);
});
<script src="./node_modules/verovio/modules/verovio-toolkit-wasm-modularize.js" defer></script>
<script type="module">
import { VerovioToolkit } from './node_modules/verovio/dist/index.mjs';
// ...
</script>
Naming that need to be discussed
|
Beta Was this translation helpful? Give feedback.
-
I just opened a pull request for this: #2937 |
Beta Was this translation helpful? Give feedback.
-
I have recently resumed the development of the Web Worker integration I started in #1315 (also see #1253). I have come to a point where it would be best if the package code (which is currently just a single file) was split into multiple files so that they could be imported into the worker file without any code repetitions. For this reason, I started to rethink the logic of npm packages for Verovio and came across some questions:
JavaScript build matrix
Why does
buildToolkit
build multiple versions of Verovio? What is the difference between the JS build and the npm build, except that the npm build exports a node module? What's the difference between thewasm
and the non-wasm
build? What is the difference between the light and the non-light version. Why do we need so many different versions and what are the use cases? The toolkit matrix in the GitHub workflow is quite big: https://github.com/rism-digital/verovio/blob/develop/.github/workflows/ci_build.yml#L257-L272. It looks like awasm
build with Humdrum support is missing in theci_build.yml
workflow. E.g. like this:Bundler
If we decide to split the source code of the current
index.js
into multiple files, it would probably be a good idea to bundle the code again with a bundler like Webpack or rollup.js. What could speak against this? A bundler could also make sure that the file can be used in multiple environments: node with CommonJS, node with ESM or even directly in the browser. There is a nice example in the Vite documentation that shows how the files could be bundled. See this example for thepackage.json
config:This could drastically increase the size of the npm package, since the Emscripten build would have to be included once as a source file and a second time inside the final bundled file. However, this could be solved with the
files
entry inpackage.json
or a.npmignore
file: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files.In my second example in the next ESM section the Emscripten build would not be bundled, so the size of the bundle would not be a problem.
ESM
It would probably be a good idea to support ESM for the npm packages, as suggested in #2473. That would mean to bump
engines.node
inpackage.json
to>= 12.0.0
. But since Node.js v18 was released just some days ago, the current LTS version is v16, and v12 is only supported until2022-04-30
I don't think this would be a problem. See https://nodejs.org/en/about/releases/.ESM would even allow native integration directly in modern browsers, as pointed out in #2473 (https://caniuse.com/es6-module), and it is also possible to use the
import
andexport
syntax within a Node.js environment. This can be achieved by using the.mjs
file extensions for single files, or by using"type": "module"
in the package.json file to enable ESM for the entire package.I found a configuration for the Emscripten build setp that should work:
-s MODULARIZE=1 -s EXPORT_ES6=1
. However, this seems to break the current setup for using the Verovio Toolkit in JavaScript, since with this flags the module needs to be created first and then returns a promise that needs to be awaited. But with the promise this seems to be a very straight forward way to set the module up! See https://stackoverflow.com/a/53384917/9009012. It could also solve some problems I had in hot reload mode with theonRuntimeInitialized
event.Something like this could be the new setup script:
Another option could be like so:
Or directly in a modern browser:
I think the second option would be the best solution, although the import is a bit verbose with two statements. I have decoupled the dependency from a global Module variable and the toolkit now has to get the Module instance passed in the constructor. This would allow the toolkit to be bundled with a tool like rollup.js or Webpack, but the Emscripten build itself would not be included in that build since you have to import it separately. This would even allow multiple versions of the JS build (e.g. the current build matrix from the
ci_build.yml
pipeline) to be delivered within the same npm package, so the newverovio-humdrum
npm package would no longer be needed. All built variants can be added into the wasm folder. This would only increase the file size of the verovio npm package, but that doesn't seem to become problematic for 20MB or so: npm/npm#12750 (comment). The bundle size of the developer application wouldn't explode either, since only the Module version they import will be bundled.In any case, this would be a breaking change and therefore it would be a good idea to implement it with the next major release
v4.0.0
.Web Worker
I've been researching Web Worker integration for npm packages and I think the best solution would be for Verovio to ship a file that developers can use to let their bundlers build the worker file for them.
The best example I found is from the Monaco Editor repository (Visual Studio Code). See https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md and vitejs/vite#1791 (comment).
The Vite example (it is using rollup.js behind the scenes) for integrating the ESM version of the Monaco editor as a web worker is easy to understand. Vite can setup a file as a web worker by simply adding
?worker
after the import statement. We should leave it to the verovio package users to create the worker, but provide a file from which they can generate it. E.g.:Insinde the verovio package we could check if the
VerovioEnvironment.createWorker
variable is present, if so create the toolkit as a worker and if not with native JavaScript:That was initially why I wanted to refactor the vervio npm package so that the 10MB Emscripts wasm build doesn't have to be included twice in the package, but it can be imported.
Without Vite or with another bundler, as far as I know, a worker can be created as follows:
new Worker(new URL('./file/to/verovio.worker.js', import.meta.url)));
. But I haven't tested this myself yet.Since the worker must use async method calls on the toolkit (see https://github.com/WolfgangDrescher/verovio-worker), I suggest also adding a Toolkit Proxy (maybe with the adapter pattern) for the non-worker version to execute Verovio toolkit calls fake asynchronously (the promise becomes resolved immediately) to make the syntax is interchangeable, no matter whether you use a worker or not. I would suggest something like that:
Click to expand the code example
window.onunload event
Is the
window.onunload
event that destroys thewasm
instance really needed? As far as I know, the browser does this itself. MDN also warns against using this event: https://developer.mozilla.org/en-US/docs/web/api/window/unload_event.I have set up a repository to showcase some of these features: https://github.com/WolfgangDrescher/verovio-npm-improvements. It's still work in progress and not very mature yet, but for a first preview it can give a good insight I think. To try it out you can add
"verovio": "github:WolfgangDrescher/verovio-npm-improvements",
into thepackage.json
dependencies. I can also provide a small example app in Vue.js if there is interest.Beta Was this translation helpful? Give feedback.
All reactions