Build configuration and module splitting setup for optimal JavaScript output, including TypeScript compilation settings, linting rules, and code splitting strategies.
The Fable library uses TypeScript for type safety and modern JavaScript features while targeting broad compatibility.
{
"compilerOptions": {
"target": "ES2015",
"lib": ["ES2015", "DOM"],
"module": "ES2015",
"moduleResolution": "node",
"declaration": false,
"sourceMap": false,
"outDir": "../../build/fable-library",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowJs": true
},
"include": [
"**/*.ts",
"**/*.js"
],
"exclude": [
"node_modules",
"**/*.test.ts",
"**/*.spec.ts"
]
}// Target: ES2015 provides modern features while maintaining compatibility
// - Arrow functions, classes, const/let, template literals
// - Promises, Maps, Sets, Symbols
// - Destructuring, spread operator
// - Modules (import/export)
// Examples of ES2015 features used in Fable library:
// Arrow functions for callbacks
const mapper = (x: number) => x * 2;
const filtered = array.filter(x => x > 0);
// Classes for type definitions
class AsyncBuilder {
Bind<T, U>(computation: IAsync<T>, binder: (x: T) => IAsync<U>): IAsync<U> {
// Implementation using modern class syntax
}
}
// Template literals for string formatting
function formatError(operation: string, error: Error): string {
return `Operation '${operation}' failed: ${error.message}`;
}
// Destructuring and spread
function combineOptions(defaults: any, options: any): any {
return { ...defaults, ...options };
}
// Maps and Sets for collections
const cache = new Map<string, any>();
const uniqueValues = new Set<number>();
// Promises for async operations (integrated with F# async)
function convertToPromise<T>(computation: IAsync<T>): Promise<T> {
return new Promise((resolve, reject) => {
// Implementation using native Promises
});
}// strict: true enables all strict type checking options:
// 1. noImplicitAny - Variables must have explicit types
function processValue(value: any): string { // Explicit 'any' required
return value.toString();
}
// 2. strictNullChecks - null and undefined handling
function safeLookup<T>(map: Map<string, T>, key: string): T | undefined {
return map.get(key); // Return type properly reflects possible undefined
}
// 3. strictFunctionTypes - Function parameter contravariance
interface EventHandler<T> {
(event: T): void;
}
// 4. strictBindCallApply - Strict bind/call/apply signatures
function bindMethod<T, Args extends any[], R>(
obj: T,
method: (this: T, ...args: Args) => R
): (...args: Args) => R {
return method.bind(obj);
}
// 5. strictPropertyInitialization - Class property initialization
class Timer {
private intervalId!: number; // Definite assignment assertion when needed
constructor(private interval: number) {
this.start();
}
private start(): void {
this.intervalId = setInterval(() => {}, this.interval);
}
}
// 6. noImplicitReturns - All code paths must return
function classify(value: number): "positive" | "negative" | "zero" {
if (value > 0) return "positive";
if (value < 0) return "negative";
return "zero"; // Required - no implicit return
}// moduleResolution: "node" enables Node.js-style module resolution
// This allows importing from npm packages and relative paths
// Import from npm packages
import * as bigJs from "big.js";
// Relative imports within the library
import { equals, compare } from "./Util.js";
import { SystemObject } from "./Types.js";
// Index file imports (resolves to index.ts/index.js)
import * as Collections from "./Collections/index.js";
// JSON imports (with resolveJsonModule: true)
import packageInfo from "./package.json";
// Type-only imports for better tree-shaking
import type { IComparer, IEquatable } from "./Util.js";// sourceMap: false in production builds for smaller size
// Can be enabled for development:
{
"compilerOptions": {
"sourceMap": true, // Enable for development
"inlineSourceMap": false,
"sourceRoot": "../src",
"mapRoot": "./maps"
}
}
// Usage in development tools:
// - Browser DevTools can show original TypeScript source
// - Stack traces reference original file locations
// - Breakpoints work in original source files// declaration: false to avoid generating .d.ts files
// F# generates its own type definitions through Fable compiler
// When enabled, would generate declarations like:
export declare class Long {
constructor(low: number, high: number, unsigned?: boolean);
low: number;
high: number;
unsigned: boolean;
GetHashCode(): number;
Equals(x: any): boolean;
CompareTo(x: any): number;
}
export declare function add(left: Long, right: Long): Long;
export declare function subtract(left: Long, right: Long): Long;{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"semicolon": [true, "always"],
"trailing-comma": [
true,
{
"multiline": "always",
"singleline": "never"
}
],
"quotemark": [true, "double"],
"indent": [true, "spaces", 2],
"max-line-length": [true, 120],
"no-console": false,
"no-var-requires": false,
"object-literal-sort-keys": false,
"ordered-imports": false,
"interface-name": false,
"no-namespace": false,
"no-empty": false,
"prefer-const": true,
"curly": true,
"eofline": true,
"no-duplicate-imports": true,
"no-unused-expression": true
},
"rulesDirectory": []
}// 1. semicolon: [true, "always"] - Require semicolons
function example(): void {
console.log("Statement must end with semicolon"); // ✓ Good
// console.log("Missing semicolon") // ✗ Error
}
// 2. quotemark: [true, "double"] - Use double quotes consistently
const message = "Use double quotes"; // ✓ Good
// const message = 'Single quotes not allowed'; // ✗ Error
// 3. indent: [true, "spaces", 2] - 2-space indentation
function nestedFunction(): void {
if (true) {
console.log("Properly indented"); // ✓ 2 spaces
}
}
// 4. max-line-length: [true, 120] - Maximum line length
const longVariableName = "This line should not exceed 120 characters including all content and proper formatting";
// 5. trailing-comma: Consistent comma usage
const config = {
option1: "value1",
option2: "value2", // ✓ Trailing comma in multiline
};
const singleLine = { a: 1, b: 2 }; // ✓ No trailing comma in single line
// 6. prefer-const: Use const for immutable bindings
const immutableValue = 42; // ✓ Good
let mutableValue = 42; // ✓ Good when modified later
// var oldStyle = 42; // ✗ Prefer let/const over var
// 7. curly: Require braces for control structures
if (condition) {
doSomething(); // ✓ Good - braces required
}
// if (condition) doSomething(); // ✗ Error - missing braces
// 8. no-duplicate-imports: Prevent duplicate import statements
import { ClassA, ClassB } from "./module.js"; // ✓ Good
// import { ClassA } from "./module.js"; // ✗ Error if ClassB imported elsewhere
// import { ClassB } from "./module.js";// Class names: PascalCase
class AsyncBuilder { }
class MailboxProcessor<T> { }
// Interface names: PascalCase, may start with 'I'
interface IComparable<T> { }
interface Continuation<T> { }
// Function names: camelCase
function createCancellationToken(): CancellationToken { }
function startAsPromise<T>(computation: IAsync<T>): Promise<T> { }
// Variable names: camelCase
const defaultTimeout = 5000;
let currentState: string;
// Constants: SCREAMING_SNAKE_CASE or camelCase
const MAX_RETRY_ATTEMPTS = 3;
const defaultCancellationToken = new CancellationToken();
// Generic type parameters: Single uppercase letters
class Container<T, U, V> { }
function map<TInput, TOutput>(mapper: (x: TInput) => TOutput): TOutput { }
// File names: PascalCase for main modules, camelCase for utilities
// AsyncBuilder.ts, MailboxProcessor.ts
// util.ts, helpers.ts/**
* Represents a 64-bit integer with arithmetic operations.
*
* @example
* ```typescript
* const bigNumber = new Long(0x12345678, 0x9ABCDEF0);
* const doubled = Long.multiply(bigNumber, Long.fromNumber(2));
* ```
*/
class Long {
/**
* Creates a new Long instance.
*
* @param low - The low 32 bits
* @param high - The high 32 bits
* @param unsigned - Whether the number is unsigned
*/
constructor(low: number, high: number, unsigned?: boolean) {
// Implementation
}
/**
* Compares this Long with another for equality.
*
* @param other - The Long to compare with
* @returns true if equal, false otherwise
*/
Equals(other: any): boolean {
// Implementation
}
}
// Inline comments for complex logic
function complexCalculation(input: number[]): number {
// Sort array for binary search optimization
const sorted = input.sort((a, b) => a - b);
// Binary search implementation
let left = 0;
let right = sorted.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
// ... rest of implementation
}
return -1; // Not found
}// splitter.config.js - Code splitting configuration for optimal loading
module.exports = {
// Entry points for different functional areas
entries: {
// Core functionality - loaded first
core: [
"./Util.ts",
"./Types.js",
"./Option.js"
],
// Numeric operations
numeric: [
"./Long.js",
"./Int32.ts",
"./Double.ts",
"./Decimal.ts",
"./BigInt.fs"
],
// String and text processing
text: [
"./String.ts",
"./RegExp.ts",
"./Char.ts",
"./Unicode.9.0.0.ts"
],
// Collections and sequences
collections: [
"./Seq.ts",
"./Array.fs",
"./List.fs",
"./Set.fs",
"./Map.fs",
"./DictTypes.js"
],
// Date and time
datetime: [
"./Date.ts",
"./DateOffset.ts",
"./TimeSpan.ts"
],
// Async and reactive programming
async: [
"./Async.ts",
"./AsyncBuilder.ts",
"./MailboxProcessor.ts",
"./Observable.ts",
"./Event.ts",
"./Timer.ts"
],
// Data encoding and I/O
encoding: [
"./Encoding.ts",
"./BitConverter.ts",
"./Uri.ts"
],
// Type system and reflection
reflection: [
"./Reflection.ts"
]
},
// Output configuration
output: {
path: "./dist/chunks/",
filename: "[name].bundle.js",
chunkFilename: "[id].[chunkhash].chunk.js"
},
// Optimization settings
optimization: {
splitChunks: {
chunks: "all",
minSize: 20000,
maxSize: 200000,
cacheGroups: {
// Vendor libraries
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
priority: 10,
chunks: "all"
},
// Common utilities used across modules
common: {
name: "common",
minChunks: 2,
priority: 5,
chunks: "all",
enforce: true
}
}
}
}
};// webpack.config.dev.js - Development build settings
const path = require("path");
const splitterConfig = require("./splitter.config.js");
module.exports = {
mode: "development",
// Use splitter configuration for entries
entry: splitterConfig.entries,
output: {
path: path.resolve(__dirname, "../../build/fable-library/dev"),
filename: "[name].js",
library: "FableLibrary",
libraryTarget: "umd"
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
}
]
},
resolve: {
extensions: [".ts", ".js"],
modules: [
path.resolve(__dirname, "./"),
"node_modules"
]
},
// Development optimizations
devtool: "source-map",
optimization: {
minimize: false,
...splitterConfig.optimization
},
// Development server configuration
devServer: {
contentBase: path.join(__dirname, "../../build"),
compress: true,
port: 9000,
hot: true
}
};// webpack.config.prod.js - Production build settings
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const splitterConfig = require("./splitter.config.js");
module.exports = {
mode: "production",
entry: splitterConfig.entries,
output: {
path: path.resolve(__dirname, "../../build/fable-library/prod"),
filename: "[name].[contenthash].js",
chunkFilename: "[id].[contenthash].chunk.js",
library: "FableLibrary",
libraryTarget: "umd",
clean: true // Clean output directory before build
},
module: {
rules: [
{
test: /\.ts$/,
use: {
loader: "ts-loader",
options: {
configFile: "tsconfig.prod.json"
}
},
exclude: /node_modules/
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", {
targets: "> 0.25%, not dead",
modules: false
}]
],
plugins: [
"@babel/plugin-transform-runtime"
]
}
}
}
]
},
resolve: {
extensions: [".ts", ".js"],
modules: [
path.resolve(__dirname, "./"),
"node_modules"
]
},
// Production optimizations
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: false, // Keep console for debugging
drop_debugger: true,
pure_funcs: ["console.debug"]
},
format: {
comments: false
}
},
extractComments: false
})
],
...splitterConfig.optimization,
// Additional production optimizations
sideEffects: false,
usedExports: true,
// Runtime chunk for better caching
runtimeChunk: "single"
},
// Performance hints
performance: {
hints: "warning",
maxAssetSize: 200000,
maxEntrypointSize: 200000
}
};{
"name": "fable-library",
"version": "1.0.0",
"description": "F# runtime library for JavaScript/TypeScript",
"main": "dist/core.js",
"module": "dist/core.esm.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/core.esm.js",
"require": "./dist/core.js",
"types": "./dist/index.d.ts"
},
"./Util": {
"import": "./dist/Util.esm.js",
"require": "./dist/Util.js",
"types": "./dist/Util.d.ts"
},
"./Long": {
"import": "./dist/Long.esm.js",
"require": "./dist/Long.js",
"types": "./dist/Long.d.ts"
},
"./String": {
"import": "./dist/String.esm.js",
"require": "./dist/String.js",
"types": "./dist/String.d.ts"
}
},
"files": [
"dist/",
"lib/",
"*.md"
],
"scripts": {
"build": "npm run build:dev && npm run build:prod",
"build:dev": "webpack --config webpack.config.dev.js",
"build:prod": "webpack --config webpack.config.prod.js",
"typecheck": "tsc --noEmit",
"lint": "tslint --project .",
"lint:fix": "tslint --project . --fix",
"test": "jest",
"test:watch": "jest --watch",
"clean": "rimraf dist build",
"dev": "webpack-dev-server --config webpack.config.dev.js"
},
"dependencies": {
"big.js": "^6.2.1"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@babel/plugin-transform-runtime": "^7.15.0",
"babel-loader": "^8.2.0",
"typescript": "^4.4.0",
"ts-loader": "^9.2.0",
"tslint": "^6.1.0",
"webpack": "^5.50.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.0.0",
"terser-webpack-plugin": "^5.2.0",
"jest": "^27.0.0",
"@types/jest": "^27.0.0",
"rimraf": "^3.0.0"
},
"peerDependencies": {},
"engines": {
"node": ">=12.0.0"
},
"keywords": [
"fsharp",
"functional-programming",
"javascript",
"typescript",
"compiler",
"runtime"
],
"repository": {
"type": "git",
"url": "https://github.com/fable-compiler/fable-library.git"
},
"bugs": {
"url": "https://github.com/fable-compiler/fable-library/issues"
},
"homepage": "https://fable.io",
"license": "MIT"
}// 1. Full library import (loads all modules)
import * as Fable from "fable-library";
// 2. Selective imports for better tree-shaking
import * as Util from "fable-library/Util";
import * as Long from "fable-library/Long";
import * as String from "fable-library/String";
// 3. Specific function imports
import { equals, compare } from "fable-library/Util";
import { fromNumber, add, multiply } from "fable-library/Long";
// 4. Dynamic imports for code splitting
async function loadNumericModule() {
const { Long } = await import("fable-library/Long");
return Long;
}
// 5. Node.js CommonJS imports
const Util = require("fable-library/Util");
const Long = require("fable-library/Long");// Bundle size optimization strategies:
// 1. Import only needed modules
import { equals } from "fable-library/Util"; // ~2KB
// import * as Util from "fable-library/Util"; // ~15KB
// 2. Use specific entry points
import { fromNumber } from "fable-library/Long"; // ~8KB
// import * as FableLib from "fable-library"; // ~150KB+
// 3. Lazy loading for optional features
const loadBigIntModule = () => import("fable-library/BigInt");
async function performBigIntCalculation(value: string) {
const { parse, multiply } = await loadBigIntModule();
return multiply(parse(value), parse("2"));
}
// 4. Conditional imports based on feature flags
let DecimalModule: any;
if (FEATURE_FLAGS.highPrecisionMath) {
DecimalModule = await import("fable-library/Decimal");
}