Skip to content

Commit

Permalink
Support dynamic font loading in as many situations as possible
Browse files Browse the repository at this point in the history
(including in web workers). For CJS builds, utilize webpack's
publicPath feature. In ESM builds, dynamic import() works natively.
  • Loading branch information
ronyeh committed Dec 8, 2021
1 parent 29c43f0 commit 6523259
Show file tree
Hide file tree
Showing 22 changed files with 339 additions and 121 deletions.
25 changes: 19 additions & 6 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ let BANNER;
// PRODUCTION_MODE will enable minification, etc.
// See: https://webpack.js.org/configuration/mode/
const PRODUCTION_MODE = 'production';
// FOR DEBUGGING PURPOSES, you can temporarily comment out the line above and use the line below to disable minification.
// const PRODUCTION_MODE = 'development';
const DEVELOPMENT_MODE = 'development';

const CODE_SPLITTING = 'split';
Expand Down Expand Up @@ -71,14 +73,14 @@ function getConfig(file, bundleStrategy = SINGLE_BUNDLE, mode = PRODUCTION_MODE)
let chunkFilename = undefined;
if (bundleStrategy === CODE_SPLITTING) {
// Font files for dynamic import. See: webpackChunkName in async.ts
chunkFilename = 'vexflow-font-[name].js';
chunkFilename = '[name].js';
}

// Control the type of source maps that will be produced.
// If not specified, production builds will get high quality source maps, and development/debug builds will get nothing.
// See: https://webpack.js.org/configuration/devtool/
// In version 3.0.9 this was called VEX_GENMAP.
const devtool = process.env.VEX_DEVTOOL || (mode === PRODUCTION_MODE ? 'source-map' : false);
const devtool = process.env.VEX_DEVTOOL || (mode === DEVELOPMENT_MODE ? false : 'source-map');

let plugins = [new webpack.BannerPlugin(BANNER) /* Add a banner at the top of the file. */];
if (CHECK_FOR_CIRCULAR_DEPENDENCIES) {
Expand All @@ -103,29 +105,40 @@ function getConfig(file, bundleStrategy = SINGLE_BUNDLE, mode = PRODUCTION_MODE)
},
globalObject: globalObject,

// The `publicPath` is the base path for the dynamically loaded JS chunks.
// The `publicPath` is the base path for dynamically loaded JS chunks.
// https://webpack.js.org/guides/public-path/
// https://webpack.js.org/configuration/output/#outputpublicpath
// It is used by async.ts to import the font files at runtime.
// There isn't one setting for `publicPath` that will work for all deployments.
// In some scenarios, it needs to be './' to work, but in others it needs to be 'auto' to work.
// You can customize the `publicPath` below to work with your production environment.
// publicPath: undefined, // undefined and `auto` are equivalent.
// publicPath: 'auto', // https://webpack.js.org/guides/public-path/#automatic-publicpath
// publicPath: '',
// publicPath: './',
// Our solution below:
// Our solution:
// Specify the VEX_BASE_PATH environment variable at build time, or
// Specify the VEX_BASE_PATH global variable at runtime.
// The value of this option should end in a slash in most cases. For example: VEX_BASE_PATH=/js/
// If not specified, we set it to 'VEX_AUTO' which tells `src/publicpath.ts` to determine the path automatically.
publicPath: process.env.VEX_BASE_PATH ?? 'VEX_AUTO',
publicPath: process.env.VEX_BASE_PATH ?? './',
// Or comment out the above line, and set it to whatever you like:
// publicPath: '/my/custom/public/path/',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '...'],
},
devtool: devtool,
module: {
rules: [
{
// Add the magic import to async.ts to support dynamic font loading.
test: /async\.ts$/,
loader: 'string-replace-loader',
options: {
search: '/* IMPORT_WEBPACK_PUBLICPATH_HERE */',
replace: "import './webpack_publicpath';",
},
},
{
test: /(\.ts$|\.js$)/,
exclude: /node_modules/,
Expand Down
6 changes: 6 additions & 0 deletions demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
This folder contains examples of ways you can use VexFlow.

To run the demos, first build the VexFlow library by running `npm install` and then `grunt` from the main folder.

## worker/

Shows how to use web workers to render multiple scores simultaneously.

Start a web server with `npx http-server` from the `vexflow/` folder, then navigate to `http://127.0.0.1:8080/demos/worker/`.
4 changes: 4 additions & 0 deletions demos/fonts/all.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ <h3>Gonville</h3>
<h3>Petaluma</h3>
<div id="outputPetaluma"></div>
<p>View the source code for more information.</p>
<p>
<a href="core.html">Also see: core.html</a><br />
<a href="core-with-promise.html">Also see: core-with-promise.html</a>
</p>
<!--
The standard VexFlow library (build/cjs/vexflow.js) includes three music fonts: Bravura, Gonville, and Petaluma.
It statically bundles all the music fonts, and selects Bravura as its default.
Expand Down
4 changes: 4 additions & 0 deletions demos/fonts/core-with-promise.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<p>This demonstrates how using vexflow-core.js allows you to load fonts on demand.</p>
<p>This page calls Flow.setMusicFont(...).then(...) with a onFontsLoaded callback.</p>
<div id="output"></div>
<p>
<a href="core.html">See: core.html</a><br />
<a href="all.html">See: all.html</a>
</p>
<script>
// Optional: set the VEX_BASE_PATH global variable to specify the location of the vexflow-font-xxxx.js files.
// Normally, the font files are assumed to be in the same directory as vexflow-core.js. However, in some
Expand Down
13 changes: 12 additions & 1 deletion demos/fonts/core.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
body {
margin: 20px;
}
#output {
min-height: 220px;
background-color: #fbfbf0;
margin-top: 20px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
Expand All @@ -25,8 +31,13 @@
<a href="#" onclick="chooseFont('Petaluma')">Petaluma</a> &nbsp;
</div>
<div id="output"></div>
<p>
<a href="core-with-promise.html">See: core-with-promise.html</a><br />
<a href="all.html">See: all.html</a>
</p>
<script>
// window.VEX_BASE_PATH = "./FOO/";
// Set this variable if the font files (e.g., vexflow-font-bravura.js) are in a different folder from vexflow-core.js.
// window.VEX_BASE_PATH = "/path/to/fonts/";
</script>
<!-- The vexflow core build includes no music fonts. -->
<script src="../../build/cjs/vexflow-core.js"></script>
Expand Down
7 changes: 5 additions & 2 deletions demos/fonts/with-bravura.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
</head>
<body>
<p>
This page uses vexflow-core-with-bravura.js, which statically bundles only the Bravura font. You do not need to
call Flow.setMusicFont(...) because VexFlow will default to using Bravura.
This page uses <b>vexflow-core-with-bravura.js</b>, which statically bundles only the Bravura font. You do not
need to call Flow.setMusicFont(...) because VexFlow will default to using Bravura.
</p>
<div id="output"></div>
<p>
<a href="with-gonville.html">See: with-gonville.html</a>
</p>
<script src="../../build/cjs/vexflow-core-with-bravura.js"></script>
<script type="module">
const factory = new Vex.Flow.Factory({ renderer: { elementId: 'output', width: 500, height: 200 } });
Expand Down
7 changes: 5 additions & 2 deletions demos/fonts/with-gonville.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
</head>
<body>
<p>
This page uses vexflow-core-with-gonville.js, which statically bundles only the Gonville font. You do not need to
call Flow.setMusicFont(...) because VexFlow will default to using Gonville.
This page uses <b>vexflow-core-with-gonville.js</b>, which statically bundles only the Gonville font. You do not
need to call Flow.setMusicFont(...) because VexFlow will default to using Gonville.
</p>
<div id="output"></div>
<p>
<a href="with-petaluma.html">See: with-petaluma.html</a>
</p>
<script src="../../build/cjs/vexflow-core-with-gonville.js"></script>
<script type="module">
const factory = new Vex.Flow.Factory({ renderer: { elementId: 'output', width: 500, height: 200 } });
Expand Down
7 changes: 5 additions & 2 deletions demos/fonts/with-petaluma.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
</head>
<body>
<p>
This page uses vexflow-core-with-petaluma.js, which statically bundles only the Petaluma font. You do not need to
call Flow.setMusicFont(...) because VexFlow will default to using Petaluma.
This page uses <b>vexflow-core-with-petaluma.js</b>, which statically bundles only the Petaluma font. You do not
need to call Flow.setMusicFont(...) because VexFlow will default to using Petaluma.
</p>
<div id="output"></div>
<p>
<a href="with-bravura.html">See: with-bravura.html</a>
</p>
<script src="../../build/cjs/vexflow-core-with-petaluma.js"></script>
<script type="module">
const factory = new Vex.Flow.Factory({ renderer: { elementId: 'output', width: 500, height: 200 } });
Expand Down
59 changes: 59 additions & 0 deletions demos/worker/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
html {
font: 16px 'Helvetica Neue', Arial, sans-serif;
}
body {
margin: 20px;
}
canvas {
width: 500px;
height: 200px;
border: 1px solid gray;
display: block;
margin-bottom: 10px;
}
</style>
</head>
<body>
<p>
The five scores below are rendered by web workers via
<a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas" target="_blank">OffscreenCanvas</a>.
</p>
<script type="module">
if (window.Worker) {
const numScores = 5;

const workers = [];
const offscreenCanvases = [];
for (let i = 0; i < numScores; i++) {
document.body.insertAdjacentHTML(
'beforeend',
`<canvas id="outputCanvas${i}" width="1000" height="400"></canvas>`
);

// Test the standard bundle (vexflow.js) and also the minimal bundle with dynamically loaded fonts (vexflow-core.js).
const workerJS = Math.random() > 0.5 ? 'worker-vexflow.js' : 'worker-vexflow-core.js';

const w = new Worker(workerJS);
w.onmessage = function (e) {
console.log(`Message from #${i} / ${workerJS}: [${e.data}]`);
};
workers.push(w);
const offscreen = document.getElementById('outputCanvas' + i).transferControlToOffscreen();
offscreenCanvases.push(offscreen);
}

for (let i = 0; i < numScores; i++) {
const canvas = offscreenCanvases[i];
workers[i].postMessage({ canvas: canvas }, [canvas] /* transfer list */);
}
} else {
alert("This browser doesn't support web workers.");
}
</script>
</body>
</html>
39 changes: 39 additions & 0 deletions demos/worker/worker-vexflow-core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable */

// Load demos/worker/index.html in a

// Web Workers have an importScripts() method that allows you to load scripts. importScripts(...) is similar to require(...) in Node.js.
VEX_BASE_PATH = '../../build/cjs/';
importScripts(VEX_BASE_PATH + 'vexflow-core.js');

onmessage = function (e) {
postMessage('VexFlow BUILD: ' + Vex.Flow.BUILD);

const fonts = ['Bravura', 'Gonville', 'Petaluma'];
const randomFont = fonts[Math.floor(Math.random() * fonts.length)];
Vex.Flow.setMusicFont(randomFont).then(() => {
const { Stave, CanvasContext, BarlineType, StaveNote, Formatter } = Vex.Flow;

const offscreenCanvas = e.data.canvas;
const offscreenCtx = offscreenCanvas.getContext('2d');
const ctx = new CanvasContext(offscreenCtx);
ctx.scale(3, 3);

// Render to the OffscreenCavans.
const stave = new Stave(15, 0, 300);
stave.setEndBarType(BarlineType.END);
stave.addClef('treble').setContext(ctx).draw();

function makeRandomNote(duration = '4') {
const notes = ['c/4', 'd/4', 'e/4', 'f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'd/5', 'e/5'];
const randomNote = notes[Math.floor(Math.random() * notes.length)];
return new StaveNote({ keys: [randomNote], duration: duration, stem_direction: randomNote[2] === '5' ? -1 : +1 });
}

const notes = [makeRandomNote(), makeRandomNote('8'), makeRandomNote(), makeRandomNote('8'), makeRandomNote()];
Formatter.FormatAndDraw(ctx, stave, notes);

ctx.fillStyle = '#CC0088';
ctx.fillText(randomFont + ' – vexflow-core.js with dynamically loaded fonts.', 5, 15);
});
};
38 changes: 38 additions & 0 deletions demos/worker/worker-vexflow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* eslint-disable */

// Load demos/worker/index.html in a

// Web Workers have an importScripts() method that allows you to load scripts. importScripts(...) is similar to require(...) in Node.js.
importScripts('../../build/cjs/vexflow.js');

onmessage = function (e) {
const fonts = ['Bravura', 'Gonville', 'Petaluma'];
const randomFont = fonts[Math.floor(Math.random() * fonts.length)];
Vex.Flow.setMusicFont(randomFont);

const { Stave, CanvasContext, BarlineType, StaveNote, Formatter } = Vex.Flow;

const offscreenCanvas = e.data.canvas;
const offscreenCtx = offscreenCanvas.getContext('2d');
const ctx = new CanvasContext(offscreenCtx);
ctx.scale(3, 3);

// Render to the OffscreenCavans.
const stave = new Stave(15, 0, 300);
stave.setEndBarType(BarlineType.END);
stave.addClef('treble').setContext(ctx).draw();

function makeRandomNote(duration = '4') {
const notes = ['c/4', 'd/4', 'e/4', 'f/4', 'g/4', 'a/4', 'b/4', 'c/5', 'd/5', 'e/5'];
const randomNote = notes[Math.floor(Math.random() * notes.length)];
return new StaveNote({ keys: [randomNote], duration: duration, stem_direction: randomNote[2] === '5' ? -1 : +1 });
}

const notes = [makeRandomNote('8'), makeRandomNote(), makeRandomNote(), makeRandomNote('8'), makeRandomNote()];
Formatter.FormatAndDraw(ctx, stave, notes);

ctx.fillStyle = '#0098BB';
ctx.fillText(randomFont + ' – vexflow.js bundled with all music fonts.', 5, 15);

postMessage('VexFlow BUILD: ' + Vex.Flow.BUILD);
};
2 changes: 1 addition & 1 deletion docs/api/assets/search.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/api/classes/Flow.html

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions entry/vexflow-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
// It also overrides the `Flow.setMusicFont(...)` function to be async,
// loading music fonts (e.g., Bravura, Petaluma, Gonville) on the fly.

import '../src/publicpath';

import { Vex } from '../src/vex';

import { setupAsyncFontLoader } from '../src/fonts/async';
Expand Down
34 changes: 17 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6523259

Please sign in to comment.