mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(data): keep node:fs out of the browser-built data modules (#83)
naturalEarthRegions.js and neighborhoodPolygons.js each carried an isNode branch that dynamically imported node:fs to read their bundled JSON packs, because a plain dynamic JSON import needs an import attribute in Node. Vite only warns about externalizing node:fs for the browser, so both warnings survived every production build and the runtime boundary rested on an environment guard. Give both loaders the import attribute instead. Vite bundles the JSON as a module exactly as before (the emitted regions/marine/san-francisco chunks are byte-identical), Node loads the same files under node:test, and the isNode split disappears. Add a source-boundary test so a node: import cannot silently return to a browser-built module. Closes #34
This commit is contained in:
1 parent
ac927de71a
commit
6d83bb6008
4 files changed
+49
-27
No files matched your search
@@ -43,6 +43,9 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
|
|||||||
outages and wait for measured photoreal-surface evidence before a 3D model
|
outages and wait for measured photoreal-surface evidence before a 3D model
|
||||||
takes over from its billboard.
|
takes over from its billboard.
|
||||||
- Cockpit altitude uses aviation MSL data rather than Cesium render height.
|
- Cockpit altitude uses aviation MSL data rather than Cesium render height.
|
||||||
|
- The bundled Natural Earth region and neighborhood-polygon packs load through a
|
||||||
|
single import path in both the browser and `node:test`, so the production
|
||||||
|
build no longer externalizes `node:fs` for two browser data modules.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFileSync, readdirSync } from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const SRC_ROOT = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|
||||||
|
/** Every browser-built module under src/ — the test files are Node-only. */
|
||||||
|
function browserModules(directory = SRC_ROOT) {
|
||||||
|
const files = [];
|
||||||
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
||||||
|
const absolute = path.join(directory, entry.name);
|
||||||
|
if (entry.isDirectory()) files.push(...browserModules(absolute));
|
||||||
|
else if (entry.isFile() && entry.name.endsWith('.js')) files.push(absolute);
|
||||||
|
}
|
||||||
|
return files.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('no browser-built module imports a Node core module', () => {
|
||||||
|
// Vite externalizes `node:*` for the browser and only WARNS, so a stray
|
||||||
|
// import survives the build and turns into a runtime failure the moment the
|
||||||
|
// guard around it is wrong. src/data/naturalEarthRegions.js and
|
||||||
|
// src/data/neighborhoodPolygons.js both carried one to read their bundled
|
||||||
|
// JSON packs under node:test; an import attribute serves both runtimes.
|
||||||
|
const offenders = [];
|
||||||
|
for (const file of browserModules()) {
|
||||||
|
const source = readFileSync(file, 'utf8');
|
||||||
|
// Static `from 'node:fs'` and dynamic `import('node:fs')`, quoted either way.
|
||||||
|
if (/\bfrom\s*['"]node:|\bimport\s*\(\s*(?:\/\*[^*]*\*\/\s*)?['"]node:/.test(source)) {
|
||||||
|
offenders.push(path.relative(SRC_ROOT, file).split(path.sep).join('/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(offenders, [], `Node core imports reached the browser build: ${offenders.join(', ')}`);
|
||||||
|
});
|
||||||
@@ -113,19 +113,13 @@ function suffixVariants(norm) {
|
|||||||
/** @type {Array|null} flat entry list for listRegions() */
|
/** @type {Array|null} flat entry list for listRegions() */
|
||||||
let _entries = null;
|
let _entries = null;
|
||||||
|
|
||||||
const isNode = typeof process !== 'undefined' && !!process.versions?.node
|
|
||||||
&& typeof window === 'undefined';
|
|
||||||
|
|
||||||
async function loadPackFile(base) {
|
async function loadPackFile(base) {
|
||||||
if (isNode) {
|
// Vite bundles these JSON files as modules; the import attribute is what Node
|
||||||
const { readFileSync } = await import(/* @vite-ignore */ 'node:fs');
|
// needs to load the same files under node:test (same pattern as
|
||||||
const url = new URL(`./local_data/natural_earth/${base}.json`, import.meta.url);
|
// neighborhoodPolygons.js). One path, so no node: import reaches the browser.
|
||||||
return JSON.parse(readFileSync(url, 'utf8'));
|
|
||||||
}
|
|
||||||
// Vite bundles these JSON files as modules (same pattern as neighborhoodPolygons.js)
|
|
||||||
const mod = base === 'regions'
|
const mod = base === 'regions'
|
||||||
? await import('./local_data/natural_earth/regions.json')
|
? await import('./local_data/natural_earth/regions.json', { with: { type: 'json' } })
|
||||||
: await import('./local_data/natural_earth/marine.json');
|
: await import('./local_data/natural_earth/marine.json', { with: { type: 'json' } });
|
||||||
return mod.default || mod;
|
return mod.default || mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,9 @@ import { createRetryableLoader } from './retryableLoad.js';
|
|||||||
|
|
||||||
// bbox = [west, south, east, north]; only load a city file when the point falls in its box.
|
// bbox = [west, south, east, north]; only load a city file when the point falls in its box.
|
||||||
const CITY_FILES = [
|
const CITY_FILES = [
|
||||||
{ id: 'san-francisco', bbox: [-122.55, 37.70, -122.35, 37.84], loader: () => import('./local_data/neighborhoods/san-francisco.json') },
|
{ id: 'san-francisco', bbox: [-122.55, 37.70, -122.35, 37.84], loader: () => import('./local_data/neighborhoods/san-francisco.json', { with: { type: 'json' } }) },
|
||||||
];
|
];
|
||||||
|
|
||||||
const isNode = typeof process !== 'undefined' && !!process.versions?.node
|
|
||||||
&& typeof window === 'undefined';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* city id → memoized loader. Failures are NOT memoized: a transient
|
* city id → memoized loader. Failures are NOT memoized: a transient
|
||||||
* chunk-load error used to cache an empty array forever, silently demoting
|
* chunk-load error used to cache an empty array forever, silently demoting
|
||||||
@@ -94,18 +91,10 @@ function cityLoader(city) {
|
|||||||
let loader = _cityLoaders.get(city.id);
|
let loader = _cityLoaders.get(city.id);
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
loader = createRetryableLoader(async () => {
|
loader = createRetryableLoader(async () => {
|
||||||
let fc;
|
// One path for both runtimes: Vite bundles the JSON as a module, and the
|
||||||
if (isNode) {
|
// import attribute is what Node needs to load the same file under node:test.
|
||||||
// node:test path — plain dynamic JSON import needs an import attribute in Node,
|
const mod = await city.loader();
|
||||||
// so read the file directly (same dual-path pattern as naturalEarthRegions.js).
|
const fc = mod.default || mod;
|
||||||
const { readFileSync } = await import(/* @vite-ignore */ 'node:fs');
|
|
||||||
const url = new URL(`./local_data/neighborhoods/${city.id}.json`, import.meta.url);
|
|
||||||
fc = JSON.parse(readFileSync(url, 'utf8'));
|
|
||||||
} else {
|
|
||||||
// Vite bundles the JSON file as a module.
|
|
||||||
const mod = await city.loader();
|
|
||||||
fc = mod.default || mod;
|
|
||||||
}
|
|
||||||
return Array.isArray(fc.features) ? fc.features : [];
|
return Array.isArray(fc.features) ? fc.features : [];
|
||||||
});
|
});
|
||||||
_cityLoaders.set(city.id, loader);
|
_cityLoaders.set(city.id, loader);
|
||||||
|
|||||||
Reference in new issue
Block a user