- Install deps:
yarn install - Check types:
yarn build - Check website build
cd website; yarn; yarn build
- Run Node tests:
yarn test-node - Run headless browser tests:
yarn test-headless - Format code:
yarn lint fix - Always run
yarn lint fixafter making changes to ensure that Biome formatting is maintained. - Use the exact script names from
package.json; do not substitute spaced forms such asyarn test nodeoryarn test headless.
- Chromium is the canonical runtime for browser-capable behavior. Plain
*.spec.tsfiles run in Chromium; use*.browser.spec.tswhen the test specifically requires browser APIs,*.cross.spec.tsonly for the small compatibility set that also runs in Node, and*.node.spec.tsonly for genuine Node-only APIs or behavior. - Keep required pull-request tests fast and hermetic. Put expensive exhaustive matrices, large-fixture parses, and long fuzz corpora in
*.slow.spec.ts(or*.node.slow.spec.ts), and put live-service checks in*.external.spec.ts. Fast and slow-hermetic projects must not access the public network. - Prefer high-signal behavior and boundary coverage over test count. Test an implementation exhaustively in its owning module, then keep only focused public-entrypoint or integration conformance in wrapper modules; do not duplicate the same fixture, call, and assertions across packages.
- Build remote loaders.gl fixture URLs from
@loaders.gl/core/constants. ItsLOADERS_GL_FIXTURE_BRANCHnames the branch the tests live on (master, or e.g.4.4-releaseon a release branch); change it when cutting a release branch.yarn test-auditfails when a test hard-codes another branch, or when CI targets a branch other thanLOADERS_GL_FIXTURE_BRANCH. - Use the smallest deterministic fixture that exercises the behavior. Fixtures over 1 MiB require an audited exception in the fast suite. Keep one representative large end-to-end parse in the slow lane when scale itself is meaningful.
- Parse immutable fixture data once with
beforeAllwhen multiple assertions inspect the same result. Tests that mutate a result must clone it or parse independently so state cannot leak between cases. - Write every new test case, and every substantially revised test, with native Vitest syntax (
test,describe,test.each, lifecycle hooks, mocks, andexpect). Do not use Tape or its retired compatibility shim; the test audit rejects these imports. - Coverage is enforced per non-private published
modules/*package across statements, lines, functions, and branches. Never lower a committed threshold or add an exclusion merely to make coverage pass; generated, vendored, declaration, or unreachable platform-specific exclusions require an adjacent reason. - Run
yarn test-auditafter changing test structure or fixtures. Useyarn test-profile <mode>when adding costly coverage, and move any fast test file that remains above the documented performance budget into smaller fast cases plus an explicit slow case.
- More workers are not automatically faster. Four Chromium workers made this suite slower than one because parsing-heavy files contended for CPU and memory. Benchmark a worker-count change with at least three equivalent runs before changing the default.
- Do not put every package import into one sequential compatibility test. A single all-package import smoke became a 75-second bottleneck and also tried to import browser-only
@loaders.gl/videoin Node. Keep Node compatibility checks lightweight and let Chromium exercise real browser package behavior. - Vitest setup files run once per isolated test file. A broad setup import is multiplied across the suite, so keep project setup minimal and import specialized polyfills or helpers only in the projects or tests that need them.
- When consolidating case modules behind one aggregator, exclude the original
*.spec.*files from direct discovery; otherwise every case runs twice. The aggregator must preserve named registrations so failures still identify the original case. - Moving or deleting a spec can leave a stale side-effect import in a legacy
test/index.ts. Check package test entrypoints as well as Vitest discovery; a missing import may appear only when another package imports that test subpath. yarn buildcleans packagedistdirectories, including previously built worker bundles. Runyarn build-workersafter the final build and before browser tests; otherwise worker URLs return Vite's HTML fallback and fail withUnexpected token '<'.- A remote URL may be stored in a constant and evade a simple static
fetch('https://…')scan. Keep the runtime outbound-network blocker enabled for hermetic projects in addition to the source audit. - Splitting tests into shards does not reduce critical-path time if shards run sequentially in one CI job. Put shards in separate matrix jobs, give blob artifacts unique names, and merge all reports before evaluating test counts or coverage.
- Coverage from a browser shard, the reduced Node suite, or a moved slow case is incomplete on its own. Enforce thresholds only after merging all browser shards, Node coverage, and coverage from the hermetic slow tests selected for the change. Keep a browser-only ratchet so Node or slow coverage cannot hide a browser regression.
- Vitest 4 removed
coverage.all; explicitcoverage.includeis what keeps unexecuted production files in the denominator. Do not reintroduce obsolete settings when tuning coverage. - File size is only a proxy for cost. For example, a moderately sized LAZ fixture can expand to hundreds of thousands of points and dominate runtime. Use profile results and decoded record counts in addition to bytes when deciding whether a case belongs in the slow lane.
- Runtime
if (isBrowser)returns still pay file discovery, setup, and import costs. Prefer project/file naming (.node,.cross,.slow,.external) so the wrong runtime never collects the file.
- If
ghauthentication fails, check whether the authenticated GitHub plugin is available before treating GitHub access as blocked. - When opening a pull request, verify that its description renders as properly formatted Markdown with real newlines, headings, and lists; do not submit a description with escaped or flattened newlines.
- After opening a PR, or when asked to address reviews or babysit it, own the work until the latest revision is ready for merge. Continue after pushing fixes; local success alone does not finish the task.
- Wait 15 minutes after opening a PR for review comments, as in luma.gl. Use that time to inspect CI and coverage, and address comments as they arrive.
- Close the loop on every actionable review thread: implement the fix, add focused regression coverage when appropriate, run the required build/tests/formatting after the final changes, reply with what changed and how it was verified, then resolve the thread. Recheck for newly posted comments after each push.
- Keep the branch current with
master, resolve merge conflicts promptly, and recheck mergeability after every push. - Inspect all required CI checks and coverage on the latest head commit. Investigate failures; rerun transient jobs when the evidence supports it. Never lower coverage thresholds or add exclusions merely to make the PR pass.
- Treat CI as the final gate after review changes. Older successful runs and focused local checks do not replace green required checks on the current revision. Do not declare readiness while checks are failing or pending.
- Before finishing, recheck review threads, current
master, mergeability, and the latest checks. A ready PR has no outstanding actionable review threads, passes every required check and coverage gate, and has an accurate Markdown description. - Report the PR link, review fixes, verification, and any remaining blockers. Do not merge unless the user asks to merge.
- Add or update TSDoc for every new class, function, method, and field.
- Update docs when behavior, public API, examples, or migration guidance changed.
- Make sure
yarn.lockis up to date by runningyarnin the repo root. - Run build:
yarn build - Run Node tests:
yarn test-node - Run headless browser tests:
yarn test-headless - Format code:
yarn lint fix - Provide a copyable markdown PR description, compared to
master, that starts with goals of the PR and then lists actual changes.
- TypeScript strict mode
- Single quotes, no semicolons
- Never abbreviate variables, always type out the full name in camelCase (variables, functions, fields), PascalCase (types), CAPITAL_CASE (constant)
- Add TSDoc to all new classes, functions, methods, fields.
- Prefer verbNoun structure for function and method names.
- Prefer
readonlyfields over getters for fixed or constructor-derived values. - We end JavaScript statements with semicolons. Do not remove semicolons.
-
Keep
NPM_TAGinmodules/core/src/constants/index.tsset tobetaonmasterandlateston release branches. UpdateLOADERS_GL_FIXTURE_BRANCHwhen cutting a release branch. -
Keep
@loaders.gl/core/polyfillsindependent: its implementation must not import the core root, other core subpath entry points, or other@loaders.gl/*modules. Put the small Node-specific helpers it needs inside the polyfills subpath instead of creating package or entry-point cycles. -
Published loaders.gl modules may import independent
@loaders.gl/core/*subpaths when they need those APIs. Declare@loaders.gl/coreas a peer dependency, not an exact runtime dependency, and never list a subpath as a package dependency. Keep the core root entry point independent of optional subpath dependencies. -
Avoid importing node specific files unless in specific functions that are intentionally designed to handle both cases. For instance,
fetchFilehandles both browser and Node.js, so importingfsis almost never required. -
In
package.jsonbrowserfields, use relative replacements that match actual emitted files in the current layout: pair./src/...*.tsentries with the corresponding./dist/...*.jsentries, and map each one either to a real browser shim file or tofalse. Do not leave stale paths such as olddist/esmtargets or entries for files that do not exist in that module. -
Keep the upgrade guide focused on deleted or deprecated functionality. New feature documentation belongs in the module docs and release notes instead.
-
Running module- or file-scoped commands such as
tsctends to generate.jsand.d.tsfiles in this repo. After such commands, check for generated files and remove them before finishing. -
Running tests that use workers requires
yarn buildfirst so the worker bundles exist before the tests run. -
Loader-required geometry construction belongs in focused
@loaders.gl/core/arrow-geometryentry points, with metadata types/helpers in@loaders.gl/core/schema. The GIS compatibility package has been removed; do not restore imports or implementations under that package. -
@loaders.gl/core/geoarrowis an opt-in utility subpath providing the richer converter and processing APIs for geospatial tables, especially GeoArrow-formatted Arrow tables. -
Loaders should not import the larger
@loaders.gl/core/geoarrowmodule; applications can install and use it when they need more extensive geospatial processing.
- Use
modules/tables/src/csvas the reference shape for loader modules that want a lightweight root import and an implementation subpath. - The package root
src/index.tsshould export metadata-only loaders, deprecated*WorkerLoaderaliases if needed for compatibility, writers, and shared public types/options. It should not export*WithParserloaders. - Each public metadata loader should live in its own
*-loader-types.tsfile. That file should contain the loader metadata, options, default values, and an optionalpreload()function, but noparse,parseSync,parseText, orparseInBatchesmethods. preload()should use a static package-subpath import such asawait import('@loaders.gl/tables/csv-loader'), and return the parser-bearing loader export. Prefer static package imports over relative paths or computed import specifiers.- Each parser-bearing loader should live in its own
*-loader.tsfile and export only the*WithParserloader. Keep parser logic out of the metadata file. - Move shared constants, option defaults, and types needed by both metadata and parser files into neutral shared files to avoid cycles.
- Keep writer entry points unchanged unless the package is intentionally splitting them as well.
- In
package.json, keep explicit subpath exports for parser entry points such as./csv-loaderand./csv-arrow-loader. Root exports stay metadata-only; direct parser access happens through the subpath. - Core async APIs may accept metadata loaders and upgrade them through
preload(). Sync parsing paths should continue to require a parser-bearing loader.
-
Core utility subpaths may contain format-neutral scan, geometry construction, conversion, and codec helpers. Actual format loader/writer descriptors and format-specific source adapters belong in their format modules.
-
Keep scan, arrow-geometry, geoarrow, and wkt out of the core root exports. Core implementations must not import other loaders.gl packages; database backends register against core scan APIs from the SQL package.
-
The standalone scan, arrow-geometry, and geoarrow packages are removed. Do not restore forwarding packages or compatibility dependency edges.
-
LAS/LAZ, PCD and PLY implementations belong in
@loaders.gl/pointcloudsformat subpaths; PLY retains both point-cloud and mesh/face support. COPC and Potree remain in tiles-3d. -
Scene owns OBJ/MTL, glTF/GLB and USD through
/obj,/gltfand/usd; geospatial owns WKT/WKB/TWKB loaders and writers. Do not restore their removed standalone packages. -
Pointclouds, scene and Draco use Arrow transport directly from core/schema-utils/arrow-transport, without tables forwarding imports or dependencies.
-
Texture and video implementations belong in
@loaders.gl/images. Keep its root loaders metadata-only and expose parsers, texture writers, schemas, and helpers through direct subpaths. Do not restore standalone texture/video compatibility packages. -
Basic XML, HTML, YAML, TOML, BSON, and Markdown loaders belong in
@loaders.gl/documents. Keep its root metadata-only and parser imports flat (/xml-loader,/bson-loader,/markdown-loader); BSON codecs and writers use/bson-codecand/bson-writer. Do not restore XML/config packages or tables BSON forwarding exports. Parquet uses the documents BSON codec.