Skip to content

Commit

Permalink
Feature: Let the qgis-js loader find the relatrive runtime files from…
Browse files Browse the repository at this point in the history
… "assets/wasm" in order to use it from a CDN
  • Loading branch information
boardend committed May 2, 2024
1 parent 9ad5fd7 commit f22c37c
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 13 deletions.
61 changes: 60 additions & 1 deletion docs/bundling.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,62 @@
# Bundling

<!-- FIXME: Write up on how to bundle .wasm assets with Vite and Webpack -->
qgis-js consists of two parts: The runtime generated by Emscripten and the TypeScript/JavaScript API, that can be seen as a wrapper around the runtime. The wrapper can be imported, used and bundled (e.g. tree-shaked) like any other JavaScript library. But it is important that the runtime is not modified and served as is.

See the [`qgis-js` Package `README.md`](../packages/qgis-js/README.md) for more information about the files involved. Everything inside `assets/wasm` is part of the runtime.

To not confuse any downstream bundler, the runtime is [dynamically loaded](../packages/qgis-js/src/loader.ts) in a way that it will not be processed. Therefore **it is up to the end user to include the runtime files in the final build**.

### Explicitly specifying the runtime location

The runtime location can be specified with the `prefix` configuration option. This is useful when the runtime is not in the same directory as the main script or served from a different server (e.g. a CDN).

```js
const { api } = await qgis({
prefix: "/path/to/runtime/assets",
});
```

## Examples

### qgis-js with [Vite](https://vitejs.dev/)

One can use the [vite-plugin-static-copy](https://github.com/sapphi-red/vite-plugin-static-copy).

For an example see the [`vite.config.ts`](./examples/qgis-js-example-ol/vite.config.js) in the [qgis-js-example-ol](./examples/qgis-js-example-ol) project and note that the COOP/COEP headers have to be set after the plugin (see [`compatibility.md`](./compatibility.md) for more information).

### qgis.js with [Webpack](https://webpack.js.org/)

With Webpack one can use the [copy-webpack-plugin](https://www.npmjs.com/package/copy-webpack-plugin).

Note that the COOP/COEP headers have to be set in the `webpack.config.js` (see [`compatibility.md`](./compatibility.md) for more information).

### Using qgis-js from a CDN

An example of how to use qgis-js from a CDN (e.g. [jsDelivr](https://www.jsdelivr.com/)):

```html
<!doctype html>
<html lang="en">
<head>
<title>qgis-js</title>
</head>
<body>
<script type="module">
// import qgis-js from a CDN
const { qgis } = await import(
"https://cdn.jsdelivr.net/npm/qgis-js/dist/qgis.js"
);
// boot the qgis-js runtime
const { api } = await qgis();
// use the qgis-js api
const rect = new api.Rectangle(1, 1, 42, 42);
const center = rect.center();
console.log(`Center: x: ${center.x}, y: ${center.y}`);
</script>
</body>
</html>
```

Note that the main script has to be explicitly loaded with `qgis-js/dist/qgis.js` (Or a prefix pointing to `qgis-js/dist/assets/wasm` has to be set ([see above](#explicitly-specifying-the-runtime-location))).

And also ensure that the HTML document has the correct COOP/COEP headers set (see [`compatibility.md`](./compatibility.md) for more information).
58 changes: 50 additions & 8 deletions packages/qgis-js/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ interface QtRuntimeFactory {
/**
* Loads the QtRuntimeFactory Emscripten module with the given prefix.
*
* @param prefix - The prefix to use for the module path.
* @param mainScriptPath - The import path of the main script
* @returns A promise that resolves with the QtRuntimeFactory module.
*/
function loadModule(prefix: string = "/"): Promise<QtRuntimeFactory> {
function loadModule(mainScriptPath: string): Promise<QtRuntimeFactory> {
return new Promise(async (resolve, reject) => {
try {
const mainScriptPath = prefix + "/" + "qgis-js" + ".js";
// hack to import es module without vite knowing about it
const createQtAppInstance = (
await new Function(`return import("${mainScriptPath}")`)()
Expand All @@ -48,14 +47,57 @@ function loadModule(prefix: string = "/"): Promise<QtRuntimeFactory> {
* @param config The {@link QgisRuntimeConfig} that will be taken into account during loading and initialization.
* @returns A promise that resolves to a {@link QgisRuntime}.
*/
export async function qgis(config: QgisRuntimeConfig): Promise<QgisRuntime> {
return new Promise(async (resolve) => {
const { createQtAppInstance } = await loadModule(config.prefix);
export async function qgis(
config: QgisRuntimeConfig = {},
): Promise<QgisRuntime> {
return new Promise(async (resolve, reject) => {
let prefix: string | undefined = undefined;
if (config.prefix) {
prefix = config.prefix;
} else {
const url = import.meta.url;
if (/.*\/src\/loader\.[ts|js]\?*[^/]*$/.test(url)) {
console.warn(
[
`qgis-js loader is running in development mode and no "prefix" seems to be configured.`,
` - Consider adding the QgisRuntimePlugin when bundling with Vite.`,
` - For more information see: https://github.com/qgis/qgis-js/blob/main/docs/bundling.md`,
].join("\n"),
);
if (typeof window !== "undefined") {
prefix = new URL("assets/wasm", window.location.href).pathname;
}
} else {
prefix = new URL("assets/wasm", import.meta.url).href;
}
}

if (!prefix) {
prefix = "assets/wasm";
} else {
prefix = prefix.replace(/\/$/, ""); // ensure no trailing slash
}

let qtRuntimeFactory: QtRuntimeFactory | undefined = undefined;
try {
const mainScriptPath = `${prefix}/qgis-js.js`;
qtRuntimeFactory = await loadModule(mainScriptPath);
} catch (error) {
reject(
new Error(`Unable to load the qgis-js.js script`, { cause: error }),
);
return;
}

const canvas = document.querySelector("#screen") as HTMLDivElement | null;
const { createQtAppInstance } = qtRuntimeFactory!;

let canvas: HTMLDivElement | undefined = undefined;
if (typeof document !== "undefined") {
canvas = document?.querySelector("#screen") as HTMLDivElement;
}

const runtimePromise = createQtAppInstance({
locateFile: (path: string) => `${config.prefix}/` + path,
locateFile: (path: string) => `${prefix}/` + path,
preRun: [
function (module: any) {
module.qtContainerElements = canvas ? [canvas] : [];
Expand Down
7 changes: 3 additions & 4 deletions sites/dev/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,9 @@ async function initDemo() {
// boot the runtime
if (timer) console.time("boot");
const { api, fs } = await qgis({
prefix: "/qgis-js/assets/wasm",
onStatus: (status: string) => {
onStatus(status);
},
// use assets form QgisRuntimePlugin
prefix: new URL("assets/wasm", window.location.href).pathname,
onStatus: (status: string) => onStatus(status),
});
if (timer) console.timeEnd("boot");

Expand Down

0 comments on commit f22c37c

Please sign in to comment.