My TypeScript enums are defined like this, as in this file:
export enum HueColors {
"red" = "hsl(0, 100%, 50%)",
"orange" = "hsl(30, 100%, 50%)",
// ...
"pink" = "hsl(330, 100%, 50%)",
}
export enum RGBExtended { /* ... */ }
export enum WebSafe { /* ... */ }
Setup/Config
// package.json
{
...
"main": "./index.js",
"types": "./index.d.ts",
"files": [
"**/*.{js,ts, map}"
],
"sideEffects": false,
"scripts": {
...
"build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.config.js",
...
},
"babel": {
"extends": "./config/.babelrc.json"
},
...
"devDependencies": {
"@babel/core": "^7.14.8",
"@babel/preset-env": "^7.14.8",
"@types/jest": "^26.0.24",
"@types/node": "^16.4.0",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"eslint": "^7.31.0",
"eslint-plugin-jest": "^24.4.0",
"jest": "^27.0.6",
"prettier": "^2.3.2",
"terser-webpack-plugin": "^5.1.4",
"ts-jest": "^27.0.4",
"ts-loader": "^9.2.4",
"ts-node": "^10.1.0",
"typedoc": "^0.21.4",
"typescript": "^4.3.5",
"webpack": "^5.46.0",
"webpack-cli": "^4.7.2"
}
}
// config/.babelrc.json
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
},
"modules": false
}
]
]
}
// config/tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "ES6",
"lib": ["DOM", "DOM.Iterable", "ES2017"],
"moduleResolution": "node",
"outDir": "../dist",
"noEmit": false,
"declaration": true,
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"removeComments": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true
},
"include": ["../src"],
"exclude": ["../node_modules", "../tests", "../coverage", "../src/debug.ts"]
}
// config/webpack.config.js
/* eslint-disable @typescript-eslint/no-var-requires */
const CopyPlugin = require("copy-webpack-plugin");
const path = require("path");
const basePath = path.resolve(__dirname, "../");
module.exports = {
entry: path.join(basePath, "src", "index.ts"),
mode: process.env.NODE_ENV,
devtool: process.env.NODE_ENV === "production" ? "source-map" : false,
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
options: {
configFile: path.join(__dirname, "tsconfig.json")
},
exclude: /node_modules/
}
]
},
plugins: [
new CopyPlugin({
patterns: [
... // not important for question
]
})
],
optimization: {
minimize: process.env.NODE_ENV === "production",
minimizer: [
(compiler) => {
const TerserPlugin = require("terser-webpack-plugin");
new TerserPlugin({
terserOptions: {
ecma: 5,
mangle: true,
module: false
}
}).apply(compiler);
}
],
usedExports: true,
sideEffects: true,
innerGraph: true
},
stats: {
usedExports: true,
providedExports: true,
env: true
},
resolve: {
extensions: [".ts"]
},
output: {
filename: "index.js",
path: path.join(basePath, "dist"),
library: "colormaster",
libraryTarget: "umd",
globalObject: "this",
clean: true
}
};
Development Build Output
I see the following in the console:
...
./src/enums/colors.ts 17.6 KiB [built] [code generated]
[exports: HueColors, RGBExtended, WebSafe]
[only some exports used: HueColors] // ← indicates that tree shaking should occur in production build
webpack 5.46.0 compiled successfully in 2368 ms
I see the following in the generated dist folder output:
// dist/index.js → mode === development
/***/ "./src/enums/colors.ts":
/*!*****************************!*\
!*** ./src/enums/colors.ts ***!
\*****************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "HueColors": () => (/* binding */ HueColors)
/* harmony export */ });
/* unused harmony exports RGBExtended, WebSafe */ // ← indicates that tree shaking should occur in production
var HueColors;
(function (HueColors) {
HueColors["red"] = "hsl(0, 100%, 50%)";
...
HueColors["pink"] = "hsl(330, 100%, 50%)";
})(HueColors || (HueColors = {}));
var RGBExtended;
(function (RGBExtended) {
RGBExtended["maroon"] = "rgb(128,0,0)";
...
RGBExtended["white"] = "rgb(255,255,255)";
})(RGBExtended || (RGBExtended = {}));
var WebSafe;
(function (WebSafe) {
WebSafe["#000000"] = "rgb(0,0,0)";
...
WebSafe["#FFFFFF"] = "rgb(255,255,255)";
})(WebSafe || (WebSafe = {}));
Production Build Output
However, in the production build output, I see the following:
Which clearly still includes the unused exports.
What can be done to circumvent this issue?
Solution
Thanks to @Jeff Bowman's extensive response, we were able to deduce that the root cause was TypeScript compiling enum
into an IIFE.
Simply replacing enum
variable with const
(Record Utility) fixed the issue and Tree Shaking was visible in the production bundle.