build: reach Navara only through import() and ship its worker wasm

The import-direction check now tells dynamic imports apart
(analyzeModule(source, { dynamic: true })) and rejects any static import
or re-export of src/globe/navara/** from outside that directory, so the
Navara engine can only be a lazily loaded chunk of the app.

Navara's prebuilt workers load their wasm by file name next to
themselves, which the bundler cannot see; a build-only Rollup plugin
copies the missing ones (draco decoder, font worker) into assets/ when
the bundle includes Navara, and adds nothing to a build without it.
This commit is contained in:
German Khokhlov committed 2026-10-15 20:56:06 +04:00
1 parent 72fec292d4
commit 306e3e9467
8 files changed
+179 -7

No files matched your search

+15 -1
View File
@@ -97,7 +97,9 @@ export function checkImportDirections(root) {
}
let analysis;
try {
analysis = analyzeModule(readFileSync(resolved, 'utf8'));
analysis = analyzeModule(readFileSync(resolved, 'utf8'), {
dynamic: true,
});
} catch (error) {
report(file, error.message);
return null;
@@ -141,6 +143,18 @@ export function checkImportDirections(root) {
continue;
}
record.edges.push(to);
// Navara stays out of the Cesium-mode bundle: outside its own directory
// it is reached only through import() (src/app/scene.js), which the
// bundler splits into a chunk loaded on ?engine=navara.
if (
to.startsWith('src/globe/navara/') &&
!file.startsWith('src/globe/navara/') &&
!analysis.dynamicImports.includes(specifier)
)
report(
file,
`The Navara engine loads only through a dynamic import(): ${to}`,
);
if (file.startsWith('src/') && to.startsWith('server/'))
report(file, `Browser imports server: ${to}`);
if (
+13 -3
View File
@@ -15,9 +15,13 @@ const browserGlobals = new Set([
'getComputedStyle',
]);
/** Inspect literal module dependencies and browser-platform references without executing code. */
export function analyzeModule(source) {
/**
* Inspect literal module dependencies and browser-platform references without
* executing code. With `dynamic`, also list the import() specifiers apart.
*/
export function analyzeModule(source, { dynamic = false } = {}) {
const imports = [];
const dynamicImports = [];
const browser = new Set();
function walk(node, parent, field) {
if (!node || typeof node !== 'object') return;
@@ -35,6 +39,8 @@ export function analyzeModule(source) {
'Computed module imports are not allowed in runtime code',
);
imports.push(node.source.value);
if (node.type === 'ImportExpression')
dynamicImports.push(node.source.value);
}
if (node.type === 'CallExpression' && node.callee?.name === 'require')
throw new Error('Runtime modules must use ES imports');
@@ -71,7 +77,11 @@ export function analyzeModule(source) {
}
}
walk(parsers.babel.parse(source));
return { imports, browser: [...browser] };
return {
imports,
browser: [...browser],
...(dynamic && { dynamicImports }),
};
}
/** Return static imports, literal dynamic imports and re-exports. */
+2 -1
View File
@@ -33,7 +33,8 @@
"build/application-html.js",
"build/allowedHosts.js",
"build/embed-framing.js",
"build/panel.js"
"build/panel.js",
"build/navaraAssets.js"
],
"external": ["vite-plugin-cesium"]
},