尝试配置webpack来编译项目

尝试配置webpack来编译项目

主要是之前的两个项目,后台管理那个用的react-cli,官网那个用的vue-cli,基本不用写到和webpack有关的配置。

(官网那个写了点,不过是在vue.config.js文件内进行编写的,文件内其实也有属性可以直接编写原生的webpack配置)。

本文主要简单写写webpack的一些常用的配置…,当然,这也是我第一次直接使用webpack来编译项目。

正文

webpack 英文官网

webpack 中文官网

可以选择直接在官网学习,英语 ok 的直接英文文档,像我这种英语半吊子的,两者结合着看,不懂翻一下,顺便学下英语…

不过还是强烈建议英文文档,中文文档有些地方更新不及时好像…

webpack可以理解为一个打包器,可以把分散在不同位置的资源(jscssjpgts等等)bundle成一个输出的文件,首页的图很好地表明了webpack的作用。

即使是顶破天的技术,归根结底基本是要编译成jshtmlcss以及其他的静态资源(static assets),因为浏览器就只认这个欸…

目前 webpack 的版本在 5(2020 年 10 月 10 号发布的),下面的链接为关于新版webpack的一些描述。

Webpack 5 release (2020-10-10)

似乎写的有点偏,ok,不多 bb,直接上代码就完事。

首先,需要建一个node的项目,使用yarn init来创建(其实就是初始化个package.json文件)。

创建完差不多是下面这样的(index.js文件是我自己添加的,现在是一个空的文件)。

现在我们全局装下webpack,使用yarn global add webpack就可以安装最新版的webpack了。

可以用webpack -v看下版本,如果第一次装的话,webpack还会提示你安装webpack-cli,按照提示安装就可以了。

接下来就正式开始编写代码了。

我们现在index.js写个简单的console.log输出。

接下来编写webpack的核心配置代码webpack.config.js

在编写配置代码之前,我们需要简单的理解 webpack 中的两个配置属性。

entry

An entry point indicates which module webpack should use to begin building out its internal dependency graph. webpack will figure out which other modules and libraries that entry point depends on (directly and indirectly).
By default its value is ./src/index.js, but you can specify a different (or multiple entry points) by setting an entry property in the webpack configuration. —— entry - webpack

一个入口节点表明webpack应该以哪个模块来开始建立它内部的依赖网,webpack会弄清楚入口节点依赖了哪些库或者模块(直接依赖或者间接依赖)
默认情况下的值为./src/index.js(也就是当前文件夹下的src目录下的index.js文件),但你也可以在webpack配置中通过设置 entry 属性来指定一个不同的(或者多个)入口点。

简单点讲就是指定入口的文件,webpack就从这个文件开始,把这个文件依赖的其他文件打包进来,而其他文件可能又依赖了其他的文件,那么就会递归这个过程。

output

The output property tells webpack where to emit the bundles it creates and how to name these files. It defaults to ./dist/main.js for the main output file and to the ./dist folder for any other generated file. —— output - webpack

output 属性告诉webpack在哪输出它创建的打包文件以及如何对这些文件命名。默认值为./dist/main.js为主要的输出文件,./dist文件夹存放其他任何生成的文件。

结合上面这两个,可以写出一个简单的webpack.config.js文件。

1
2
3
4
5
6
7
8
9
10
11
12
const path = require("path");

module.exports = {
// 入口
entry: "./index.js",
output: {
// 生成文件存放的文件夹
path: path.resolve(__dirname, "dist"),
// 主要输出的js文件
filename: "bundle.js",
},
};

然后我们可以在package.jsonscript下新增一条命令。

1
2
3
4
5
6
{
// ...其他的配置
"scripts": {
"build": "webpack -c webpack.config.js"
}
}

然后通过yarn build来执行这条命令。

可以发现打包成功了,没有出现什么错误,并且多了一个dist文件夹,里面有一个bundle.js

生成的bundle.js内容如下:

1
console.log("Hello World!");

webpack在默认情况下是production(生产)模式,这个模式是由配置中的mode指定的。

可以查看该页面 mode - webpack

可以修改配置文件为。

1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require("path");

module.exports = {
mode: "development",
// 入口
entry: "./index.js",
output: {
// 生成文件存放的文件夹
path: path.resolve(__dirname, "dist"),
// 主要输出的js文件
filename: "bundle.js",
},
};

输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is not neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => {
// webpackBootstrap
/*!******************!*\
!*** ./index.js ***!
\******************/
/*! unknown exports (runtime-defined) */
/*! runtime requirements: */
eval(
'console.log("Hello World!");\r\n\n\n//# sourceURL=webpack://webpack-test/./index.js?'
);
/******/
})();

可以发现eval中出现了"console.log("Hello World!");这句代码,这句代码对应了我们index.js的内容。

在顶部的多行注释中,提到了The "eval" devtool has been used (maybe by default in mode: "development").,也就是在开发环境下会使用eval这个开发工具。

在开发环境下webpack会默认启动一些配置,如下(这个表格可以在上面mode的网址里面找到,对应也有生产mode下默认的配置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- devtool: 'eval',
- cache: true,
- performance: {
- hints: false
- },
- output: {
- pathinfo: true
- },
- optimization: {
- moduleIds: 'named',
- chunkIds: 'named',
- mangleExports: false,
- nodeEnv: 'development',
- flagIncludedChunks: false,
- occurrenceOrder: false,
- concatenateModules: false,
- splitChunks: {
- hidePathInfo: false,
- minSize: 10000,
- maxAsyncRequests: Infinity,
- maxInitialRequests: Infinity,
- },
- emitOnErrors: true,
- checkWasmTypes: false,
- minimize: false,
- removeAvailableModules: false
- },
- plugins: [
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}

回到生产环境下的打包,可以发现生成的文件非常的简单,简答地让人怀疑是不是没打包…,为了看出打包的效果,可以稍稍写多点代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log("Hello World!");

const el = document.getElementById("app");

el.onclick = function () {
const array = [1, 3, 4, 2, 1, 4];
array.sort();
console.log(array);
};

let a = 1;

let b = 2;

console.log(a + b);

打包之后如下

1
2
3
4
5
6
console.log("Hello World!"),
(document.getElementById("app").onclick = function () {
const o = [1, 3, 4, 2, 1, 4];
o.sort(), console.log(o);
}),
console.log(3);

可以发现缩进和没必要的空格全部没有了,并且也优化了变量,像el就被省略了,由于变量ab都没被使用,输出直接生成了一个静态的语句console.log(3),省略了这两个变量。

除了js文件,webpack也支持导入其他的文件。

1
2
3
// 导入样式
import style from "./css/style.css";
// ...其他代码

当然这样子直接编译是会报错的。

可以看到错误信息中有一句Module parse failed,也就是模块解析错误。

下一句是You may need an appropriate loader to handler this file type ...,也就是提示我们需要一个适合的loader来处理这个文件。

那么就要了解webpack中的loader了。

loader

webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.

webpack使用各种 loader 来预处理文件,这允许你在JavaScript上打包任何静态的资源。

所以我们需要一个 loader 来处理css文件,使得css文件转成 js 能够理解的代码。

这个 loader 就是css-loader

先通过yarn add css-loader -D来安装css-loader开发依赖,-D安装在开发依赖里面。

修改webpack的配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const path = require("path");

module.exports = {
mode: "development",
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
// 验证规则,这里就是取到css文件,i(ignore)标志不区分大小写
test: /\.css/i,
// 对这些css文件使用css-loader预处理,这里是一个数组,也就是可以使用多个loader
use: ["css-loader"],
},
],
},
};

然后我们输出style这个变量看看是什么样子。

1
2
3
import style from "./css/style.css";

console.log(style);

我们在dist目录下创建一个html文件来引用这个js文件,然后看看控制台的输出。

可以看到导出了一个数组,也可以理解为css文件转换成了下面的js文件。

1
2
3
4
5
6
7
8
9
10
11
const o = [["./css/style.css", ".text {↵  font-size: 18px;↵}", ""]];

o.i = function (modules, mediaQuery, dedup) {
// ...
};

o.toString = function () {
// ...
};

export default o;

在使用 Vue 的时候,可能会发现开发环境下每个组件的样式会通过style标签来插入到DOM中,这就使用到了另一个loader - style-loader

可以安装这个loader,运行yarn add style-loader -D

然后修改配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const path = require("path");

module.exports = {
mode: "development",
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
// 验证规则,这里就是取到css文件,i(ignore)标志不区分大小写
test: /\.css/i,
// 对这些css文件使用css-loader预处理,这里是一个数组,也就是可以使用多个loader
use: ["style-loader", "css-loader"],
},
],
},
};

然后我们运行打包,打开网页会发现在head中插入了对应的样式,并且style变量为一个空的对象。

这里可能有疑问的就是loader的执行顺序问题。

官方在Loader - Using Loader - Configuration一节的文档中明确指出,loader是从右往左进行执行的。

Loaders are evaluated/executed from right to left (or from bottom to top).

Loader Features一节指出了loader的特性。

每个loader也有自己的配置,在use中,我们可以以一个对象来表示一个loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const path = require("path");

module.exports = {
// ...
module: {
rules: [
{
// 验证规则,这里就是取到css文件,i(ignore)标志不区分大小写
test: /\.css/i,
// 对这些css文件使用css-loader预处理,这里是一个数组,也就是可以使用多个loader
use: [{ loader: "style-loader" }, { loader: "css-loader" }],
},
],
},
};

style-loader,除了通过style标签注入之外,也有其他的一些注入方式。

style-loader的仓库地址webpack-contrib/style-loader

README中,也为我们写明了这些配置。

其中injectType为配置注入的方式。

可以通过每个loader对象的options来配置,这里使用了singletonStyleTag,也就是只插入一个单独的style标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require("path");

module.exports = {
// ...
module: {
rules: [
{
test: /\.css/i,
use: [
{
loader: "style-loader",
options: {
injectType: "singletonStyleTag",
},
},
{ loader: "css-loader" },
],
},
],
},
};

然后写另一个 css 文件

1
2
3
4
5
import style from "./css/style.css";
import another from "./css/another.css";

console.log(style);
console.log(another);

然后打包,打开浏览器,发现head中只有一个style标签了。

如果不加情况下(默认为styleTag),那么会是两个style标签,如下:

到现在为止看起来很不错,webpack 发挥了他的功能,也就是打包

不过还有一点不够智能,我们希望html文件也可以自动的生成,那就非常完美了,我们只需在开启一个本地的服务器来绑定dist这个目录,然后每一次打包之后就可以直接打开浏览器就可以看到结果。

这就需要用到webpack的另一个配置plugin(插件)了

plugin

Plugins - webpack

While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables.

loader 用于转化某些模块的类型,而 plugin 被用来执行一些更广泛的任务,比如打包优化,资源管理以及环境变量的注入。

可以简单理解为plugin的功能更加的强大,可以完成一些做不到的事情,而loader主要的功能就是转换文件类型。

像为了可以自动生成一个HTML文件,可以使用HtmlWebpackPlugin

插件也要安装对应的npm包,执行yarn add html-webpack-plugin -D来安装插件。

然后修改配置文件,新增plugins属性。

1
2
3
4
5
6
7
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
// ...
plugins: [new HtmlWebpackPlugin()],
};

不过这样子编译的话会出现错误,原因应该是插件需要用到项目的webpack,而不能使用全局的webpack

这里解决办法可以装个项目的webpack依赖,yarn add webpack -D

或者链接下全局的webpackyarn link webpack -D

这里我使用的是后面的方法。

注意,在执行yarn link webpack -D,需要在webpack的包下面执行yarn link来注册webpack

接着我们删除dist文件夹,然后打包。

发现打包成功了,而且也自动生成了HTML文件,并且引用了生成的bundle.js