一、前端发展的阶段

到Web前端的发展是非常快速的,对于开发者来说我们会更加深有体会

  • 从后端渲染的JSP、PHP,到前端原生JavaScript,再到jQuery开发,再到目前的三大框架Vue、React、Angular;
  • 开发方式也从原来的JavaScript的ES5语法,到ES6、7、8、9、10,到TypeScript,包括编写CSS的预处理器less、scss等;

二、前端开发的复杂化

开发过程中我们需要通过模块化的方式来开发;

  • 比如也会使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑, 通过sass、less等方式来编写css样式代码;
  • 比如开发过程中,我们还希望试试的监听文件的变化来并且反映到浏览器上,提高开发的效率;
  • 比如开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化;
  • Vue-CLI、create-react-app、Angular-CLI都是基于webpack来帮助我们支持模块化、less、TypeScript、打包优化等的

三、webpack是什么

webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序;

我们来对上面的解释进行拆解:

  • 打包bundler:webpack可以将帮助我们进行打包,所以它是一个打包工具
  • 静态的static:这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器);
  • 模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等;
  • 现代的modern:我们前端说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展;

日常工作来说,比如在开发vue、react、 angular等项目的过程中我们需要一些特殊的配置:

  • 比如给某些目录结构起别名
  • 让我们的项目支持sass、less等预处理器
  • 希望在项目中手动的添加 TypeScript的支持
  • 都需要对webpack 进行一些特殊的配置工作。

在原有的脚手架上来定制一些自己的特殊配置提供性能:

  • 比如安装性能分析工具、
  • 使用gzip压缩代码、引用cdn的资源,公共代码抽取等等操作
  • 甚至包括需要编写属于自己的loader和plugin。

对于想要在前端领域进阶成为高级前端开发工程师来说:

  • webpack等构建工具是必须学习的, 包括其中的一些高级特性和原理,都是要熟练掌握的。
  • 企业在招聘高级前端工程师 或者架构师时,必然会对webpack和其他 的构建工具有比较高的要求。

四、webpack的安装

webpack的安装目前分为两个:webpack、webpack-cli

那么它们是什么关系呢?

  • 执行webpack命令,会执行node_modules下的.bin目录下的webpack;
  • webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
  • 而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;
  • 所以在安装webpack时,我们需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于 自己的vue-service-cli的东西)
1
2
npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装

五、传统开发的问题

传统开发我们的代码存在什么问题呢?某些语法浏览器是不认识的(尤其在低版本浏览器上)

  1. 使用了ES6的语法,比如const、箭头函数等语法;
  2. 使用了ES6中的模块化语法;
  3. 使用CommonJS的模块化语法;
  4. 在通过script标签引入时,必须添加上 type=”module” 属性;

上面存在的问题,让我们在发布静态资源时,是不能直接发布的,因为运行在用户浏览器必然会存在各种各样的兼容性问题。

我们需要通过某个工具对其进行打包,让其转换成浏览器可以直接识别的语法;

比如此时我新建文件如下:

index.js:

1
2
3
4
import { message } from './math.js';
console.log(message);
const sum = require('sum');
sum(1, 2);

math.js

1
2
3
4
5
function sum(n1, n2) {
return n1 + n2;
}
export const message="Hello"
module.exports={sum}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./src/index.js" type="module"></script>
</body>
</html>

此时浏览器报错:

因为浏览器不认识CommonJS的语法,无法进行解析编译。

这就是传统开发不借用打包工具存在的问题。

六、webpack默认打包

可以通过webpack进行打包,之后运行打包之后的代码

在目录下直接执行 webpack 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
E:\project\vs  project\webpack\01_webpack初体验>webpack
asset main.js 891 bytes [emitted] [minimized] (name: main)
runtime modules 1020 bytes 4 modules
cacheable modules 204 bytes
./src/index.js 108 bytes [built] [code generated]
./src/math.js 96 bytes [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.52.1 compiled with 1 warning in 284 ms

注意此时的webpack是全局下的webpack,局部我还没有安装

打包后会 生成一个dist文件夹,里面存放一个main.js的文件,就是我们打包之后的文件:

dist -> main.js:

1
(()=>{"use strict";var e,r={52:(e,r,o)=>{o.r(r),o.d(r,{message:()=>t}),e=o.hmd(e);const t="Hello";e.exports={sum:function(e,r){return e+r}}}},o={};function t(e){var n=o[e];if(void 0!==n)return n.exports;var s=o[e]={id:e,loaded:!1,exports:{}};return r[e](s,s.exports,t),s.loaded=!0,s.exports}t.d=(e,r)=>{for(var o in r)t.o(r,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:r[o]})},t.hmd=e=>((e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:()=>{throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e),t.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},e=t(52),console.log(e.message),t(52)(1,2)})();

这个文件中的代码被压缩和丑化了; 暂时不关心他是如何做到的,后续讲webpack实现模块化原理时会再次讲到;

另外发现代码中依然存在ES6的语法,比如箭头函数、const等,这是因为默认情况下webpack并不清楚我们打包后的文件是否需要转成ES5之前的语法,后续需要通过babel来进行转换和设置

我们发现是可以正常进行打包的,但是有一个问题,webpack是如何确定我们的入口的呢?

  • 事实上,当我们运行webpack时,webpack会查找当前目录下的 src/index.js作为入口;
  • 所以,如果当前项目中没有存在src/index.js文件,那么会报错;

七、本地安装webpack

我们不能一直使用全局下的webpack

因为我们使用全局webpack的版本可能很高,比如v5,而别人本地的webpack版本是v3

那么别人再拉取到项目并执行打包命令时,就可能会引发很多问题

所以我们需要再本地也就是局部安装webpack,webpack-cli,这样就可以在package.json中进行记录

在别人拉取项目时,执行npm install,就会安装和我们一样的版本的webpack,避免了很多问题

首先npm init生成package.json文件

npm init

1
2
3
4
5
6
7
8
9
10
11
{
"name": "01_webpack_learn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

本地安装webpack webpack-cli

1
npm install webpack webpack-cli –D

注意:安装以后执行webpack也会去执行全局下的webpack

解决办法:

  1. npx webpack 这就会去局部的node_modules下的bin目录下去寻找webpack打包文件
  2. 在package.json中配置脚本
1
2
3
4
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},

这样执行npm run build,就会默认去局部node_modules下的bin目录下的webpack进行打包

八、webpack配置文件

  • 前面提到当我们运行webpack时,webpack会查找当前目录下的 src/index.js作为入口;
  • 所以,如果当前项目中没有存在src/index.js文件,那么会报错;

修改入口出口文件配置

1
npx webpack --entry ./src/main.js --output-path ./build

指定入口文件为src目录下main.js

指定出口文件为 ./build文件夹

可以放到脚本里来简化

1
2
3
4
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --entry ./src/main.js --output-path ./build"
},

更多相关API可以参考官网document文档。

webpack.config.js

不能一直在命令行里写相关命令

应该新建webpack.confin.js文件进行统一配置

webpack在进行打包时会看目录下是否含有webpack.config.js这个文件

  • 有的话会按照这个文件里的配置进行打包
  • 没有的话就按照默认的配置打包
1
2
3
4
5
6
7
8
9
const path = require('path');
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
//__dirname,当前文件的上级目录的绝对路径
path: path.resolve(__dirname, './build'),
},
};

webpack.config.js改名

如果我们的配置文件并不是webpack.config.js的名字,而是其他的名字呢?

比如我们将webpack.config.js修改成了 wk.config.js;

这个时候我们可以通过 –config 来指定对应的配置文件;

1
webpack --config wk.config.js

但是每次这样执行命令来对源码进行编译,会非常繁琐

所以我们可以在package.json中增加一个新的脚本:

1
2
3
4
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config wk.config.js"
},

之后我们执行 npm run build来打包即可 。

九、webpack依赖图

webpack到底是如何对我们的项目进行打包的呢?

  1. 事实上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件
  2. 从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、 字体等);
  3. 然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)