200 lines
6.6 KiB
JavaScript
200 lines
6.6 KiB
JavaScript
import { build } from 'vite';
|
|
import { resolve, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { readFileSync, rmSync, mkdirSync, existsSync, cpSync } from 'fs';
|
|
|
|
const __dirname = resolve(fileURLToPath(import.meta.url), '..');
|
|
|
|
function cssModulePlugin() {
|
|
return {
|
|
name: 'css-module-plugin',
|
|
enforce: 'pre',
|
|
transform(code, id) {
|
|
if (id.endsWith('.js') || id.endsWith('.mjs') || id.endsWith('.ts')) {
|
|
let cleanCode = code.replace(
|
|
/import\s+([\w\s{},*]+)\s+from\s+(['"][\w@\-.\/]+\.css['"])\s*(?:with|assert)\s*\{\s*type\s*:\s*['"]css['"]\s*\};?/gs,
|
|
'import $1 from $2;'
|
|
);
|
|
cleanCode = cleanCode.replace(
|
|
/(import|export)\s+([\w\s{},*]+\s+from\s+)?(['"][\w@\-.\/]+\.css)(['"])/g,
|
|
'$1 $2$3.js$4'
|
|
);
|
|
|
|
// Dynamically inline all SVG files fetched at runtime
|
|
const regex = /const\s+(\w+)\s*=\s*await\s+fetch\(\s*new\s+URL\(\s*(['"])([^'"]+)\2\s*,\s*import\.meta\.url\s*\)\.href\s*\)\.then\(\s*(\w+)\s*=>\s*\4\.text\(\)\s*\);?/g;
|
|
cleanCode = cleanCode.replace(regex, (match, varName, quote, relPath) => {
|
|
const absoluteSvgPath = resolve(dirname(id), relPath);
|
|
if (existsSync(absoluteSvgPath)) {
|
|
const svgContent = readFileSync(absoluteSvgPath, 'utf-8');
|
|
if (varName === 'LogoDead') {
|
|
const dataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgContent)}`;
|
|
return `const ${varName} = ${JSON.stringify(dataUrl)};`;
|
|
} else {
|
|
return `const ${varName} = ${JSON.stringify(svgContent)};`;
|
|
}
|
|
}
|
|
return match;
|
|
});
|
|
|
|
// Patch MapStyleComposer.js layout["text-field"].includes crash for complex style sheets
|
|
if (id.includes('MapStyleComposer.js')) {
|
|
cleanCode = cleanCode.replace(
|
|
/if\s*\(\s*l\.layout\s*\[\s*(['"])text-field\1\s*\]\s*\.includes\s*\(\s*(['"])name:latin\2\s*\)\s*\)/g,
|
|
"if(typeof l.layout['text-field'] === 'string' && l.layout['text-field'].includes('name:latin'))"
|
|
);
|
|
|
|
// Patch getMapStyle() to forward glyphs property to the final style object
|
|
cleanCode = cleanCode.replace(
|
|
/return\s*\{\s*version\s*:\s*8\s*,\s*name\s*:\s*(['"])Panoramax Map Style\1\s*,\s*sources\s*,\s*layers\s*:\s*layers\.concat\(\s*labelLayers\s*\)\.filter\(\s*l\s*=>\s*l\.id\s*\)\s*,\s*metadata\s*,\s*sprite\s*:\s*sprite\.length\s*>\s*0\s*\?\s*sprite\s*:\s*undefined\s*,?\s*\};/g,
|
|
`return {
|
|
version: 8,
|
|
name: "Panoramax Map Style",
|
|
sources,
|
|
layers: layers.concat(labelLayers).filter(l => l.id),
|
|
metadata,
|
|
sprite: sprite.length > 0 ? sprite : undefined,
|
|
glyphs: (this.basemap && this.layerRanges.basemaps[this.basemap] && this.layerRanges.basemaps[this.basemap].glyphs) || "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf",
|
|
};`
|
|
);
|
|
}
|
|
|
|
return {
|
|
code: cleanCode,
|
|
map: null
|
|
};
|
|
}
|
|
},
|
|
async resolveId(source, importer) {
|
|
if (source.endsWith('.css.js')) {
|
|
const rawSource = source.slice(0, -3);
|
|
const resolved = await this.resolve(rawSource, importer);
|
|
if (resolved) {
|
|
return resolved.id + '.js';
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
load(id) {
|
|
if (id.endsWith('.css.js')) {
|
|
const cssFilePath = id.slice(0, -3);
|
|
let cssContent = readFileSync(cssFilePath, 'utf-8');
|
|
|
|
// Rewrite Atkinson font relative paths to local Django static paths
|
|
cssContent = cssContent.replace(
|
|
/url\(['"]?(?:\.\/)?files\/(atkinson-hyperlegible-next-[^'")]+\.(?:woff2|woff))['"]?\)/g,
|
|
'url(/static/panoramax/files/$1)'
|
|
);
|
|
|
|
const escapedCss = JSON.stringify(cssContent);
|
|
return `
|
|
const cssText = ${escapedCss};
|
|
if (typeof document !== 'undefined') {
|
|
const style = document.createElement('style');
|
|
style.appendChild(document.createTextNode(cssText));
|
|
document.head.appendChild(style);
|
|
}
|
|
let sheet = null;
|
|
if (typeof CSSStyleSheet !== 'undefined') {
|
|
sheet = new CSSStyleSheet();
|
|
sheet.replaceSync(cssText);
|
|
}
|
|
export default sheet;
|
|
`;
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
|
|
const configs = [
|
|
// 1. Panoramax Viewer
|
|
{
|
|
configFile: false,
|
|
plugins: [cssModulePlugin()],
|
|
build: {
|
|
target: 'esnext',
|
|
lib: {
|
|
entry: resolve(__dirname, 'index.js'),
|
|
name: 'PanoramaxViewer',
|
|
formats: ['es'],
|
|
fileName: () => 'js/panoramax-viewer.js'
|
|
},
|
|
outDir: resolve(__dirname, '../static/panoramax'),
|
|
emptyOutDir: false
|
|
}
|
|
},
|
|
// 2. PMTiles
|
|
{
|
|
configFile: false,
|
|
build: {
|
|
target: 'esnext',
|
|
lib: {
|
|
entry: resolve(__dirname, 'node_modules/pmtiles/dist/index.js'),
|
|
name: 'pmtiles',
|
|
formats: ['es'],
|
|
fileName: () => 'js/pmtiles.js'
|
|
},
|
|
outDir: resolve(__dirname, '../static/panoramax'),
|
|
emptyOutDir: false
|
|
}
|
|
},
|
|
// 3. GeoJSON-VT
|
|
{
|
|
configFile: false,
|
|
build: {
|
|
target: 'esnext',
|
|
lib: {
|
|
entry: resolve(__dirname, 'node_modules/geojson-vt/geojson-vt.js'),
|
|
name: 'geojsonvt',
|
|
formats: ['es'],
|
|
fileName: () => 'js/geojson-vt.js'
|
|
},
|
|
outDir: resolve(__dirname, '../static/panoramax'),
|
|
emptyOutDir: false
|
|
}
|
|
},
|
|
// 4. PBF
|
|
{
|
|
configFile: false,
|
|
build: {
|
|
target: 'esnext',
|
|
lib: {
|
|
entry: resolve(__dirname, 'node_modules/pbf/index.js'),
|
|
name: 'pbf',
|
|
formats: ['es'],
|
|
fileName: () => 'js/pbf.js'
|
|
},
|
|
outDir: resolve(__dirname, '../static/panoramax'),
|
|
emptyOutDir: false
|
|
}
|
|
}
|
|
];
|
|
|
|
async function run() {
|
|
const outPath = resolve(__dirname, '../static/panoramax');
|
|
if (existsSync(outPath)) {
|
|
rmSync(outPath, { recursive: true, force: true });
|
|
}
|
|
mkdirSync(outPath, { recursive: true });
|
|
|
|
// Copy font files to the static output directory
|
|
const fontsSrc = resolve(__dirname, 'node_modules/@fontsource/atkinson-hyperlegible-next/files');
|
|
const fontsDest = resolve(__dirname, '../static/panoramax/files');
|
|
if (existsSync(fontsSrc)) {
|
|
console.log('Copying Atkinson font files...');
|
|
mkdirSync(fontsDest, { recursive: true });
|
|
cpSync(fontsSrc, fontsDest, { recursive: true });
|
|
}
|
|
|
|
for (const config of configs) {
|
|
const name = config.build.lib.fileName();
|
|
console.log(`\n--- Building: ${name} ---`);
|
|
await build(config);
|
|
}
|
|
console.log('\n--- All builds completed successfully! ---');
|
|
}
|
|
|
|
run().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|