chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# `@perspective-dev/esbuild-plugin`
|
||||
|
||||
Applications bundled with `esbuild` can make use of the
|
||||
`@perspective-dev/esbuild-plugin` module. A full example can be found in the
|
||||
repo under
|
||||
[`examples/esbuild-example`](https://github.com/perspective-dev/perspective/tree/master/examples/esbuild-example).
|
||||
|
||||
```javascript
|
||||
const esbuild = require("esbuild");
|
||||
const { PerspectiveEsbuildPlugin } = require("@perspective-dev/esbuild-plugin");
|
||||
|
||||
esbuild.build({
|
||||
entryPoints: ["src/index.js"],
|
||||
plugins: [PerspectiveEsbuildPlugin()],
|
||||
format: "esm",
|
||||
bundle: true,
|
||||
loader: {
|
||||
".ttf": "file",
|
||||
".wasm": "file",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
When bundling via `esbuild`, you must also:
|
||||
|
||||
- Use the `type="module"` attribute in your app's `<script>` tag, as this build
|
||||
mode is only supported via ES modules.
|
||||
- Use the direct imports for the `esm` versions Perspective, specifically
|
||||
`@perspective-dev/client/dist/esm/perspective.js` and
|
||||
`@perspective-dev/viewer/dist/esm/perspective-viewer.js`
|
||||
@@ -0,0 +1,51 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const get_banner = (deps) => ({
|
||||
js: `
|
||||
define(
|
||||
${JSON.stringify(deps)},
|
||||
function (...args) {
|
||||
const exports = {};
|
||||
const module = {exports};
|
||||
const define = undefined;
|
||||
const require = (x) => args[${JSON.stringify(deps)}.indexOf(x)];
|
||||
`.trimStart(),
|
||||
});
|
||||
|
||||
const get_footer = () => ({
|
||||
js: `
|
||||
return exports;
|
||||
});`,
|
||||
});
|
||||
|
||||
exports.AMDLoader = function AMDLoader(deps) {
|
||||
function setup(build) {
|
||||
const options = build.initialOptions;
|
||||
if (deps === undefined) {
|
||||
console.warn("Setting `deps` to [`@jupyter-widgets/base`]");
|
||||
deps = [`@jupyter-widgets/base`];
|
||||
}
|
||||
|
||||
options.banner = get_banner(deps);
|
||||
options.footer = get_footer();
|
||||
if (options.format !== "cjs" && options.format !== undefined) {
|
||||
console.warn("Setting options to `cjs` for `AMDLoader`");
|
||||
options.format = "cjs";
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: "amd",
|
||||
setup,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const esbuild = require("esbuild");
|
||||
|
||||
const CUTOFF_PERCENT = 0.02;
|
||||
|
||||
const DEFAULT_BUILD = {
|
||||
target: ["es2022"],
|
||||
bundle: true,
|
||||
minify: !process.env.PSP_DEBUG,
|
||||
sourcemap: true,
|
||||
metafile: true,
|
||||
entryNames: "[name]",
|
||||
chunkNames: "[name]",
|
||||
assetNames: "[name]",
|
||||
};
|
||||
|
||||
exports.build = async function build(config) {
|
||||
const result = await esbuild.build({
|
||||
...DEFAULT_BUILD,
|
||||
...config,
|
||||
});
|
||||
|
||||
if (result.metafile) {
|
||||
for (const output of Object.keys(result.metafile.outputs)) {
|
||||
const { inputs, bytes } = result.metafile.outputs[output];
|
||||
for (const input of Object.keys(inputs)) {
|
||||
if (inputs[input].bytesInOutput / bytes < CUTOFF_PERCENT) {
|
||||
delete inputs[input];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const text = await esbuild.analyzeMetafile(result.metafile, {
|
||||
color: true,
|
||||
});
|
||||
|
||||
console.log(text);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
exports.NodeModulesExternal = function NodeModulesExternal(whitelist) {
|
||||
function setup(build) {
|
||||
build.onResolve({ filter: /^[A-Za-z0-9\@]/ }, (args) => {
|
||||
if (!whitelist || !args.path.startsWith(whitelist)) {
|
||||
return {
|
||||
path: args.path,
|
||||
external: true,
|
||||
namespace: "skip-node-modules",
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name: "node_modules_external",
|
||||
setup,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const { WasmPlugin } = require("./wasm.js");
|
||||
const { WorkerPlugin } = require("./worker.js");
|
||||
|
||||
exports.PerspectiveEsbuildPlugin = function PerspectiveEsbuildPlugin(
|
||||
options = {},
|
||||
) {
|
||||
// const wasm_plugin = WasmPlugin(
|
||||
// !!options.wasm?.inline,
|
||||
// !options.wasm?.webpack_hack
|
||||
// );
|
||||
|
||||
// `inline` (default true) keeps the legacy single-bundle behavior
|
||||
// — worker source embedded as a string and wrapped in a Blob URL
|
||||
// at runtime. Pass `worker: { inline: false }` to emit the worker
|
||||
// as a sibling file (preserves source maps in DevTools).
|
||||
const worker_plugin = WorkerPlugin({
|
||||
inline: options.worker?.inline !== false,
|
||||
});
|
||||
|
||||
function setup(build) {
|
||||
// if (options.wasm !== false) {
|
||||
// wasm_plugin.setup(build);
|
||||
// }
|
||||
|
||||
if (options.worker !== false) {
|
||||
worker_plugin.setup(build);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: "@perspective-dev/esbuild-plugin",
|
||||
setup,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@perspective-dev/esbuild-plugin",
|
||||
"version": "4.5.2",
|
||||
"description": "esbuild plugin for Perspective",
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"*.js",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"esbuild": "catalog:"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/perspective-dev/perspective/packages/build"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
exports.ResolvePlugin = function ResolvePlugin(options) {
|
||||
return {
|
||||
name: "resolve",
|
||||
setup: (build) => {
|
||||
for (const moduleName of Object.keys(options)) {
|
||||
const moduleTarget = options[moduleName];
|
||||
const filter = new RegExp("^" + moduleName + "$");
|
||||
|
||||
build.onResolve({ filter }, async (args) => {
|
||||
if (args.resolveDir === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: "resolve",
|
||||
pluginData: {
|
||||
resolveDir: args.resolveDir,
|
||||
moduleName,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad({ filter, namespace: "resolve" }, async (args) => {
|
||||
const importerCode = `
|
||||
export * from '${args.path.replace(
|
||||
args.pluginData.moduleName,
|
||||
moduleTarget
|
||||
)}';
|
||||
export { default } from '${args.path.replace(
|
||||
args.pluginData.moduleName,
|
||||
moduleTarget
|
||||
)}';
|
||||
`;
|
||||
return {
|
||||
contents: importerCode,
|
||||
resolveDir: args.pluginData.resolveDir,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
exports.WasmPlugin = function WasmPlugin(inline, webpack_hack) {
|
||||
function setup(build) {
|
||||
const options = build.initialOptions;
|
||||
options.metafile = true;
|
||||
const KEYSET = [];
|
||||
|
||||
build.onResolve({ filter: /\.wasm$/ }, (args) => {
|
||||
if (
|
||||
args.namespace === "wasm-stub" ||
|
||||
args.namespace === "wasm-inline"
|
||||
) {
|
||||
let entryPoint = args.path;
|
||||
if (args.path.startsWith(".")) {
|
||||
entryPoint = path.join(
|
||||
args.pluginData.resolveDir,
|
||||
entryPoint
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
path: entryPoint,
|
||||
namespace: "wasm",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: inline ? "wasm-inline" : "wasm-stub",
|
||||
pluginData: {
|
||||
resolveDir: args.resolveDir,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad(
|
||||
{ filter: /.*/, namespace: "wasm-inline" },
|
||||
async (args) => ({
|
||||
pluginData: args.pluginData,
|
||||
contents: `
|
||||
import wasm from ${JSON.stringify(args.path)};
|
||||
export default function() {
|
||||
return Promise.resolve(wasm.buffer);
|
||||
};
|
||||
`,
|
||||
})
|
||||
);
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: "wasm-stub" }, async (args) => {
|
||||
const key = `__PSP_INLINE_WASM__${Math.random()
|
||||
.toString()
|
||||
.slice(2)}__`;
|
||||
KEYSET.push(key);
|
||||
const url = webpack_hack ? `${key}(wasm)` : `wasm`;
|
||||
|
||||
return {
|
||||
pluginData: args.pluginData,
|
||||
contents: `
|
||||
import wasm from ${JSON.stringify(args.path)};
|
||||
export default function() {
|
||||
return fetch(new URL(${url}, import.meta.url));
|
||||
};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: "wasm" }, async (args) => {
|
||||
const path = require.resolve(args.path);
|
||||
const contents = await fs.promises.readFile(path);
|
||||
return {
|
||||
pluginData: args.pluginData,
|
||||
contents,
|
||||
loader: inline ? "binary" : "file",
|
||||
};
|
||||
});
|
||||
|
||||
build.onEnd(({ metafile }) => {
|
||||
if (webpack_hack) {
|
||||
for (const file of Object.keys(metafile.outputs)) {
|
||||
if (file.endsWith(".js")) {
|
||||
let contents = fs.readFileSync(file).toString();
|
||||
let updated = false;
|
||||
for (const key of KEYSET) {
|
||||
const symbol = contents.match(
|
||||
new RegExp(`${key}\\(([a-zA-Z0-9_\$]+?)\\)`)
|
||||
);
|
||||
|
||||
if (symbol?.[1]) {
|
||||
updated = true;
|
||||
const escapedSymbol = symbol[1].replace(
|
||||
/\$/g,
|
||||
"\\$"
|
||||
);
|
||||
|
||||
const filename = contents.match(
|
||||
new RegExp(
|
||||
`(?<![a-zA-Z0-9_\$])${escapedSymbol}\\s*?=\\s*?\\"(.+?)\\"`
|
||||
)
|
||||
);
|
||||
|
||||
contents = contents.replace(
|
||||
new RegExp(
|
||||
`${key}\\(([a-zA-Z0-9_\$]+?)\\)`,
|
||||
"g"
|
||||
),
|
||||
`"${filename[1]}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
fs.writeFileSync(file, contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name: "wasm",
|
||||
setup,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,314 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const esbuild = require("esbuild");
|
||||
|
||||
/**
|
||||
* Esbuild plugin that compiles a `.worker.js` import into a
|
||||
* URL-yielding module. The shim `getWorkerURL()` returns a string
|
||||
* that's usable by both `new Worker(url, { type: "module" })` *and*
|
||||
* `await import(url)` — the in-process renderer path uses the latter
|
||||
* so the chart code lives in the worker bundle exactly once.
|
||||
*
|
||||
* Two output modes:
|
||||
* - `inline: true` (default, prod). The subbuild's bytes are
|
||||
* embedded as a JS string in the parent bundle. `getWorkerURL()`
|
||||
* creates a Blob URL at runtime, cached so both consumers share
|
||||
* the same URL (and the same module instance via dynamic-import
|
||||
* dedup).
|
||||
* - `inline: false` (debug). The subbuild's output is written to
|
||||
* the parent's `outdir` next to the main bundle, with source
|
||||
* maps. `getWorkerURL()` resolves `import.meta.url` to that file,
|
||||
* so DevTools can show real source paths and breakpoints work.
|
||||
*/
|
||||
exports.WorkerPlugin = function WorkerPlugin(options = {}) {
|
||||
/**
|
||||
* Optional esbuild plugins to apply to the worker sub-build (e.g.
|
||||
* `GlslMinify`, `LightningCssMinify`). Use when the worker entry
|
||||
* imports the same custom-loader file types as the outer bundle.
|
||||
*/
|
||||
const subbuildPlugins = options.plugins || [];
|
||||
|
||||
/**
|
||||
* Optional `loader` map for the worker sub-build (e.g.
|
||||
* `{ ".glsl": "text", ".css": "text" }`).
|
||||
*/
|
||||
const subbuildLoader = options.loader || {};
|
||||
|
||||
/**
|
||||
* `false` to emit the worker bundle as a real file alongside the
|
||||
* parent bundle (debug builds — preserves source maps + real
|
||||
* paths in DevTools). Defaults to inline-Blob mode for prod.
|
||||
*/
|
||||
const inline = options.inline !== false;
|
||||
|
||||
const additionalOptions = options.additionalOptions || {};
|
||||
|
||||
function setup(build) {
|
||||
build.initialOptions.metafile = true;
|
||||
|
||||
build.onResolve({ filter: /\.worker(\.js)?$/ }, (args) => {
|
||||
if (args.namespace === "worker-stub") {
|
||||
const baseName = path
|
||||
.basename(args.path)
|
||||
.replace(".worker", "");
|
||||
|
||||
const entryPoint = path.join(
|
||||
args.pluginData.resolveDir,
|
||||
args.path,
|
||||
);
|
||||
|
||||
// `outdir` is set so that file-mode subbuilds produce
|
||||
// real, non-`<stdout>` paths in `outputFiles[].path`
|
||||
// — we use those paths to name the on-disk artifacts
|
||||
// when copying to the parent bundle's outdir. In
|
||||
// inline mode the path doesn't matter (we only read
|
||||
// the bytes), but setting `outdir` is harmless.
|
||||
const subbuild = esbuild.build({
|
||||
target: ["es2021"],
|
||||
entryPoints: [entryPoint],
|
||||
define: {
|
||||
global: "self",
|
||||
},
|
||||
outdir: ".",
|
||||
entryNames: "[name]",
|
||||
chunkNames: "[name]",
|
||||
assetNames: "[name]",
|
||||
minify: !process.env.PSP_DEBUG,
|
||||
bundle: true,
|
||||
sourcemap: !inline,
|
||||
write: false,
|
||||
plugins: subbuildPlugins,
|
||||
loader: subbuildLoader,
|
||||
format: "esm",
|
||||
...additionalOptions,
|
||||
});
|
||||
|
||||
return {
|
||||
path: args.path.replace(".worker", ""),
|
||||
namespace: "worker",
|
||||
pluginData: {
|
||||
baseName,
|
||||
subbuild,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: "worker-stub",
|
||||
pluginData: {
|
||||
resolveDir: args.resolveDir,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad(
|
||||
{ filter: /.*/, namespace: "worker-stub" },
|
||||
async (args) => {
|
||||
if (inline) {
|
||||
return {
|
||||
pluginData: args.pluginData,
|
||||
|
||||
// Inline mode: the parent bundle imports the
|
||||
// worker bytes as a text string and constructs
|
||||
// a Blob URL on first call to `getWorkerURL`.
|
||||
// The cached URL is reused for both
|
||||
// `new Worker(url)` and `await import(url)` so
|
||||
// module dedup keeps a single instance.
|
||||
//
|
||||
// `initialize()` adds a Worker-or-shim path:
|
||||
// attempts `new Worker(blobUrl)` first; falls
|
||||
// back to running the worker source text on the
|
||||
// main thread via `new Function(...)` when
|
||||
// Worker construction is unavailable (e.g.
|
||||
// `file://` origins where module-Worker support
|
||||
// is gated, or environments without the Worker
|
||||
// constructor at all). The shim returned by
|
||||
// \`make_host\` is MessagePort-shaped so
|
||||
// downstream consumers can treat it like a real
|
||||
// Worker.
|
||||
contents: `
|
||||
import workerSource from ${JSON.stringify(args.path)};
|
||||
let cached = null;
|
||||
export async function getWorkerURL() {
|
||||
if (cached) return cached;
|
||||
const blob = new Blob([workerSource], {
|
||||
type: "application/javascript",
|
||||
});
|
||||
|
||||
cached = URL.createObjectURL(blob);
|
||||
return cached;
|
||||
}
|
||||
|
||||
function make_host(a, b) {
|
||||
return {
|
||||
addEventListener(type, callback) {
|
||||
if (type === "message") {
|
||||
a.push(callback);
|
||||
}
|
||||
},
|
||||
removeEventListener(type, callback) {
|
||||
const idx = a.indexOf(callback);
|
||||
if (idx > -1) {
|
||||
a.splice(idx, 1);
|
||||
}
|
||||
},
|
||||
postMessage(msg, ports) {
|
||||
for (const listener of b) {
|
||||
listener({
|
||||
data: msg,
|
||||
ports: ports,
|
||||
});
|
||||
}
|
||||
},
|
||||
terminate() {},
|
||||
location: { href: "" },
|
||||
};
|
||||
}
|
||||
|
||||
function run_single_threaded() {
|
||||
console.warn(
|
||||
"Running perspective in single-threaded mode"
|
||||
);
|
||||
const f = Function(
|
||||
"const self = arguments[0];" + workerSource
|
||||
);
|
||||
const workers = [];
|
||||
const mains = [];
|
||||
f(make_host(workers, mains));
|
||||
return make_host(mains, workers);
|
||||
}
|
||||
|
||||
export async function initialize(opts) {
|
||||
const workerOpts = opts || {
|
||||
type: "module",
|
||||
};
|
||||
try {
|
||||
if (
|
||||
typeof window !== "undefined" &&
|
||||
window.location &&
|
||||
window.location.protocol &&
|
||||
window.location.protocol.startsWith(
|
||||
"file"
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
"file:// protocol does not reliably support Web Workers"
|
||||
);
|
||||
return run_single_threaded();
|
||||
}
|
||||
|
||||
const url = await getWorkerURL();
|
||||
const worker = new Worker(url, workerOpts);
|
||||
return worker;
|
||||
} catch (e) {
|
||||
console.error(
|
||||
"Error instantiating worker; falling back to single-threaded mode",
|
||||
e
|
||||
);
|
||||
return run_single_threaded();
|
||||
}
|
||||
}
|
||||
|
||||
export default getWorkerURL;
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
// File mode: the subbuild writes its output to disk
|
||||
// next to the parent bundle, and `getWorkerURL`
|
||||
// resolves the worker file relative to the consuming
|
||||
// module's URL (so `<script type="module" src=...>`
|
||||
// and dynamic imports both work without a baked-in
|
||||
// absolute path). The basename comes from the
|
||||
// namespace import below.
|
||||
return {
|
||||
pluginData: args.pluginData,
|
||||
contents: `
|
||||
import basename from ${JSON.stringify(args.path)};
|
||||
const workerURL = new URL(
|
||||
"./" + basename,
|
||||
import.meta.url,
|
||||
).toString();
|
||||
|
||||
export async function getWorkerURL() {
|
||||
return workerURL;
|
||||
}
|
||||
|
||||
export default getWorkerURL;
|
||||
`,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: "worker" }, async (args) => {
|
||||
const result = await args.pluginData.subbuild;
|
||||
|
||||
const outpath =
|
||||
build.initialOptions.outdir ||
|
||||
path.dirname(build.initialOptions.outfile);
|
||||
|
||||
if (!fs.existsSync(outpath)) {
|
||||
fs.mkdirSync(outpath, { recursive: true });
|
||||
}
|
||||
|
||||
if (inline) {
|
||||
// Embed the bundle bytes as a `text` string in the
|
||||
// parent bundle; the stub above wraps them in a Blob
|
||||
// URL at runtime. The `.js.map` (when present) is
|
||||
// discarded — Blob URLs strip the source-map comment
|
||||
// anyway since DevTools can't follow it back to disk.
|
||||
const jsOut = result.outputFiles.find((o) =>
|
||||
o.path.endsWith(".js"),
|
||||
);
|
||||
if (!jsOut) {
|
||||
throw new Error("worker subbuild produced no .js output");
|
||||
}
|
||||
|
||||
return { contents: jsOut.contents, loader: "text" };
|
||||
}
|
||||
|
||||
// File mode: write the worker bundle (and source map)
|
||||
// alongside the parent bundle. Multiple output files are
|
||||
// emitted by esbuild when `sourcemap: true`. Use the
|
||||
// actual emitted .js basename for the URL so the stub
|
||||
// and the on-disk artifact agree (esbuild-emitted
|
||||
// basename includes `.worker` from the entry filename).
|
||||
let jsBaseName = null;
|
||||
for (const out of result.outputFiles) {
|
||||
const base = path.basename(out.path);
|
||||
const dst = path.join(outpath, base);
|
||||
await fs.promises.writeFile(dst, out.contents);
|
||||
if (base.endsWith(".js")) {
|
||||
jsBaseName = base;
|
||||
}
|
||||
}
|
||||
|
||||
if (!jsBaseName) {
|
||||
throw new Error("worker subbuild produced no .js output");
|
||||
}
|
||||
|
||||
return {
|
||||
contents: `export default ${JSON.stringify(jsBaseName)};`,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name: "webworker",
|
||||
setup,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user