Files
2026-07-13 12:25:07 +08:00

193 lines
8.8 KiB
JavaScript

// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ 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). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { NodeModulesExternal } from "@perspective-dev/esbuild-plugin/external.js";
import { WorkerPlugin } from "@perspective-dev/esbuild-plugin/worker.js";
import { build } from "@perspective-dev/esbuild-plugin/build.js";
import { transform as transformCss } from "lightningcss";
import { execSync } from "node:child_process";
import * as fs from "node:fs/promises";
import { GlslMinify as AstGlslMinify } from "webpack-glsl-minify/build/minify.js";
/**
* Pull every identifier the JS code might resolve by string out of the
* unminified shader source so we can hand them to the AST minifier's
* `nomangle` list. `preserveUniforms: true` already covers `uniform`
* declarations, and the minifier auto-preserves `varying` / `in` /
* `out` names. The one category the minifier won't infer is the
* GLSL ES 1.00 `attribute` declaration in vertex shaders — those are
* the names `getAttribLocation` queries, so we surface them here.
*/
function extractPreservedNames(src) {
const names = new Set();
const attrRe =
/\battribute\s+(?:highp\s+|mediump\s+|lowp\s+)?\S+\s+([a-zA-Z_][\w]*)/g;
let m;
while ((m = attrRe.exec(src))) {
names.add(m[1]);
}
return [...names];
}
// AST-based GLSL minifier. Mangles function locals and non-`main`
// function names; preserves uniforms, attributes, varyings, and `gl_*`
// built-ins (the chart impls resolve those by string via
// `getUniformLocation` / `getAttribLocation`). Saves ~7% of the
// bundled shader payload over the prior regex pass, and parses
// `#`-directives natively so the previous newline-preservation hack
// is no longer needed.
const GlslMinify = () => ({
name: "glsl-minify",
setup(build) {
build.onLoad({ filter: /\.glsl$/ }, async (args) => {
const src = await fs.readFile(args.path, "utf8");
if (process.env.PSP_DEBUG) {
return { contents: src, loader: "text" };
}
const minifier = new AstGlslMinify(
{
preserveDefines: true,
preserveUniforms: true,
preserveVariables: false,
nomangle: extractPreservedNames(src),
output: "source",
esModule: false,
stripVersion: false,
},
undefined,
undefined,
);
const { sourceCode } = await minifier.execute(src);
return { contents: sourceCode, loader: "text" };
});
},
});
// CSS is imported via `import style from "...css"` + the `.css: text`
// loader, so the final bundle embeds the source verbatim as a JS
// string literal — esbuild's own minifier doesn't touch string
// contents. Route `.css` loads through lightningcss so the embedded
// CSS is minified (whitespace collapse, selector shortening, value
// normalisation).
//
// Skipped in `PSP_DEBUG` builds to keep source maps useful.
const LightningCssMinify = () => ({
name: "lightningcss-minify",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const src = await fs.readFile(args.path);
if (process.env.PSP_DEBUG) {
return { contents: src.toString("utf8"), loader: "text" };
}
const { code } = transformCss({
filename: args.path,
code: src,
minify: true,
});
return { contents: code.toString("utf8"), loader: "text" };
});
},
});
const BUILD = [
{
entryPoints: ["src/ts/index.ts"],
define: {
global: "window",
},
plugins: [
NodeModulesExternal(),
WorkerPlugin({
inline: !process.env.PSP_DEBUG,
plugins: [GlslMinify(), LightningCssMinify()],
loader: {
".css": "text",
".glsl": "text",
},
additionalOptions: {
minifyWhitespace: !process.env.PSP_DEBUG,
minifyIdentifiers: !process.env.PSP_DEBUG,
mangleProps: process.env.PSP_DEBUG
? undefined
: /^[_#]|^(plotRect|paddedX(?:Min|Max)|paddedY(?:Min|Max)|dataToPixel|tickColor|labelColor|axisLineColor|gridlineColor|legendText|legendBorder|tooltipBg|tooltipText|tooltipBorder|areaOpacity|heatmapGapPx|sunburstGapPx|gradientStops|seriesPalette|bufferPool)$/,
reserveProps: /(handle_response|__unsafe_open_view)/,
},
}),
GlslMinify(),
LightningCssMinify(),
],
// minifyWhitespace: !process.env.PSP_DEBUG,
// minifyIdentifiers: !process.env.PSP_DEBUG,
// mangleProps: process.env.PSP_DEBUG ? false : /^[_#]/,
format: "esm",
loader: {
".css": "text",
".glsl": "text",
},
outfile: "dist/esm/perspective-viewer-charts.js",
},
{
entryPoints: ["src/ts/index.ts"],
define: {
global: "window",
},
// minifyWhitespace: !process.env.PSP_DEBUG,
// minifyIdentifiers: !process.env.PSP_DEBUG,
// mangleProps: process.env.PSP_DEBUG ? false : /^[_#]/,
plugins: [
WorkerPlugin({
// Inline (Blob URL) for prod, file mode for debug —
// file mode preserves source maps + real paths in
// DevTools so worker breakpoints work.
inline: !process.env.PSP_DEBUG,
plugins: [GlslMinify(), LightningCssMinify()],
loader: {
".css": "text",
".glsl": "text",
},
additionalOptions: {
minifyWhitespace: !process.env.PSP_DEBUG,
minifyIdentifiers: !process.env.PSP_DEBUG,
mangleProps: process.env.PSP_DEBUG
? undefined
: /^[_#]|^(plotRect|paddedX(?:Min|Max)|paddedY(?:Min|Max)|dataToPixel|tickColor|labelColor|axisLineColor|gridlineColor|legendText|legendBorder|tooltipBg|tooltipText|tooltipBorder|areaOpacity|heatmapGapPx|sunburstGapPx|gradientStops|seriesPalette|bufferPool)$/,
reserveProps: /(handle_response|__unsafe_open_view)/,
},
}),
GlslMinify(),
LightningCssMinify(),
],
// minifyWhitespace: !process.env.PSP_DEBUG,
// minifyIdentifiers: !process.env.PSP_DEBUG,
// mangleProps: process.env.PSP_DEBUG ? false : /^[_#]/,
format: "esm",
loader: {
".css": "text",
".glsl": "text",
},
outfile: "dist/cdn/perspective-viewer-charts.js",
},
];
async function build_all() {
await Promise.all(BUILD.map(build)).catch(() => process.exit(1));
try {
execSync("tsc", { stdio: "inherit" });
} catch (error) {
console.error(error);
process.exit(1);
}
}
build_all();