其他章节请看:

​webpack 快速入门 系列​


实战一

准备本篇的环境

虽然可以仅展示核心代码,但笔者认为在一个完整的环境中边看边做,举一反三,效果更佳。

这里的环境其实就是​​初步认识 webpack​​一文完整的示例,包含 webpack、devServer、处理css、生成 html。

项目结构如下:

webpack-example2        
- src // 项目源码
- a.css
- b.js
- c.js
- index.html // 页面模板
- index.js // 入口
- package.json // 存放了项目依赖的包
- webpack.config.js // webpack配置文件

src中的代码如下:

// a.css
body{color:blue;}

// b.js
import './c.js'
console.log('moduleB')
console.log('b2')

// c.js
console.log('moduleC')

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=`, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>请查看控制台</p>
</body>
</html>

// index.js
import './b.js'
import './a.css'
console.log('moduleA')

package.json:

{
"name": "webpack-example2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack-dev-server"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^5.2.4",
"html-webpack-plugin": "^4.5.2",
"style-loader": "^2.0.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}

webpack.config.js:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
],
mode: 'development',
devServer: {
open: true,
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
},
};

在 webpack-example2 目录下运行项目:

// 安装项目依赖的包
> npm i
// 启动服务
> npm run dev

启动服务器后,浏览器会自动打开页面,如果看到蓝色文字”请查看控制台“,说明环境已准备就绪。

打包样式

处理 css 和 less

less 是一种 css 预处理语言,在 webpack 中要处理 less 需要使用 ​​less-loader​​,用于将 less 转为 css。

首先安装依赖,然后修改配置文件:

// 安装包。版本8安装失败,所以降了一个版本
> npm i -D less-loader@7

// webpack.config.js
// 增加对 less 文件处理的loader
rules: [
// 需要保留,否则识别不了 css 文件
{
test: /\.css$/i,
use: ["style-loader", "css-loader"]
},
// +
{
test: /\.less$/i,
loader: [
// compiles Less to CSS
"style-loader",
"css-loader",
"less-loader",
],
},
],

然后增加 a.less 文件,在 index.js 中引入 a.less,重新启动服务进行测试:

// src/a.less
body{
p{
color:pink;
}
}

// index.js
import './b.js'
import './a.css'
// +
import './a.less'
console.log('moduleA')

// 启动服务
> npm run dev

在新开的页面中,看到粉色文字”请查看控制台“,说明 less 处理成功。

提取 css 成单独文件

通过浏览器我们发现现在 css 是嵌在页面内的,就像这样:

<head>
...
<style>body{color:blue;}</style>
<style>body p {
color: pink;
}
</style>
</head>

通常我们会通过 link 来引入 css 文件,所以接下来就将 css 取成单独的文件。这里需要使用 ​​mini-css-extract-plugin​​ 这个包。

我们只需要安装依赖包,修改配置文件即可:

// 安装依赖包 
> npm i -D mini-css-extract-plugin@1
// 不在需要 style-loader,卸载
> npm r -D style-loader

// webpack.config.js
// +
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
module: {
rules: [
// 修改规则
{
test: /\.css$/i,
// 将 style-loader 改为 MiniCssExtractPlugin.loader
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.less$/i,
loader: [
// 将 style-loader 改为 MiniCssExtractPlugin.loader
MiniCssExtractPlugin.loader,
"css-loader",
"less-loader",
],
},
],
},
plugins: [
// +
new MiniCssExtractPlugin(),
...
],

};

启动服务(​​npm run dev​​),在打开的页面中可以看到 css 已经改为 link 的方式引入,就是这样:

// 从 style 改为 link 方式
<link href="main.css" rel="stylesheet">

// 通过网络查看 main.css 的内容是:
body{color:blue;}
body p {
color: pink;
}

由于我们对 css 和 less 都使用了 MiniCssExtractPlugin.loader,所以 a.css 和 a.less 都被提取到 main.css 中。

Tip​:如果通过​​npm run build​​打包,则可以看到 dist/main.css 文件。

使用 PostCSS

​PostCSS​​ - 使用JavaScript转换CSS的工具。

可以将 postcss 当作一个平台,下面我们通过 postcss 做两件事:

  • 增加代码可读性(或增加前缀)
:fullscreen {
}

// 转为

:-webkit-full-screen {
}
:-ms-fullscreen {
}
:fullscreen {
}
  • 立即使用明天的CSS
body {
color: lch(53 105 40);
}

// 转为

body {
color: rgb(250, 0, 4);
}

webpack 可以通过 ​​postcss-loader​​ 来使用 postcss。

由于 postcss 只是一个平台,具体功能需要通过插件来实现,这里我们使用 ​​postcss-preset-env​​。

postcss-preset-env 可以将现代CSS转换为大多数浏览器可以理解的内容,并根据目标浏览器或运行时环境确定所需的polyfill。而且它包含自动前缀。

首先安装相关依赖,并修改配置文件:

> npm i -D postcss-loader@4 postcss-preset-env@6

// webpack.config.js
// +
const postcssPresetEnv = require('postcss-preset-env');
// +
const postcssLoader = {
loader: 'postcss-loader',
options: {
// postcss 只是个平台,具体功能需要使用插件
// Set PostCSS options and plugins
postcssOptions:{
plugins:[
// 配置插件 postcss-preset-env
[
"postcss-preset-env",
{
// 自动前缀。默认是true
// autoprefixer: true,
// 根据您所支持的浏览器来确定需要哪些polyfill。这里仅做演示
browsers: 'ie >= 8, chrome > 10',
// stage 默认是 2
// stage:2
},
],
]
}
}
}

module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
// + 放在css-loader后面
postcssLoader
]
},
{
test: /\.less$/i,
loader: [
MiniCssExtractPlugin.loader,
"css-loader",
// +
postcssLoader,
"less-loader",
],
},
]
},
};

接着修改 a.css 和 a.less,重新启动服务器:

// a.css
body{
color: lch(53 105 40);
}

// a.less
body{
p{
transform: scale(1, 2);
}
}

// 启动服务
> npm run dev

在新开的页面中,我们看到红色文字”请查看控制台“,而且文字纵向拉长了一倍。通过浏览器查看 main.css 源码如下:

body{
color: rgb(250, 0, 4);
}
body p {
-webkit-transform: scale(1, 2);
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}

至此,增加前缀以及立即使用明天的CSS都已经完成。

Tip​:stage(阶段)可以是0(实验)到4(稳定),默认是2,如果我们改为3或4,重新打包,​​lch(53 105 40);​​​则不会转为 ​​rgb(250, 0, 4)​​;将plugins换成下面的写法效果相同。

plugins:[
postcssPresetEnv({
browsers: 'ie >= 8, chrome > 10',
})
]

postcss-preset-env 支持任何标准的 browserslist 配置,可以是 .browserslistrc 文件,package.json 中的browserslist 键或 browserslist 环境变量。

如果将 ​​browsers: 'ie >= 8, chrome > 10',​​​ 注释,browsers 将使用默认的 browserslist 查询(即​​> 0.5%, last 2 versions, Firefox ESR, not dead​​),重新构建,则不会添加前缀。

如果不想在 browsers 中写,在 package.json 中的 browserslist 中配置也是可以的:

// package.json
{
...
"browserslist": [
"ie >= 8",
"chrome > 10"
]
}

​:package.json 不能写注释,本文在 package.json 中的注释仅作说明。

如果觉得 package.json 写的内容太多,我们甚至可以将这部分提取到一个单独的文件中来写:

// .browserslistrc
// from github browserslist
# Browsers that we support

ie >= 8
chrome > 10

最后,如果我们针对开发环境和生成环境做不同的处理,比如开发环境支持 ie8+,而生产环境支持 chrom10+,我们可以这么写:

// .browserslistrc
# Browsers that we support

[production]
chrome > 10

[development]
ie >= 8

然后在配置文件中通过 process.env 来指定环境:

// +
// process.env属性返回一个包含用户环境的对象
process.env.NODE_ENV = 'development' // or production

Browserslist将根据BROWSERSLIST_ENV或NODE_ENV变量选择,所以设置 ​​process.env.BROWSERSLIST_ENV​​ 也是可以的。

再次打包 ​​npm run build​​,则只会针对 ie,生成的 main.css 内容如下:

body{
color: rgb(250, 0, 4);
}
body p {
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}

压缩 css

如果我们需要压缩 css 代码,可以使用 ​​optimize-css-assets-webpack-plugin​​,用于优化或最小化 css。

首先安装依赖,然后修改配置:

> npm i -D optimize-css-assets-webpack-plugin@5

// webpack.config.js
// +
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new MiniCssExtractPlugin(),
// +
new OptimizeCssAssetsPlugin()
],

重新打包,原来的 main.css 则变成一行,请看:

> npm run build

// main.css(优化前)
body{
/* 注释 */
color: rgb(250, 0, 4);
}
body p {
-ms-transform: scale(1, 2);
transform: scale(1, 2);
}

// main.css(优化后)
body{color:#fa0004}body p{-ms-transform:scaleY(2);transform:scaleY(2)}

优化后,css 变成了一行,注释也删除了。

打包图片

前端资源通常有图片,由于 webpack 只识别 javascript,所以需要 loader 来帮们识别图片。

我们使用 ​​url-loader​​,能将图片转为 base64。

首先安装依赖,并修改配置文件:

> npm i -D url-loader@4

// webpack.config.js
module: {
rules: [
...
// +
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
// 指定文件的最大大小(以字节为单位)
limit: 1024*7,
},
},
],
},
]
},

接着引入图片,启动服务:

// 引入图片。src/6.68kb.png

// a.less
body{
p{
transform: scale(1, 2);
}
.m-box{display:block;width:100px;height:100px;}
.img-from-less{background:url(./6.68kb.png) no-repeat;background-size:100% 100%;}
}

// index.html
...
<body>
<p>请查看控制台</p>
<span class='m-box img-from-less'></span>
</body>
...

// 启动服务
> npm run dev

Tip​: 笔者的图片大小为 6.68kb,上面的 limit 只需要大于6.68kb即可

在新开的页面中,我们在”请查看控制台“文字下面看见了我们设置的图片。通过检查元素会发现这张图片是 base64。

body .img-from-less{
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA... no-repeat;
...
}

如果将 ​​limit: 1024*7​​​ 修改为 ​​limit: 1024*6​​​(也就是将 limit 设置的比图片的 size 更小),再次运行 ​​npm run dev​​,会发现报错了。还会提示找不到 file-loader。这是因为这张图片(6.68kb.png)大于 1024*6,所以就不会被打包成 base64,所以需要 file-loader 来处理。

安装依赖包 ​​npm i -D file-loader@6​​,再次启动服务器,页面上又看到我们的图片,而且这次不再是 base64,而是直接生成了一张图片。

body .img-from-less{
background: url(26bd867dd65e26dbc77d1e151ffd36e0.png) no-repeat;
...
}

图片除了在 css 中使用,我们也会通过 img 元素引用,于是我们在 index.html 中新增 ​​<img class='m-box' src="./6.68kb.png" alt="">​​ 再次启动服务,在打开的浏览器页面中发现 img 引用的图片没生效,而且源码也没变化。

这里需要使用 html-loader 这个包,它能让每个被加载的属性(例如:​​<img src="image.png"​​)能被引入(imported)。

安装依赖包,修改配置:

> npm i -D html-loader@1

// webpack.config.js
module: {
rules: [
...
// +
{
test: /\.html$/i,
loader: 'html-loader',
},
]
},

再次启动服务,就能看到两张一样的图片了。img 的代码变为 ​​<img class="m-box" src="26bd867dd65e26dbc77d1e151ffd36e0.png" alt="">​​。

如果再次将 ​​limit: 1024*6​​​ 修改为 ​​limit: 1024*7​​,启动服务你会发现这两处图片都变为 base64。

打包 javascript

js 语法检查

有时我们希望团队成员写的 javascript 代码风格一致。

我们可以使用 eslint,它能查找并修复JavaScript代码中的问题;可以自定义 eslint,使其完全按照项目所需的方式工作。代码风格,笔者选用 airbnb,一个流行的 javascript 风格指南(此刻是第 6 名(topics javascript))。

在 webpack 中使用 eslint,需要使用 ​​eslint-webpack-plugin​​​( eslint-loader废弃了),而 eslint-webpack-plugin 依赖于 ​​eslint​​;

eslint-config-airbnb 默认导出包含我们所有的ESLint规则,包括ECMAScript 6+和React,而 我们不需要使用 react,所以使用 ​​eslint-config-airbnb-base​​ 即可。

首先安装依赖包,修改配置:

// 没有引入 eslint-plugin-import
> npm i -D eslint@7 eslint-webpack-plugin@2 eslint-config-airbnb-base@14

// webpack.config.js
// +
const ESLintPlugin = require('eslint-webpack-plugin');

module.exports = {
// ...
plugins: [
new ESLintPlugin({
// 将启用ESLint自动修复功能。此选项将更改源文件
fix: true
})

],
// ...
};

// package.json
{
// +
"eslintConfig": {
"extends": "airbnb-base"
}
}

重新打包 ​​npm run build​​,出现了一些警告和错误,核心信息如下:

WARNING in 

webpack-example2\src\index.js
6:1 warning Unexpected console statement no-console

6 problems (2 errors, 4 warnings)

ERROR in

webpack-example2\src\index.js
1:8 error Unexpected use of file extension "js" for "./b.js" import/extensions

5 problems (2 errors, 3 warnings)

错误(import/extensions)是不希望使用 js 扩展名,将 ​​./b.js​​​ 改为 ​​./b​​​ 就好了,可参考issues:import/extensions。

警告(no-console)是因为不能出现 console.log。可以通过配置将这个告警关闭:

// package.json
{
"eslintConfig": {
"extends": "airbnb-base",
// +
"rules": {
"no-console": "off",
}
}
}

将​​import/extensions​​​修复,并将警告关闭,重新打包 ​​npm run build​​则不会出现警告和错误。

Tip​:打包后,源码也会自动修复,比如 src/index.js 中的 sum() 方法,​​a,​​ 后面是多个空格,打包后会合并成一个空格:

function sum(a,    b) {
return a + b;
}
// 需要调用 sum() 方法
// 否则报错:error 'sum' is defined but never used no-unused-vars
console.log(sum(1, 100));

// 修复后

function sum(a, b) {
return a + b;
}

如果在 js 文件中使用 window ,再次打包会报错,就像这样:

// index.js
// +
setTimeout(() => {
window.location = 'https://www.baidu.com/';
}, 1000);

// 打包
> npm run build
...
error 'window' is not defined no-undef

可以在配置文件中指定环境来解决这个问题。就像这样:

// package.json
"eslintConfig": {
// +
"env": {
"browser": true
}
}

如果不想写到 package.json,也可以配置到单独的文件(.eslintrc.js)中:

// .eslintrc.js
module.exports = {
"extends": "airbnb-base",
"rules": {
"no-console": "off"
},
"env": {
"browser": true
}
}

js 兼容性处理

我们想使用 es6 来编写代码,但有的浏览器支持的不够全面,所以我们会将 es6 转成 es5。

接着上面的例子进行,重写 index.js,放入一个箭头函数,再次打包,你会发现 webpack 不会对 es6 语法做处理,const 还是 const,而不是 var:

// src/index.js
const sum = (a, b) => (a + b);
console.log(sum(1, 10));

// sum 还是我们的箭头函数
eval("const sum = (a, b) => (a + b);\nconsole.log(sum(1, 10));\n\n\n//# sourceURL=webpack:///./src/index.js?");

Babel 是一个 JavaScript 编译器。通过它可以让我们使用下一代的 JavaScript 语法编程。

在 webpack 中要使用 babel 就得用 ​​label-loader​​。用法(Usage)如下:

// 安装依赖包。没有使用 @babel/core
> npm i -D babel-loader@8 @babel/preset-env@7

// webpack.config.js
module: {
rules: [
// +
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
}

重新打包,箭头函数就变成普通函数:

eval("var sum = function sum(a, b) {\n  return a + b;\n};\n\nconsole.log(sum(1, 10));\n\n//# sourceURL=webpack:///./src/index.js?");

如果我们在 js 中使用 Promise,重新打包后 Promise 还是 Promise,而且在不识别 Promise 语法的浏览器中(比如 ie11)运行会报错。

// index.js
Promise.resolve('aaron').then((v) => {
console.log(v);
});

// 重新打包,Promise 还是 Promise
eval("Promise.resolve('aaron').then(function (v) {\n console.log(v);\n});\n\n//# sourceURL=webpack:///./src/index.js?");

babel 官网提到,使用@babel/polyfill,就可以使用新的内置函数(例如Promise或WeakMap),静态方法(例如Array.from或Object.assign),实例方法(例如Array.prototype.includes)等等。所以这个 polyfill 是我们的解决方案。

但是 @babel/polyfill 废弃了。而 @babel/polyfill 包含 regenerator runtime 和 core-js。

core-js,包括适用于2021年前ECMAScript的polyfill,而且仅加载必需的功能。

在 useBuiltIns 参数中也提到:由于在7.4.0中已弃用@ babel/polyfill,因此我们建议直接添加core-js并通过corejs选项设置版本。

于是我们知道 core-js 能解决 Promise 这类问题。

如何使用 core-js ?我们先来介绍一下​插件​和​预设​。

babel 通过将插件(或预设)应用于配置文件来启用Babel的代码转换。比如插件列表​中的​​es2015​​,这是一个集合,包含了箭头函数(arrow-functions)、类(classes)等插件;

而预设(presets)其实是多个插件(plugin)的集合。比如 @babel/preset-env 这种预设则包含了 es2015、es2016、es2017等最新的插件。

最后根据​​babel-preset-env​​中的介绍,我们将 core-js 应用上:

// 安装依赖
> npm i -D core-js@3.11

// webpack.config.js
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
// +
{
// 配置处理polyfill的方式
useBuiltIns: "usage",
// 版本与我们下载的版本保持一致
corejs: { version: "3.11"},
"targets": "> 0.25%, not dead"
}
]
]
}
}

重新打包,dist/main.js 的Size变成 109 Kib,而之前还不到 4kiB。

启动服务,在不支持 Promise 语法的浏览器中,比如 ie11,也能在控制台输入 aaron。

至此 javascript 的兼容处理完毕。


其他章节请看:

​webpack 快速入门 系列​



作者:​​彭加李​

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。