Temelj packages are distributed as ES modules optimized for modern bundlers. This guide covers configuration for popular build tools and optimization strategies.
Package structure
Each Temelj package follows a consistent structure:
@temelj/result/
├── dist/
│ ├── mod.mjs # ES module entry point
│ └── mod.d.mts # TypeScript declarations
├── package.json # Package metadata
└── README.md
The package.json exports field provides the entry point:
{
"name" : "@temelj/result" ,
"type" : "module" ,
"main" : "./dist/mod.mjs" ,
"types" : "./dist/mod.d.mts" ,
"exports" : {
"." : {
"types" : "./dist/mod.d.mts" ,
"import" : "./dist/mod.mjs"
}
}
}
All Temelj packages use the .mjs extension and "type": "module" to ensure they’re treated as ES modules by all tools.
Vite
Vite works with Temelj packages out of the box with minimal configuration.
Basic configuration
import { defineConfig } from 'vite' ;
import { resolve } from 'path' ;
export default defineConfig ({
build: {
target: 'es2022' ,
minify: 'esbuild' ,
sourcemap: true ,
rollupOptions: {
output: {
manualChunks: {
// Separate Temelj utilities into their own chunk
'temelj-core' : [
'@temelj/result' ,
'@temelj/value' ,
'@temelj/string'
],
'temelj-async' : [ '@temelj/async' ]
}
}
}
} ,
optimizeDeps: {
include: [
'@temelj/result' ,
'@temelj/async' ,
'@temelj/value' ,
'@temelj/string'
]
}
}) ;
Library mode
When building a library that depends on Temelj:
import { defineConfig } from 'vite' ;
import { resolve } from 'path' ;
export default defineConfig ({
build: {
lib: {
entry: resolve ( __dirname , 'src/index.ts' ),
name: 'MyLibrary' ,
formats: [ 'es' ],
fileName: 'index'
},
rollupOptions: {
// Externalize Temelj packages
external: [
'@temelj/result' ,
'@temelj/async' ,
'@temelj/value' ,
'@temelj/string' ,
'@temelj/iterator' ,
'@temelj/math' ,
'@temelj/request' ,
'@temelj/id' ,
'@temelj/color'
]
}
}
}) ;
Externalize Temelj packages in library mode to avoid bundling them twice when consumers install both your library and Temelj.
Webpack
Configure Webpack 5 to handle ES modules correctly.
import path from 'path' ;
import { fileURLToPath } from 'url' ;
const __filename = fileURLToPath ( import . meta . url );
const __dirname = path . dirname ( __filename );
export default {
entry: './src/index.ts' ,
output: {
path: path . resolve ( __dirname , 'dist' ),
filename: '[name].[contenthash].js' ,
module: true ,
chunkFormat: 'module'
} ,
experiments: {
outputModule: true
} ,
module: {
rules: [
{
test: / \. ts $ / ,
use: 'ts-loader' ,
exclude: /node_modules/
}
]
} ,
resolve: {
extensions: [ '.ts' , '.js' , '.mjs' ],
extensionAlias: {
'.js' : [ '.js' , '.ts' ],
'.mjs' : [ '.mjs' , '.mts' ]
}
} ,
optimization: {
moduleIds: 'deterministic' ,
runtimeChunk: 'single' ,
splitChunks: {
cacheGroups: {
temelj: {
test: / [ \\ / ] node_modules [ \\ / ] @temelj [ \\ / ] / ,
name: 'temelj' ,
chunks: 'all' ,
priority: 10
},
vendor: {
test: / [ \\ / ] node_modules [ \\ / ] / ,
name: 'vendors' ,
chunks: 'all'
}
}
}
}
} ;
TypeScript with Webpack
{
"compilerOptions" : {
"target" : "ES2022" ,
"module" : "ESNext" ,
"moduleResolution" : "bundler" ,
"esModuleInterop" : true ,
"resolveJsonModule" : true ,
"isolatedModules" : true ,
"jsx" : "react-jsx"
}
}
esbuild
Use esbuild for fast builds with Temelj packages.
import * as esbuild from 'esbuild' ;
await esbuild . build ({
entryPoints: [ 'src/index.ts' ],
bundle: true ,
outfile: 'dist/bundle.js' ,
format: 'esm' ,
target: 'es2022' ,
platform: 'browser' ,
minify: true ,
sourcemap: true ,
splitting: true ,
outdir: 'dist' ,
outExtension: { '.js' : '.mjs' },
external: [
// Optionally externalize for library builds
// '@temelj/*'
],
treeShaking: true ,
metafile: true
});
package.json scripts
build.js (with analysis)
{
"scripts" : {
"build" : "node build.js" ,
"build:watch" : "node build.js --watch" ,
"build:analyze" : "node build.js --analyze"
}
}
Rollup
Rollup configuration for Temelj packages.
import typescript from '@rollup/plugin-typescript' ;
import resolve from '@rollup/plugin-node-resolve' ;
import commonjs from '@rollup/plugin-commonjs' ;
import terser from '@rollup/plugin-terser' ;
export default {
input: 'src/index.ts' ,
output: [
{
file: 'dist/index.mjs' ,
format: 'es' ,
sourcemap: true
},
{
file: 'dist/index.min.mjs' ,
format: 'es' ,
sourcemap: true ,
plugins: [ terser ()]
}
] ,
plugins: [
resolve ({
extensions: [ '.ts' , '.js' , '.mjs' ]
}),
commonjs (),
typescript ({
tsconfig: './tsconfig.json' ,
declaration: true ,
declarationDir: 'dist' ,
rootDir: 'src'
})
] ,
external: [
// For library builds, externalize Temelj
/ ^ @temelj \/ /
]
} ;
Turborepo (monorepo builds)
Temelj itself uses Turbo for its monorepo. Use this configuration as a reference.
{
"$schema" : "https://turborepo.com/schema.json" ,
"tasks" : {
"build" : {
"dependsOn" : [ "^build" ],
"inputs" : [ "$TURBO_DEFAULT$" , ".env*" ],
"outputs" : [ "dist/**" ]
},
"dev" : {
"dependsOn" : [ "^build" ],
"cache" : false ,
"persistent" : true
},
"typecheck" : {
"dependsOn" : [ "^build" ]
},
"test" : {
"dependsOn" : [ "^build" ],
"outputs" : [ "coverage/**" ]
}
}
}
Using tsdown (Temelj's build tool)
Bundle size optimization
Temelj packages are already optimized, but you can further reduce bundle size.
Tree shaking
All Temelj packages support tree shaking. Import only what you need:
// ✓ Good - only imports what you use
import { ok , err } from '@temelj/result' ;
import { retry } from '@temelj/async' ;
// ✗ Avoid - imports everything
import * as Result from '@temelj/result' ;
import * as Async from '@temelj/async' ;
Code splitting
Split Temelj utilities across chunks for better caching:
Identify shared utilities
Determine which Temelj packages are used across multiple routes or components.
Create dedicated chunks
Configure your bundler to separate frequently-used utilities: export default defineConfig ({
build: {
rollupOptions: {
output: {
manualChunks ( id ) {
if ( id . includes ( '@temelj/result' ) || id . includes ( '@temelj/value' )) {
return 'utils-core' ;
}
if ( id . includes ( '@temelj/async' )) {
return 'utils-async' ;
}
}
}
}
}
}) ;
Measure impact
Use bundle analysis tools to verify size improvements: npx vite-bundle-visualizer
Dependency analysis
Check what Temelj packages bring in:
# Install analyzer
npm install -D webpack-bundle-analyzer
# For Webpack
npx webpack-bundle-analyzer dist/stats.json
# For Vite
npx vite-bundle-visualizer
# For esbuild (with metafile)
node -e "console.log(require('./meta.json'))"
Most Temelj packages have zero dependencies. Notable exceptions:
@temelj/value: uses deepmerge and react-fast-compare
@temelj/request: uses tough-cookie
@temelj/id: uses id128
Production optimization
Optimize for production deployments.
Environment-specific builds
import { defineConfig } from 'vite' ;
export default defineConfig (({ mode }) => ({
build: {
minify: mode === 'production' ? 'esbuild' : false ,
sourcemap: mode === 'development' ,
target: 'es2022' ,
rollupOptions: {
output: {
compact: mode === 'production' ,
manualChunks: mode === 'production' ? {
temelj: [
'@temelj/result' ,
'@temelj/async' ,
'@temelj/value'
]
} : undefined
}
}
} ,
define: {
__DEV__: JSON . stringify ( mode === 'development' )
}
})) ;
Compression
Enable compression for maximum size reduction:
import { defineConfig } from 'vite' ;
import compression from 'vite-plugin-compression' ;
export default defineConfig ({
plugins: [
compression ({
algorithm: 'brotliCompress' ,
ext: '.br' ,
threshold: 1024
}),
compression ({
algorithm: 'gzip' ,
ext: '.gz'
})
]
}) ;
Testing bundled output
Verify your bundle works correctly:
import { describe , it , expect } from 'vitest' ;
import { ok , isOk } from '@temelj/result' ;
describe ( 'Bundled utilities' , () => {
it ( 'imports Result utilities correctly' , () => {
const result = ok ( 42 );
expect ( isOk ( result )). toBe ( true );
});
it ( 'tree-shakes unused exports' , async () => {
// This test verifies that only imported functions are bundled
const module = await import ( '@temelj/result' );
expect ( module ). toHaveProperty ( 'ok' );
expect ( module ). toHaveProperty ( 'err' );
});
});
Common bundling issues
If you encounter “Cannot find module” errors, ensure your bundler is configured to resolve .mjs extensions and ES modules correctly.
Module resolution errors
Check package.json type
Ensure your project uses ES modules:
Verify bundler configuration
Add .mjs to resolved extensions: resolve : {
extensions : [ '.ts' , '.tsx' , '.js' , '.jsx' , '.mjs' ]
}
Update TypeScript config
Set proper module resolution: {
"compilerOptions" : {
"moduleResolution" : "bundler"
}
}
Dual package hazard
Avoid importing the same package as both ESM and CJS:
// ✗ Don't do this
import { ok } from '@temelj/result' ; // ESM
const { err } = require ( '@temelj/result' ); // CJS (not supported)
// ✓ Always use ESM imports
import { ok , err } from '@temelj/result' ;
Framework-specific guides
export default {
experimental: {
esmExternals: true
} ,
transpilePackages: [
'@temelj/result' ,
'@temelj/async' ,
'@temelj/value' ,
'@temelj/string'
] ,
webpack : ( config ) => {
config . resolve . extensionAlias = {
'.js' : [ '.js' , '.ts' ],
'.mjs' : [ '.mjs' , '.mts' ]
};
return config ;
}
} ;
import { sveltekit } from '@sveltejs/kit/vite' ;
import { defineConfig } from 'vite' ;
export default defineConfig ({
plugins: [ sveltekit ()] ,
optimizeDeps: {
include: [
'@temelj/result' ,
'@temelj/async' ,
'@temelj/value'
]
}
}) ;
import { defineConfig } from 'astro/config' ;
export default defineConfig ({
vite: {
optimizeDeps: {
include: [ '@temelj/result' , '@temelj/async' ]
}
}
}) ;
Typical bundle sizes for common Temelj usage:
Bundle sizes (minified + gzipped)
@temelj/result alone: ~1.2 KB
@temelj/async alone: ~3.5 KB
@temelj/value alone: ~2.1 KB
@temelj/string alone: ~1.8 KB
All core packages: ~7.5 KB
Complete library: ~15 KB
These sizes include all exports. With tree shaking, your actual bundle will typically be 40-60% smaller depending on what you import.