在前端开发中生成最后的部署代码都会执行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。
弄懂什么是模块化,需要了解什么是命令式编程,即面向过程编程。
命令式:"过程思维"
命令式编程,它关注的是一系列具体的执行步骤,当你想要使用一段命令式的代码来达到某个目的,你需要一步一步地告诉计算机应该"怎样做"。
我们拿番茄炒蛋来说明什么是命令式编程。你需要按顺序发出如下命令:
- 你要把鸡蛋敲开打散,番茄切块备用
- 锅里倒油,油热后倒入蛋液
- 鸡蛋凝固后,放入西红柿开炒
- 加调味,再炒
- 大火收汁
- 关火
- 装盘
命令式做菜要求必须对做菜的过程了如指掌,每一个步骤都需要事无巨细地关注到。
这些具体步骤组成的命令序列,就是一段命令式程序。
再来看一个我们以前写前端代码的例子。
比如,一个页面分为头部、侧边栏,内容三个部分,代码如下:
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 认识啊。
所以可以通过 webpack 把 import 翻译成浏览器认识的语法。
即把 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>