为什么现代前端工程都需要使用webpack打包?

1,231 阅读6分钟

在前端开发中生成最后的部署代码都会执行npm run build进行打包,它底层就是调用webpack对我们写的vue/react源码打包成浏览器能运行的js/css/html文件。

那为什么现代前端工程都需要使用webpack打包?

我直接用js/css/html三板斧进行开发,行不行?

当然是可以的,这还避免了打包这一步骤呢。

那为什么vue/react等框架都选择了webpack进行打包呢?

你可能会说,这是因为我们写的是.vue语法,或者是jsx语法,这些语法浏览器不识别,因此需要使用webpack进行转换成浏览器识别的js语法。

但是,进行语法转换我们使用babel就可以了,而且webpack在进行语法转换的时候其实也是使用的babel-loader

这是因为,随着前端项目越来越庞大,为了方便管理必须进行模块化开发,但是浏览器和javascript语法根本就不支持模块化开发,也就是浏览器根本不认识import/require。那么就需要一个工具支持这种模块化开发模式,这就诞生了webpack

弄懂什么是模块化,需要了解什么是命令式编程,即面向过程编程。

命令式:"过程思维"

命令式编程,它关注的是一系列具体的执行步骤,当你想要使用一段命令式的代码来达到某个目的,你需要一步一步地告诉计算机应该"怎样做"。

我们拿番茄炒蛋来说明什么是命令式编程。你需要按顺序发出如下命令:

  1. 你要把鸡蛋敲开打散,番茄切块备用
  2. 锅里倒油,油热后倒入蛋液
  3. 鸡蛋凝固后,放入西红柿开炒
  4. 加调味,再炒
  5. 大火收汁
  6. 关火
  7. 装盘

命令式做菜要求必须对做菜的过程了如指掌,每一个步骤都需要事无巨细地关注到。

这些具体步骤组成的命令序列,就是一段命令式程序。

再来看一个我们以前写前端代码的例子。

比如,一个页面分为头部、侧边栏,内容三个部分,代码如下:

var dom = document.getElementById('root')
var header = document.createElement('div')
header.innerHTML = 'header'
var sideBar = document.createElement('div')
sideBar.innerHTML = 'sideBar'
var content = document.createElement('div')
content.innerHTML = 'content'
dom.appendChild(header)
dom.appendChild(sideBar)
dom.appendChild(content)

如果把上面的三个部分都填充进内容,代码就变得非常的长。

这其实就是面向过程的编程,即把所有的逻辑写在一个文件中,最后变得越来越大,最终不可维护。

那怎么办呢?

那就把头部、侧边栏、内容分别放在单独的一个文件中管理,然后在一个index.html中通过script标签把这三部分引入就可以了。

改造成如下代码:

// index.html
<script src="./header.js"></script>
<script src="./sideBar.js"></script>
<script src="./content.js"></script>
<script src="./index.js"></script>

// index.js
var dom = document.getElementById('root')
new Header()
new SideBar()
new Content()

这样代码就更具有维护性。

但是上面的写法,会带来几个问题:

  • 整个页面的加载速度变慢,原来只需要一个 http 请求,现在需要 4 个;

  • index.js 文件中看不出引入的文件与文件之间的位置关系;

  • 很难去查错,如果把文件引入的顺序改变下,就会报错,因为 index.js 执行的时候,content 根本就没有加载回来;

模块化: webpack

如果我们利用模块化来组织代码,那么代码就是如下这个模式:

// index.js
import Header from './header'
import SideBar from './sideBar'
import Content from './content'
var dom = document.getElementById('root')
new Header()
new SideBar()
new Content()

首先有一个入口文件index.js,在入口文件中引入了各个模块,最后在 index.html 引入 index.js 文件。

问题1:整个页面的加载速度变慢

利用一个入口文件index.js来引入各个模块,在打包的时候就会把各个模块的代码全部打包到index.js文件中,index.html只需要引入入口文件即可,这样就解决了加载慢的问题。

问题2:文件引入顺序问题

通过import语法很清楚的知道文件引入的顺序,从而避免因位置顺序而导致的代码错误。

如果我们这样写代码能不能在浏览器中运行呢?答案是不行的,这是因为浏览器不认识 import 这个语句的。

那么这个时候就是 webpack 登场了,浏览器不认识,但是 webpack 认识啊。

所以可以通过 webpackimport 翻译成浏览器认识的语法。

即把 index.js 文件翻译成一个浏览器能读懂的 js 文件,然后 index.html 引入这个 js 文件,页面就可以正常显示了。

那我们暂时可以得出结论:webpack 就是 js 代码的翻译器

但是webpack其实称不上一个翻译器,它只认识import/require这么两个语法, 其他es的高级语法,它也不认识,所以把webpack当成js的翻译器,其实是高看了它。那么webpack到底是什么呢?

根据官网的描述,它是一个模块打包工具

这里面有两个关键词:模块 vs 打包

下面是webpack官网中关于模块的定义:

在模块化编程中,开发者将程序分解为功能离散的 chunk,并称之为 模块。每个模块都拥有小于完整程序的体积,使得验证、调试及测试变得轻而易举。 精心编写的 模块 提供了可靠的抽象和封装界限,使得应用程序中每个模块都具备了条理清晰的设计和明确的目的。

在我们前端代码中,你可以这样认为:任何通过import/require引入的都称之为模块,它可以是js,css,图片等等资源。

打包的意思就是把多个文件整理到一起

因此,webpack就是一个模块打包工具。它没有其他功能,它唯一有的功能就是认识import/require语法,知道这是一个引入文件的意思,然后把引入的文件打包到一起生成一个最终的文件bundle.js

在上面的例子中,Header Siderbar Content就是一个个的模块,入口文件index.js引入了这些模块,当webpack解析入口文件时发现有import其他模块,它就会把模块的代码打包到入口文件中,最终形成一个bundle

script 标签之 type = module

我们知道浏览器最开始是不支持模块化的,不过最近慢慢开始支持了。

script标签中加上type = module,我们就可以在代码中直接使用import语法,而不需要使用webpack这样的翻译器。

// module.js
export default function test(){
  return 'test...'
}

// index.js
import test from './module.js';
console.log(test())

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  // 方法 1 : 引入module.js,然后在script标签里面调用
  <script type="module">
    import test from './module.js';
    console.log(test())
  </script>

  // 方法 2 : 直接引入index.js,使用src引入
  <script type="module" src="./index.js"></script>

</body>
</html>