Summary
When building a TypeScript library that depends on external packages like the ArcGIS JS API, types may appear as any in consuming projects after build and publish. This usually happens because the ArcGIS type definitions are either not properly bundled, are marked as external, or are not re-exported in a way that preserves their type information. The core issue is that TypeScript library builds often separate declaration files (.d.ts) from implementation, and if dependencies aren’t correctly referenced or bundled, the consuming project loses type context.
Root Cause
The root cause typically lies in one of the following configuration errors in the build toolchain (e.g., tsup, rollup, or tsc):
- Missing Type Bundling: The build process compiles
.tsto.jsbut fails to generate or bundle corresponding.d.tsfiles that reference the ArcGIS types. - External Dependencies: The
externalconfiguration in the bundler excludes the ArcGIS package (e.g.,@arcgis/core) from the output. While this prevents code duplication, it leaves the generated declaration files with unresolved references if not handled correctly. - Improper Export: The library exports interfaces that extend or use ArcGIS types, but the ArcGIS types themselves are not re-exported or installed as a
peerDependency, causing the consuming project to lack the necessary definitions. - Declaration Map Misconfiguration: Missing
declarationMapintsconfig.jsonprevents IDEs from navigating back to the source types, though this usually affects development, not usage.
Why This Happens in Real Systems
- Complex Dependency Chains: ArcGIS JS API has a complex type structure (
__esri.*) that isn’t always tree-shakeable or easily bundled by standard TS compilers. - Bundler Limitations: Standard
tscoutput keeps dependencies asimportstatements. If the consuming project doesn’t have the exact same type definitions installed, the types resolve toanyorunknown. - Peer Dependencies vs. Direct Dependencies: Library authors often bundle dependencies to save space, but type safety requires the consuming app to have access to the original type definitions.
Real-World Impact
- Loss of IntelliSense: Developers using the library lose autocomplete and method signatures for ArcGIS objects, significantly slowing down development.
- Type Safety Violation: The compiler stops catching errors related to MapView or SceneView properties, leading to runtime errors (e.g., accessing a property on
undefined). - Increased Debugging Time: Consumers must manually look up ArcGIS documentation instead of relying on their IDE, increasing the likelihood of implementation errors.
Example or Code
To fix this, we must ensure the build outputs declaration files that preserve the imports from ArcGIS, and we must declare ArcGIS as a peerDependency.
1. Library tsup.config.ts (or rollup.config.js):
Ensure d.ts is generated and external packages are handled correctly.
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true, // Crucial: Generates .d.ts files
external: ['@arcgis/core', '@mui/material'], // Keep these external
clean: true,
});
2. Library package.json:
Define peer dependencies so the consumer knows they need the types.
{
"name": "my-maps-library",
"version": "1.0.0",
"peerDependencies": {
"@arcgis/core": "^4.30.0",
"@mui/material": "^5.0.0"
},
"devDependencies": {
"@arcgis/core": "^4.30.0",
"@mui/material": "^5.0.0"
}
}
3. Library src/index.ts:
Explicitly export the types you use so consumers can reference them.
// Re-exporting types ensures they are available in the public API
export type { MapView, SceneView, ExpandProperties } from '@arcgis/core/views';
// Your custom interface
export interface MyCustomWidgetProps {
view: __esri.MapView; // Reference to ArcGIS type
visible: boolean;
}
How Senior Engineers Fix It
- Audit the Build Output: Senior engineers inspect the generated
distfolder to verify that.d.tsfiles contain actual type references (e.g.,import { MapView } from '@arcgis/core') rather thanany. - Enforce Peer Dependencies: They modify
package.jsonto move ArcGIS types topeerDependencies. This forces the consuming application to install the types, ensuring global availability. - Configure Type Resolution: If using a bundler like Rollup or Vite, they ensure
preserveModulesor specific DTS plugins are used to correctly map the ArcGIS namespace. - Use Reference Types: In rare cases where bundling fails, they might add
/// <reference types="@arcgis/core" />to the entry point, though modern package management usually solves this.
Why Juniors Miss It
- “It Works on My Machine”: Junior developers often test the library within the same monorepo or workspace where ArcGIS is already installed as a regular dependency, masking the missing peer dependency issue.
- Bundling Blindness: They trust the build tool (e.g., “TypeScript compiled successfully”) without actually checking the generated declaration files in the output directory.
- Misunderstanding
any: They may not immediately recognize thatanyin the consuming project stems from a missing type definition rather than a logic error in their code. - Ignoring
peerDependencies: They often overlook the concept of peer dependencies, which are critical for shared libraries to avoid version conflicts and ensure type availability.