如何指定Webpack打包node_modules的ES5版本?解决IE11兼容问题
升级AJV.js到6.4版本后,我的vendor bundle中包含了uri-js的ESNEXT版本而非ES5版本,直接破坏了IE11的兼容性。
分析过程
AJV通过require('uri-js')引入该依赖,而uri-js在/node_modules/uri-js/dist/下明确提供了两个版本:
- es5
- esnext
但Webpack 4.8默认打包了esnext版本,我找不到指定优先使用ES5版本的配置方式。
相关配置文件
webpack.config.js
const path = require("path"); const webpack = require("webpack"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { entry: { app: './src/index.tsx' }, output: { filename: "js/[name].bundle.js", path: __dirname + "/dist" }, devtool: "source-map", resolve: { extensions: [".ts", ".tsx", ".js", ".jsx", ".json"] }, module: { rules: [ { test: /\.tsx?$/, loader: "ts-loader" }, { test: /\.less$/, use: ExtractTextPlugin.extract({ use: [{ loader: "css-loader", options: { localIdentName: '[local]--[hash:5]', sourceMap: true } }, { loader: "less-loader", options: { sourceMap: true } }], fallback: "style-loader", publicPath: "../" }), exclude: "/node_modules/" }, { test: /\.html$/, use: 'raw-loader' }, { test: /\.jpe?g$|\.gif$|\.png$/i, loader: "file-loader?name=assets/img/[name].[ext]" }, { test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/, use: "file-loader?name=assets/[name].[ext]" } ] }, plugins: [ new ExtractTextPlugin({ filename: "quino/style/style.css", allChunks: true }), new HtmlWebpackPlugin({ template: "src/index.html", filename: "index.html" }), new CopyWebpackPlugin([]) ], optimization: { splitChunks: { cacheGroups: { commons: { test: /[\\/]node_modules[\\/]/, name: "vendors", chunks: "all" } } } } };
package.json
{ "name": "MyApp", "version": "1.0.0", "sideEffects": false, "description": "-", "private": false, "scripts": { "build": "webpack --config webpack.config.js --mode production", "dev": "webpack-dev-server --config webpack.config.js --host 0.0.0.0 --progress --colors --history-api-fallback --mode development" }, "author": "Me", "license": "some", "devDependencies": { .... stuff .... }, "dependencies": { .... stuff .... "ajv": "^6.4.0", .... more stuff .... } }
tsconfig.json
{ "compilerOptions": { "outDir": "build/dist", "module": "esnext", "target": "es5", "lib": ["es6", "dom"], "sourceMap": true, "allowJs": true, "jsx": "react", "moduleResolution": "node", "rootDir": "src", "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "experimentalDecorators": true, "emitDecoratorMetadata": true }, "exclude": [ "dist/**", "./*.js", "node_modules", "build", "scripts", "acceptance-tests", "webpack", "jest", "src/setupTests.ts" ] }
我的核心疑问:
- 能否在webpack.config或package.json中指定优先使用node_modules里dist目录的ES5版本?
- node_modules下dist目录的版本选择逻辑是怎样的?
问题1:如何指定优先使用ES5版本
当然可以!这里有几种简单有效的方式:
方式1:配置Webpack别名(最直接)
在webpack.config.js的resolve对象里添加别名,直接把uri-js指向它的ES5入口文件:
resolve: { extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], alias: { // 强制Webpack使用uri-js的ES5版本 'uri-js': require.resolve('uri-js/dist/es5/uri.all.js') } }
这个方法相当于给Webpack硬指定了解析路径,完全不会有歧义,是我最推荐的方案。
方式2:利用项目package.json的browser字段
如果你的项目是纯浏览器环境,可以在自己的package.json里添加browser字段,指定该模块在浏览器下的替代入口:
{ "browser": { "uri-js": "uri-js/dist/es5/uri.all.js" } }
不过这个方法依赖Webpack对browser字段的默认支持,如果你修改过resolve.mainFields配置,需要注意优先级。
方式3:调整模块入口字段的优先级
Webpack解析模块时,会按resolve.mainFields的顺序读取模块package.json里的字段。默认浏览器环境的顺序是['browser', 'module', 'main'],而uri-js的module字段指向esnext版本,main字段指向ES5版本。
你可以修改这个顺序,把main放到module前面:
resolve: { extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], mainFields: ['browser', 'main', 'module'] }
这样Webpack会优先读取main字段对应的ES5文件,但要注意这个配置会影响所有模块的解析顺序,如果你其他模块需要优先用ES模块版本做tree-shaking,需要权衡。
问题2:node_modules下版本选择的逻辑
Webpack选择模块入口的核心逻辑围绕这几点:
- 模块package.json字段的优先级:默认浏览器环境下是
browser>module>main,Node.js环境是main>modulebrowser:专门为浏览器环境提供的入口,通常是ES5兼容版本module:指向ES6+的ES模块版本,方便tree-shaking优化main:指向CommonJS格式的版本,一般是ES5兼容的
- 你的Webpack配置:
resolve.alias会直接覆盖默认解析路径;resolve.mainFields会改变字段读取顺序 - 模块自身的结构:像uri-js这种在dist目录下分版本的,是作者主动提供了不同环境的构建产物,Webpack会根据上述规则匹配对应的入口
回到你的情况,uri-js的module字段指向了esnext版本,而Webpack默认优先读取module字段,所以才打包了不兼容IE11的版本。
另外补充:虽然你用TypeScript转译自己的代码到ES5,但Webpack默认不会转译node_modules里的代码,除非你修改module.rules的exclude配置。不过既然uri-js已经提供了现成的ES5版本,直接指定使用它是更高效的方案,没必要额外做转译。
内容的提问来源于stack exchange,提问作者Marc

