Webpack 默认支持 ES2015 的模块定义,但是在进行代码转换的时候,像 const
这样的语法并不会被转换。这样的话,在一些老旧浏览器中就会产生很多问题。
为了能更好的理解 Webpack 的默认输出,我们将 Webpack 的 mode
参数设置为 none
, 然后观察一下构建输出:
dist/main.js
...
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((text = "Hello world") => {
const element = document.createElement("div");
element.className = "rounded bg-red-100 border max-w-md m-4 p-4";
element.innerHTML = text;
return element;
});
...
2
3
4
5
6
7
8
9
10
11
12
13
14
为了解决上面的语法转换的问题,我们可以借助于 Babelopen in new window. Babel 是一个 JavaScript 语法转译器,支持 ES2015 以及更新版本的各种特性。
在 Webpack 中使用 Babel
在 Webpack 中,我们可以通过 babel-loaderopen in new window 来使用 Babel。
首先安装 babel-loader.
npm add babel-loader @babel/core --develop
webpack.parts.js
const APP_SOURCE = path.join(__dirname, 'src');
exports.loadJavaScript = () => ({
module: {
rules: [{ test: /\.js$/, include: APP_SOURCE, use: 'babel-loader' }],
},
});
2
3
4
5
6
7
webpack.config.js
const commonConfig = merge([...parts.loadJavaScript()]);
设置完 babel-loader 的配置以后,我们还需要添加一个 babel 的配置,我们把配置放在 .babelrc
里面。
通常情况下,我们至少需要安装 @babel/preset-envopen in new window. 这个包是 Babel 的一个插件预置包,它通过 browserslist
中的定义来加载需要的插件。
browserslist 我们在前面已经讲过,这里不再赘述。
安装预置包,
npm add @babel/preset-env --develop
.babelrc 定义如下:
{
"presets": [["@babel/preset-env", { "modules": false }]]
}
2
3
这时候执行 npm run build -- --mode none
,然后查看 dist/main.js
的内容,你会发现,当 .browserslistrc
内的定义变化的时候,dist/main.js
也会发生变化.
我们将 .browserslistrc
中的定义修改为只包含 IE8,则输出的 dist/main.js
内容为:
...
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => __WEBPACK_DEFAULT_EXPORT__
/* harmony export */ });
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (function () {
var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "Hello world";
var element = document.createElement("div");
element.className = "rounded bg-red-100 border max-w-md m-4 p-4";
element.innerHTML = text;
return element;
});
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对比前面的 dist/main.js
输出,我们可以看到函数的语法发生了变化。
使用 Polyfill
@babel/preset-env
可以针对一些老旧浏览器填补语法功能的缺失。要使用这个功能,需要开启 useBuiltIns
参数并安装 core-jsopen in new window.如果代码中使用了 async
函数,那么还需要安装 regenerator-runtimeopen in new window 这个包。
除非我们使用 useBuiltIns: 'usage'
来配置 @babel/preset-env
, 否则我们需要在代码中显示的引入 core-js
或者在 Webpack 的 entry 中配置,app: ["core-js", "./src"]
。
注意,core-js 会污染全局变量,比如 Promise. 对于一个三方库开发者来说,引入 core-js 会引发许多问题。Babel 有一个插件 @babel/plugin-transform-runtimeopen in new window 可以很好的解决这个问题。
关于 Babel 的一些小提示
.babelrc
还有很多其他的选项可以使用,可以参考官网说明open in new window。.babelrc
支持 JSON5open in new window 格式,也就是可以在 .babelrc
中添加注释、使用单引号字符串等。
有时候如果你要使用一些新特性或者实验性的特性,如果项目是一个长期迭代的项目,那么注释是非常必要的。
Babel 的一些插件
Babel 有很多插件可以使用。
- babel-plugin-importopen in new window 可以重写模块的导入语法。比如在代码中可以直接写
import { Button } from "antd";
,而不是现实的使用Button
模块的路径。 - babel-plugin-transform-react-remove-prop-typesopen in new window 会在 production 构建中删除
propType
相关的代码。
在 NodeJS 中,我们可以通过 babel-registeropen in new window 或者 babel-cliopen in new window 来使用 Babel。
针对不同浏览器输出不同的构建结果
为了更好的利用现代浏览器对一些语法的支持,同时支持老旧浏览器,我们需要构建两份输出,然后在启动代码中根据浏览器加载不同的构建结果。这样的话,在现代浏览器中,加载的代码体积更小,同时解析时间也更短。
我们可以像下面这样,在 HTML 中使用不同的构建结果。
<!-- 支持 ES module 语法的加载这个文件 -->
<script type="module" src="main.mjs"></script>
<!-- 老浏览器加载这个文件 (同时支持 module 语法的浏览器知道不去加载这个文件(nomodule)). -->
<script nomodule src="main.es5.js"></script>
2
3
4
5
我们可以通过下面的方式配置 Webpack 输出两份构建结果。
.browserslistrc
# 支持老的 IE
[legacy]
IE 8
# 新浏览器,可以根据需要自行决定
[modern]
> 1% # Browser usage over 1%
2
3
4
5
6
7
webpack.config.js
// 记住在生产构建中设置 "mode": "production"
const getConfig = (mode) => {
switch (mode) {
case "prod:legacy":
process.env.BROWSERSLIST_ENV = "legacy";
return merge(commonConfig, productionConfig);
case "prod:modern":
process.env.BROWSERSLIST_ENV = "modern";
return merge(commonConfig, productionConfig);
...
default:
throw new Error(`Trying to use an unknown mode, ${mode}`);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
package.json
{
"scripts": {
"build": "wp --mode prod:legacy && wp --mode prod:modern"
}
}
2
3
4
5
上面的两次 Webpack 构建是串行执行,我们可以通过 concurrentlyopen in new window 并行支持 Webpack 构建。
使用 TypeScript
微软的 TypeScript 是一门编译型语言,使用模式与 Babel 类似。TypeScript 除了有 JavaScript 能力以外,还有类型定义。
在 Webpack 中,我们可以通过 ts-loaderopen in new window 来使用 TypeScript。在使用 ts-loader 的时候,我们可以仅仅使用它的代码编译功能,把类型检查能力通过编辑器来实现,或者通过 fork-ts-checker-webpack-pluginopen in new window 这个插件在另一个进程中实现。
我们也可以通过 @babel/plugin-transform-typescriptopen in new window 来使用 TypeScript。
Webpack 5 已经默认支持了 TypeScript。在使用 Webpack 5 的时候要确保项目中不要安装
@types/webpack
这个包,因为会有冲突。
有时候项目中的 TypeScript 配置有多个,比如使用 extends
属性 "extends": "./tsconfig.common"
配置了多个配置,这时候,我们可以通过 ts-loader 的 configFile
来指定一个具体配置文件。
使用 TypeScript 来编写 Webpack 配置文件
如果项目中使用 TypeScript,那么 Webpack 的配置文件也可以使用 TypeScript 来编写,webpack.config.ts。 Webpack 会自动检测和执行这个配置文件。
要让 TypeScript 编辑的配置文件能够正确执行,我们需要安装 ts-nodeopen in new window 和 ts-node-devopen in new window 这两个包,因为 Webpack 需要这两个包来执行配置文件。
默认情况下,在 Webpack watc 模式或者 webpack-dev-server 中,编译错误会导致构建失败。为了避免这个问题,我们可以使用下面的配置:
tsconfig.json
{ "ts-node": { "logError": true, "transpileOnly": true } }
logError
非常重要,因为没有这个选项的话,ts-node 在遇到错误的时候会直接退出。transpileOnly
只编译,不做类型检查。因为很多时候编辑器已经可以帮助我们做了类型检查。
WebAssembly
WebAssemblyopen in new window 让开发者在浏览器上运行一些非 JavaScript 代码,比如 C++。
wasm-simpleopen in new window 和 wasm-complexopen in new window 是两个官方示例。
总结
Webpack 默认支持 JavaScript,Babel 工具可以帮助我们根据不同浏览器定制构建输出。
Babel 给我们提供了针对不同浏览器定制转换代码的能力。@babel/preset-env 可以根据 .browserslistrc
的定义决定哪些特性要编译,需要使用哪些 polyfill.
借助于 Babel,我们可以使用还处在试验阶段的语言特性。Babel 有很多 preset 和 插件来定制使用。
我们可以根据不同环境定制不同的 Babel 能力。我们需要在不同环境中确保使用正确的 preset 和插件。
关注微信公众号,获取最新推送~
加微信,深入交流~