This skill provides a comprehensive solution for ensuring Internet Explorer (IE8+) compatibility in modern web applications built with Webpack. It encapsulates configurations for Babel, PostCSS, and Webpack, along with necessary polyfills.
The solution addresses compatibility issues in four main layers:
ie-compatibility-skill/
├── SKILL.md # This documentation
└── assets/
├── index.js # Main exports: babelRule, postcssPlugins, etc.
├── polyfills.js # Entry point for all polyfills
├── console.js # Console polyfill for IE8
├── IE-polyfills.js # IE-specific DOM polyfills
├── HarmonyWebpackPlugin.js # Webpack plugin for ES3 compatibility
├── postcss-opacity.js # PostCSS plugin for opacity fallback
└── TransformObjectRestSpread.js # Babel plugin for object rest spread
babelRule)The Babel configuration transpiles ES6+ JavaScript to ES5 compatible code.
Features:
@babel/preset-env in loose mode with forceAllTransforms: truemodules: false to preserve ES modules for Webpack@babel/transform-function-name to avoid IE issuesTransformObjectRestSpread plugin using _.assign instead of Object.assignExport:
export const babelRule = {
enforce : 'post',
test : /\.js$/,
use : {
loader : 'babel-loader',
options : {
presets : [
['@babel/env', {
forceAllTransforms : true,
loose : true,
modules : false,
exclude : ['@babel/transform-function-name'],
}],
],
plugins : [
['@babel/transform-runtime', {
helpers : false,
regenerator : true,
}],
[TransformObjectRestSpread, {loose : true, useBuiltIns : true}],
],
},
},
};
postcssPlugins)PostCSS plugins for CSS compatibility with older browsers.
Plugins:
postcss-selector-not: Transforms :not() selectors for better compatibilitypostcss-opacity: Adds filter: alpha(opacity=X) fallback for IEautoprefixer: Adds vendor prefixes automaticallyExport:
export const postcssPlugins = [
postcssSelectorNot(),
postcssOpacity(),
autoprefixer(),
];
cssSplitPlugin)Splits CSS files to avoid the IE9 limit of 4095 selectors per file.
export const cssSplitPlugin = new CSSSplitWebpackPlugin({});
harmonyPlugin)Patches Webpack output to quote ES3 reserved keywords (like default, catch) when used as property names. This is critical
for IE8 which doesn't support ES5+ syntax.
Patches applied:
Object.defineProperty)__esModule marker (simplifies assignment)document.head to document.getElementsByTagName("head")[0]export const harmonyPlugin = HarmonyPlugin.es3();
terserOptions)Minification options configured to be safe for IE8.
Features:
ie8: true - Enables IE8-safe transformationsquote_style: 1 - Always quote property namesconsole.debug calls in productionexport const terserOptions = {
ie8 : true,
output : {
quote_style : 1,
},
mangle : {},
compress : {
warnings : false,
dead_code : true,
pure_funcs : ['debug', 'console.debug'],
},
};
polyfills.js)Import this file at the very beginning of your application entry point.
// Core-js stable polyfills (ES6+ features)
import 'core-js/stable';
// Additional polyfills for specific DOM features
import 'classlist-polyfill';
// Custom Console Methods under IE
import './console.js';
// Custom IE hacks (if any, e.g., for HTML5 tags in IE8)
import './IE-polyfills.js';
console.js)Ensures console methods are available in IE8 where the console object might be null or undefined when developer tools are
closed.
Features:
console.log behaviorIE-polyfills.js)Contains fixes for specific Internet Explorer issues:
preventDefault and stopPropagation to Event.prototypeimport {
babelRule,
postcssPlugins,
cssSplitPlugin,
terserOptions,
harmonyPlugin
} from './ie-compatibility-skill/assets/index.js';
import TerserPlugin from 'terser-webpack-plugin';
export default {
entry : {
app : './src/index.js',
},
module : {
rules : [
babelRule, // Add Babel loader rule
{
test : /\.css$/,
use : [
'style-loader',
'css-loader',
{
loader : 'postcss-loader',
options : {
postcssOptions : {
plugins : postcssPlugins,
},
},
},
],
},
],
},
plugins : [
cssSplitPlugin, // Handle IE CSS selector limit
harmonyPlugin, // Handle ES3 keywords
],
optimization : {
minimizer : [
new TerserPlugin({terserOptions}), // IE8 safe minification
],
},
};
Include polyfills at the very beginning of your entry file:
// Import polyfills first - MUST be before any other imports
import './ie-compatibility-skill/assets/polyfills.js';
// Then import your application
import './app.js';
module.exports = {
module : {
rules : [
babelRule,
{
test : /\.scss$/,
use : [
'style-loader',
'css-loader',
{
loader : 'postcss-loader',
options : {
postcssOptions : {
plugins : postcssPlugins,
},
},
},
'sass-loader',
],
},
],
},
};
Ensure the following packages are installed:
npm install --save-dev \
# Babel
babel-loader \
@babel/core \
@babel/preset-env \
@babel/plugin-transform-runtime \
# PostCSS
postcss-loader \
autoprefixer \
postcss-selector-not \
# Webpack plugins
terser-webpack-plugin \
css-split-webpack-plugin
# Runtime dependencies
npm install core-js classlist-polyfill
| Feature | IE8 | IE9 | IE10 | IE11 |
|---|---|---|---|---|
| ES5 Syntax | ✅ | ✅ | ✅ | ✅ |
| ES6+ Syntax (transpiled) | ✅ | ✅ | ✅ | ✅ |
| CSS Opacity | ✅ | ✅ | ✅ | ✅ |
| CSS Selector Limit | ⚠️ | ✅ | ✅ | ✅ |
| HTML5 Tags | ✅ | ✅ | ✅ | ✅ |
| Console API | ✅ | ✅ | ✅ | ✅ |
| Event.preventDefault/stopPropagation | ✅ | ✅ | ✅ | ✅ |
Solution: Ensure core-js/stable is imported at the very top of your entry file before any other code.
// WRONG - other code may execute before polyfills
import './app.js';
import './polyfills.js';
// CORRECT - polyfills load first
import './polyfills.js';
import './app.js';
Possible causes:
Solution: Ensure cssSplitPlugin and postcssPlugins are configured correctly.
Cause: ES3 reserved keywords used as property names without quotes.
Solution: Ensure harmonyPlugin is added to your Webpack plugins array.
Solution: The console.js polyfill handles this. Ensure it's imported in your polyfills entry.
Solution: The IE-polyfills.js includes a focus patch. Ensure it's imported.
Always import polyfills before any application code to ensure all features are available.
// polyfills.js should be your first import
import './ie-compatibility-skill/assets/polyfills.js';
Don't rely solely on IE emulation modes. Test in:
Only include polyfills you actually need. Consider using @babel/preset-env with useBuiltIns: 'usage' for smaller bundles.
Monitor your CSS bundle sizes. If approaching 4095 selectors per file, the cssSplitPlugin will automatically split them.
loose Mode CarefullyBabel's loose mode produces smaller, faster code but may deviate slightly from spec. Test thoroughly.
class SyntaxPer project conventions, avoid ES6 class definitions. Use factory functions or prototype-based inheritance instead.
import {terserOptions} from './ie-compatibility-skill/assets/index.js';
const customTerserOptions = {
...terserOptions,
compress : {
...terserOptions.compress,
// Add custom compress options
drop_console : true, // Remove all console.* calls
},
};
import {postcssPlugins} from './ie-compatibility-skill/assets/index.js';
const customPostcssPlugins = [
...postcssPlugins,
// Add additional plugins
require('postcss-preset-env')({
stage : 3,
}),
];
Load polyfills only when needed:
// In your entry file
if (typeof window !== 'undefined' && !window.Promise) {
// Browser needs polyfills
require('./ie-compatibility-skill/assets/polyfills.js');
}
Ensure polyfills appear first in your bundle:
# Analyze bundle composition
npx webpack-bundle-analyzer dist/app.js
Check that opacity fallbacks are generated:
/* Input */
.element {
opacity: 0.5;
}
/* Output */
.element {
filter: alpha(opacity=50);
opacity: 0.5;
}
Check that reserved keywords are quoted in output:
// Input (ESM)
export default function () {}
// Output (ES3 compatible)
exports['default'] = function () {};