实战一
准备本篇的环境
虽然可以仅展示核心代码,但笔者认为在一个完整的环境中边看边做,举一反三,效果更佳。
这里的环境其实就是初步认识 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" >
再次启动服务,在打开的浏览器页面中发现 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" >
。
如果再次将 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 的兼容处理完毕。