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

+38
View File
@@ -241,6 +241,44 @@ for (const [name, files, pattern] of [
});
}
test('module analysis can tell dynamic imports from static ones', () => {
assert.deepEqual(
analyzeModule(
"import a from 'one'; export * from 'two'; import('three'); const x = () => import('four');",
{ dynamic: true },
),
{
imports: ['one', 'two', 'three', 'four'],
browser: [],
dynamicImports: ['three', 'four'],
},
);
});
test('the app reaches the Navara engine only through a dynamic import', (t) => {
const { root, write } = fixture(t);
write('src/globe/navara/index.js', "import './engine.js';");
write('src/globe/navara/engine.js', '');
write(
'src/app/scene.js',
"export const load = () => import('../globe/navara/index.js');",
);
assert.doesNotThrow(() => checkImportDirections(root));
write('src/app/scene.js', "import '../globe/navara/index.js';");
assert.throws(
() => checkImportDirections(root),
/src\/app\/scene\.js: The Navara engine loads only through a dynamic import\(\): src\/globe\/navara\/index\.js/,
);
write(
'src/app/scene.js',
"export { createNavaraGlobe } from '../globe/navara/index.js';",
);
assert.throws(
() => checkImportDirections(root),
/only through a dynamic import/,
);
});
test('geo modules may import each other, and its tests may import cesium', (t) => {
const { root, write } = fixture(t);
write('src/geo/math.js', 'export const half = (value) => value / 2;');
+56
View File
@@ -0,0 +1,56 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { navaraWorkerAssets } from '../../build/navaraAssets.js';
import { createBrowserViteConfig } from '../../build/vite.js';
test('the build copies the wasm files Navara workers load by name next to them', (t) => {
const dir = mkdtempSync(path.join(os.tmpdir(), 'gev-navara-assets-'));
t.after(() => rmSync(dir, { recursive: true, force: true }));
writeFileSync(path.join(dir, 'draco_decoder-AAAA.wasm'), 'draco');
writeFileSync(path.join(dir, 'navara_wasm_worker_bg-BBBB.wasm'), 'worker');
writeFileSync(path.join(dir, 'fontWorker-CCCC.js'), 'not wasm');
const plugin = navaraWorkerAssets({ assetsDir: dir });
const emitted = [];
const bundle = {
// Rollup already emitted this one under the same name.
'assets/navara_wasm_worker_bg-BBBB.wasm': { type: 'asset' },
// A Navara worker chunk shows the build includes Navara.
'assets/index-DDDD.js': { type: 'chunk', code: 'navara_wasm_worker_bg' },
};
plugin.generateBundle.call(
{ emitFile: (file) => emitted.push(file) },
{},
bundle,
);
assert.deepEqual(
emitted.map(({ fileName, type }) => [fileName, type]),
[['assets/draco_decoder-AAAA.wasm', 'asset']],
);
assert.equal(String(emitted[0].source), 'draco');
});
test('a build without Navara gets no Navara wasm', (t) => {
const dir = mkdtempSync(path.join(os.tmpdir(), 'gev-navara-assets-'));
t.after(() => rmSync(dir, { recursive: true, force: true }));
writeFileSync(path.join(dir, 'draco_decoder-AAAA.wasm'), 'draco');
const plugin = navaraWorkerAssets({ assetsDir: dir });
const emitted = [];
plugin.generateBundle.call(
{ emitFile: (file) => emitted.push(file) },
{},
{ 'assets/index-EEEE.js': { type: 'chunk', code: 'cesium only' } },
);
assert.deepEqual(emitted, []);
});
test('the browser build runs the Navara asset copy as a Rollup plugin', () => {
const { build } = createBrowserViteConfig({});
assert.deepEqual(
build.rollupOptions.plugins.map(({ name }) => name),
['navara-worker-assets'],
);
assert.equal(build.chunkSizeWarningLimit, 1500);
});