Build Tools and Development Workflow
1. Webpack and Polyfill Integration
| Approach | Configuration | Use Case | Bundle Impact |
|---|---|---|---|
| Entry Polyfills | entry: ['core-js', './src'] | Global polyfills | Large bundle |
| ProvidePlugin | Auto-inject globals | Replace globals like Promise | On-demand injection |
| Dynamic Import | import() with magic comments | Code splitting | Separate chunks |
| splitChunks | Vendor chunk optimization | Cache polyfills separately | Better caching |
| Polyfill Service Loader | Custom webpack loader | Conditional loading | Minimal for modern browsers |
Example: Webpack 5 polyfill configuration
// webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'production',
entry: {
// Separate polyfill entry
polyfills: './src/polyfills.js',
app: './src/index.js'
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
// Webpack 5 doesn't auto-polyfill Node.js modules
resolve: {
fallback: {
"buffer": require.resolve("buffer/"),
"process": require.resolve("process/browser"),
"stream": require.resolve("stream-browserify"),
"util": require.resolve("util/")
}
},
plugins: [
// Provide global polyfills automatically
new webpack.ProvidePlugin({
Promise: ['es6-promise', 'Promise'],
fetch: ['whatwg-fetch', 'fetch'],
process: 'process/browser',
Buffer: ['buffer', 'Buffer']
})
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
// Separate polyfills into their own chunk
polyfills: {
test: /[\\/]node_modules[\\/](core-js|regenerator-runtime|whatwg-fetch)[\\/]/,
name: 'polyfills',
chunks: 'all',
priority: 20
},
// Vendor chunk for other libraries
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
priority: 10
}
}
},
minimize: true,
usedExports: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3,
targets: '> 0.25%, not dead'
}]
]
}
}
}
]
}
};
// src/polyfills.js - Manual polyfill entry
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'whatwg-fetch';
import 'intersection-observer';
// Or selective imports
import 'core-js/features/promise';
import 'core-js/features/array/includes';
import 'core-js/features/object/assign';
// HTML template with separate polyfill chunk
// <script src="polyfills.bundle.js"></script>
// <script src="vendor.bundle.js"></script>
// <script src="app.bundle.js"></script>
Example: Conditional polyfill loading with Webpack
// src/polyfill-loader.js
export function loadPolyfills() {
const polyfills = [];
// Check what's needed
if (!window.Promise) {
polyfills.push(
import(/* webpackChunkName: "polyfill-promise" */ 'es6-promise/auto')
);
}
if (!window.fetch) {
polyfills.push(
import(/* webpackChunkName: "polyfill-fetch" */ 'whatwg-fetch')
);
}
if (!('IntersectionObserver' in window)) {
polyfills.push(
import(/* webpackChunkName: "polyfill-intersection-observer" */
'intersection-observer')
);
}
return Promise.all(polyfills);
}
// src/index.js
import { loadPolyfills } from './polyfill-loader';
loadPolyfills().then(() => {
// Now safe to use polyfilled features
import('./app').then(app => {
app.init();
});
});
// Custom webpack loader for auto-injection
// webpack-polyfill-injector-loader.js
module.exports = function(source) {
const polyfillCode = `
// Auto-injected polyfill detection
(function() {
const polyfills = [];
if (!window.Promise) {
polyfills.push(import('es6-promise/auto'));
}
if (polyfills.length > 0) {
Promise.all(polyfills).then(() => {
// Original code executes after polyfills load
${source}
});
} else {
// No polyfills needed, execute immediately
${source}
}
})();
`;
return polyfillCode;
};
Note: Webpack 5 removed automatic Node.js polyfills. Use resolve.fallback
to manually configure needed polyfills for browser compatibility.
2. Babel and Transform Plugin Configuration
| Plugin/Preset | Purpose | Configuration | Impact |
|---|---|---|---|
| @babel/preset-env | Smart polyfill injection | useBuiltIns: 'usage' | Optimal bundle size |
| @babel/transform-runtime | Helper deduplication | Avoid helper duplication | Smaller output |
| core-js | ECMAScript polyfills | corejs: 3 | Feature complete |
| regenerator-runtime | async/await support | Auto-injected | +7 KB |
| Custom transforms | Project-specific needs | Custom plugins | Variable |
Example: Babel configuration for polyfills
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
// Automatic polyfill injection based on usage
useBuiltIns: 'usage',
// core-js version
corejs: {
version: 3,
proposals: true // Include stage 3 proposals
},
// Target browsers
targets: {
browsers: [
'>0.25%',
'not dead',
'not ie 11',
'not op_mini all'
]
},
// Enable all spec transformations
spec: false,
// Loose mode for smaller output (less spec-compliant)
loose: false,
// Module transformation
modules: false, // Keep ES modules for webpack tree-shaking
// Debug output
debug: false,
// Include polyfills needed by dependencies
include: [],
// Exclude specific polyfills
exclude: ['es.promise.finally']
}]
],
plugins: [
['@babel/plugin-transform-runtime', {
// Don't polyfill (let preset-env handle it)
corejs: false,
// Extract Babel helpers into a module
helpers: true,
// Transform regenerator (async/await)
regenerator: true,
// Use ES modules
useESModules: true
}]
],
// Environment-specific configuration
env: {
production: {
plugins: [
// Remove console.log in production
['transform-remove-console', { exclude: ['error', 'warn'] }]
]
},
test: {
presets: [
['@babel/preset-env', {
targets: { node: 'current' }
}]
]
}
}
};
// .browserslistrc - Alternative browser targets
> 0.25%
not dead
not ie 11
not op_mini all
last 2 versions
Firefox ESR
// package.json browserslist (another alternative)
{
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"legacy": [
"ie 11"
]
}
}
Example: Custom Babel plugin for polyfills
// babel-plugin-custom-polyfill.js
module.exports = function({ types: t }) {
return {
name: 'custom-polyfill-injector',
visitor: {
// Detect usage of specific APIs
MemberExpression(path) {
const { object, property } = path.node;
// Detect Array.prototype.includes
if (
t.isMemberExpression(object) &&
t.isIdentifier(object.object, { name: 'Array' }) &&
t.isIdentifier(object.property, { name: 'prototype' }) &&
t.isIdentifier(property, { name: 'includes' })
) {
// Add import at top of file
const program = path.findParent(p => p.isProgram());
const importDeclaration = t.importDeclaration(
[],
t.stringLiteral('core-js/features/array/includes')
);
// Check if import already exists
const hasImport = program.node.body.some(node =>
t.isImportDeclaration(node) &&
node.source.value === 'core-js/features/array/includes'
);
if (!hasImport) {
program.unshiftContainer('body', importDeclaration);
}
}
},
// Detect Promise usage
Identifier(path) {
if (path.node.name === 'Promise' && path.isReferencedIdentifier()) {
const program = path.findParent(p => p.isProgram());
const importDeclaration = t.importDeclaration(
[],
t.stringLiteral('core-js/features/promise')
);
const hasImport = program.node.body.some(node =>
t.isImportDeclaration(node) &&
node.source.value === 'core-js/features/promise'
);
if (!hasImport) {
program.unshiftContainer('body', importDeclaration);
}
}
}
}
};
};
Note: Use useBuiltIns: 'usage' for automatic polyfill injection
based on code analysis. Reduces bundle size by 40-60% compared to 'entry' mode.
3. Rollup and ES Module Polyfill Building
| Plugin | Purpose | Configuration | Output |
|---|---|---|---|
| @rollup/plugin-babel | Transform and inject polyfills | Babel integration | ES5 + polyfills |
| @rollup/plugin-node-resolve | Resolve node_modules | Find polyfill packages | Bundled dependencies |
| @rollup/plugin-commonjs | Convert CJS to ESM | Legacy polyfills | ES modules |
| @rollup/plugin-replace | Build-time replacements | Environment variables | Optimized code |
| rollup-plugin-terser | Minification | Dead code elimination | Smaller bundles |
Example: Rollup configuration for polyfills
// rollup.config.js
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';
export default [
// Modern build (ES2015+)
{
input: 'src/index.js',
output: {
file: 'dist/bundle.modern.js',
format: 'esm',
sourcemap: true
},
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true
}),
resolve({
browser: true,
preferBuiltins: false
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: [
['@babel/preset-env', {
targets: { esmodules: true },
bugfixes: true,
modules: false,
useBuiltIns: false // No polyfills for modern build
}]
]
}),
terser({
ecma: 2015,
module: true,
compress: {
passes: 2,
pure_getters: true
}
})
]
},
// Legacy build (ES5 + polyfills)
{
input: 'src/index.js',
output: {
file: 'dist/bundle.legacy.js',
format: 'iife',
name: 'MyApp',
sourcemap: true
},
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true
}),
resolve({
browser: true,
preferBuiltins: false
}),
commonjs(),
babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: [
['@babel/preset-env', {
targets: 'ie 11',
modules: false,
useBuiltIns: 'usage',
corejs: 3
}]
]
}),
terser({
ecma: 5,
compress: {
passes: 2
},
safari10: true
})
]
},
// Library build (UMD)
{
input: 'src/lib.js',
output: [
{
file: 'dist/lib.umd.js',
format: 'umd',
name: 'MyLib',
sourcemap: true
},
{
file: 'dist/lib.esm.js',
format: 'esm',
sourcemap: true
}
],
external: ['core-js'], // Don't bundle polyfills for libraries
plugins: [
resolve(),
commonjs(),
babel({
babelHelpers: 'runtime',
exclude: 'node_modules/**',
plugins: [
['@babel/plugin-transform-runtime', {
corejs: 3,
helpers: true,
regenerator: true,
useESModules: true
}]
]
}),
terser()
]
}
];
// package.json exports for dual builds
{
"name": "my-library",
"version": "1.0.0",
"main": "dist/lib.umd.js",
"module": "dist/lib.esm.js",
"unpkg": "dist/lib.umd.js",
"types": "dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/lib.esm.js",
"require": "./dist/lib.umd.js"
}
},
"files": [
"dist"
]
}
Note: Rollup excels at tree-shaking ES modules. Create separate
modern and legacy builds for optimal performance across browsers.
4. PostCSS and CSS Polyfill Processing
| Plugin | Polyfills | Browser Support | Output |
|---|---|---|---|
| Autoprefixer | Vendor prefixes | Based on browserslist | -webkit-, -moz-, etc. |
| postcss-preset-env | Modern CSS features | Stage-based adoption | Transformed CSS |
| postcss-custom-properties | CSS variables | IE11 | Static values |
| postcss-flexbugs-fixes | Flexbox bugs | All browsers | Workarounds |
| postcss-normalize | CSS normalize/reset | Cross-browser | Baseline styles |
Example: PostCSS configuration for CSS polyfills
// postcss.config.js
module.exports = {
plugins: [
// Modern CSS features
require('postcss-preset-env')({
stage: 3, // Stage 3+ features
features: {
'custom-properties': {
preserve: false // Convert CSS variables to static values
},
'custom-media-queries': true,
'nesting-rules': true,
'color-mod-function': { unresolved: 'warn' }
},
browsers: 'last 2 versions',
autoprefixer: {
flexbox: 'no-2009',
grid: 'autoplace'
}
}),
// Autoprefixer
require('autoprefixer')({
overrideBrowserslist: [
'> 1%',
'last 2 versions',
'not dead',
'IE 11'
],
grid: 'autoplace',
flexbox: 'no-2009'
}),
// Flexbox bug fixes
require('postcss-flexbugs-fixes'),
// CSS custom properties polyfill
require('postcss-custom-properties')({
preserve: true, // Keep original for modern browsers
importFrom: './src/css-variables.css'
}),
// Normalize CSS
require('postcss-normalize')({
forceImport: true
}),
// Minification (production only)
process.env.NODE_ENV === 'production' ? require('cssnano')({
preset: ['default', {
discardComments: {
removeAll: true
},
normalizeWhitespace: true
}]
}) : false
].filter(Boolean)
};
// Input CSS
:root {
--primary-color: #007bff;
--spacing: 1rem;
}
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing);
color: var(--primary-color);
}
.flex-container {
display: flex;
flex-wrap: wrap;
}
.custom-selector:has(> .child) {
background: color-mod(var(--primary-color) alpha(50%));
}
// Output CSS (after PostCSS)
.container {
display: -ms-grid;
display: grid;
-ms-grid-columns: (minmax(250px, 1fr))[auto-fit];
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
color: #007bff;
}
.flex-container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
// Fallback for :has() selector
.custom-selector {
background: rgba(0, 123, 255, 0.5);
}
Note: PostCSS can polyfill modern CSS features at build time.
Use postcss-preset-env for automatic feature detection and transformation.
5. ESLint Rules for Polyfill Code Quality
| Rule Category | ESLint Rules | Purpose | Severity |
|---|---|---|---|
| ES5 Compatibility | no-restricted-syntax | Prevent modern syntax | Error |
| Globals | no-undef, no-global-assign | Protect global scope | Error |
| Best Practices | no-extend-native | Safe prototype extension | Warning |
| Security | no-eval, no-new-func | Prevent code injection | Error |
| Performance | no-loop-func | Avoid closure overhead | Warning |
Example: ESLint configuration for polyfills
// .eslintrc.js
module.exports = {
env: {
browser: true,
es6: true
},
extends: [
'eslint:recommended'
],
parserOptions: {
ecmaVersion: 2015,
sourceType: 'module'
},
rules: {
// Prevent extending native prototypes unsafely
'no-extend-native': 'error',
// Protect global objects
'no-global-assign': 'error',
'no-undef': 'error',
// Security rules
'no-eval': 'error',
'no-new-func': 'error',
'no-implied-eval': 'error',
// Best practices
'eqeqeq': ['error', 'always'],
'no-proto': 'error',
'no-with': 'error',
// ES5 compatibility (for polyfills targeting old browsers)
'no-restricted-syntax': [
'error',
{
selector: 'ArrowFunctionExpression',
message: 'Arrow functions not supported in ES5'
},
{
selector: 'TemplateLiteral',
message: 'Template literals not supported in ES5'
},
{
selector: 'SpreadElement',
message: 'Spread operator not supported in ES5'
}
],
// Prevent console in production
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
// Performance
'no-loop-func': 'warn'
},
// Override for polyfill files
overrides: [
{
files: ['**/polyfills/**/*.js', '**/*.polyfill.js'],
rules: {
// Allow extending native prototypes in polyfill files
'no-extend-native': 'off',
// Allow function constructors for polyfills
'no-new-func': 'off',
// Polyfills need to check globals
'no-undef': 'warn'
}
},
// Stricter rules for modern code
{
files: ['src/**/*.js'],
excludedFiles: ['src/polyfills/**/*.js'],
parserOptions: {
ecmaVersion: 2022
},
rules: {
'no-var': 'error',
'prefer-const': 'error',
'prefer-arrow-callback': 'warn'
}
}
]
};
// Custom ESLint plugin for polyfill validation
// eslint-plugin-polyfill-checker.js
module.exports = {
rules: {
'check-feature-detection': {
meta: {
type: 'problem',
docs: {
description: 'Ensure polyfills include feature detection'
}
},
create(context) {
return {
AssignmentExpression(node) {
// Check if assigning to prototype
if (
node.left.type === 'MemberExpression' &&
node.left.property.name === 'prototype'
) {
// Look for if statement checking existence
const parent = context.getAncestors()[context.getAncestors().length - 2];
if (!parent || parent.type !== 'IfStatement') {
context.report({
node,
message: 'Polyfill should include feature detection guard'
});
}
}
}
};
}
},
'no-unsafe-polyfill': {
meta: {
type: 'problem',
docs: {
description: 'Prevent unsafe polyfill patterns'
}
},
create(context) {
return {
MemberExpression(node) {
// Detect Object.prototype modification
if (
node.object.name === 'Object' &&
node.property.name === 'prototype'
) {
const parent = context.getAncestors()[context.getAncestors().length - 1];
if (parent.type === 'AssignmentExpression') {
context.report({
node,
message: 'Modifying Object.prototype is dangerous'
});
}
}
}
};
}
}
}
};
Warning: Configure ESLint overrides for polyfill files to allow necessary prototype extensions while maintaining strict rules for application code.
6. TypeScript Declaration Files for Polyfills
| Approach | File Type | Use Case | Type Safety |
|---|---|---|---|
| Ambient Declarations | .d.ts files | Global polyfills | Full type checking |
| Module Augmentation | declare module | Extend existing types | Merged declarations |
| Global Augmentation | declare global | Add global types | Global scope |
| Triple-slash | /// <reference /> | Include type definitions | Explicit imports |
| @types packages | npm packages | Published type definitions | Community maintained |
Example: TypeScript declarations for polyfills
// types/polyfills.d.ts - Ambient declarations
/// <reference lib="es2015" />
// Extend Array interface with includes method
interface Array<T> {
includes(searchElement: T, fromIndex?: number): boolean;
}
// Extend Object with assign
interface ObjectConstructor {
assign<T, U>(target: T, source: U): T & U;
assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;
assign(target: object, ...sources: any[]): any;
}
// Extend String with startsWith and endsWith
interface String {
startsWith(searchString: string, position?: number): boolean;
endsWith(searchString: string, endPosition?: number): boolean;
includes(searchString: string, position?: number): boolean;
padStart(targetLength: number, padString?: string): string;
padEnd(targetLength: number, padString?: string): string;
}
// Promise polyfill types
interface PromiseConstructor {
allSettled<T>(promises: Array<Promise<T>>): Promise<Array<PromiseSettledResult<T>>>;
any<T>(promises: Array<Promise<T>>): Promise<T>;
}
type PromiseSettledResult<T> = PromiseFulfilledResult<T> | PromiseRejectedResult;
interface PromiseFulfilledResult<T> {
status: 'fulfilled';
value: T;
}
interface PromiseRejectedResult {
status: 'rejected';
reason: any;
}
// Web APIs
interface Window {
IntersectionObserver?: typeof IntersectionObserver;
ResizeObserver?: typeof ResizeObserver;
fetch?: typeof fetch;
}
// IntersectionObserver types
interface IntersectionObserverInit {
root?: Element | null;
rootMargin?: string;
threshold?: number | number[];
}
interface IntersectionObserverEntry {
readonly boundingClientRect: DOMRectReadOnly;
readonly intersectionRatio: number;
readonly intersectionRect: DOMRectReadOnly;
readonly isIntersecting: boolean;
readonly rootBounds: DOMRectReadOnly | null;
readonly target: Element;
readonly time: number;
}
type IntersectionObserverCallback = (
entries: IntersectionObserverEntry[],
observer: IntersectionObserver
) => void;
declare class IntersectionObserver {
constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit);
readonly root: Element | null;
readonly rootMargin: string;
readonly thresholds: ReadonlyArray<number>;
disconnect(): void;
observe(target: Element): void;
takeRecords(): IntersectionObserverEntry[];
unobserve(target: Element): void;
}
// Module augmentation example
declare module 'my-polyfill-library' {
export function polyfillAll(): void;
export function polyfillFeature(featureName: string): void;
export interface PolyfillConfig {
features?: string[];
force?: boolean;
debug?: boolean;
}
export function configure(config: PolyfillConfig): void;
}
// Global augmentation in module
export {};
declare global {
interface Window {
myPolyfillLibrary: {
version: string;
isPolyfilled(feature: string): boolean;
};
}
}
// tsconfig.json configuration
{
"compilerOptions": {
"target": "ES5",
"lib": ["ES2015", "DOM"],
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": false,
"types": ["node"],
"typeRoots": ["./types", "./node_modules/@types"],
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*", "types/**/*"],
"exclude": ["node_modules", "dist"]
}
Example: Publishing TypeScript polyfill library
// src/index.ts - Polyfill library with types
export function polyfillArrayIncludes(): void {
if (!Array.prototype.includes) {
Array.prototype.includes = function<T>(
this: T[],
searchElement: T,
fromIndex?: number
): boolean {
const O = Object(this);
const len = parseInt(String(O.length)) || 0;
if (len === 0) {
return false;
}
const n = parseInt(String(fromIndex)) || 0;
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (O[k] === searchElement ||
(searchElement !== searchElement && O[k] !== O[k])) {
return true;
}
k++;
}
return false;
};
}
}
export function polyfillObjectAssign(): void {
if (!Object.assign) {
Object.assign = function<T extends object, U extends object>(
target: T,
...sources: U[]
): T & U {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target) as T & U;
for (const source of sources) {
if (source != null) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
(to as any)[key] = source[key];
}
}
}
}
return to;
};
}
}
// Generate declaration files
// dist/index.d.ts (auto-generated by tsc)
export declare function polyfillArrayIncludes(): void;
export declare function polyfillObjectAssign(): void;
// package.json for TypeScript library
{
"name": "my-polyfill-library",
"version": "1.0.0",
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"typings": "dist/index.d.ts",
"files": [
"dist",
"types"
],
"scripts": {
"build": "tsc && rollup -c",
"typecheck": "tsc --noEmit"
}
}
Key Takeaways - Build Tools & Workflow
- Webpack: Use splitChunks for separate polyfill bundles, ProvidePlugin for globals
- Babel: useBuiltIns: 'usage' reduces bundle size by 40-60%
- Rollup: Excellent tree-shaking, create modern + legacy dual builds
- PostCSS: Polyfill modern CSS features at build time with preset-env
- ESLint: Configure overrides for polyfill files, enforce safety rules
- TypeScript: Provide .d.ts files for type safety with polyfilled features