前端工程化实战 - Webpack 打包

前端工程化实战 - Webpack 打包

模块打包工具的由来

模块化确实很好地解决了复杂应用开发过程当中的代码组织问题,但是随着我们引入模块化,我们的应用又会产生许多新的问题:

  • ES Modules 存在环境兼容问题
  • 模块文件过多,网络请求频繁
  • 所有前端资源都需要模块化

无容置疑,模块化是必要的,不过我们需要在原有的基础之上引入更好的方案或者工具去解决上面几个问题。让开发者在开发阶段可以继续享受模块化所带来的优势,又不必担心模块化对生产环境所产生的一些影响。

我们希望它们能够满足:

  1. 需要这样一个工具能够编译我们的代码,将开发阶段编写的那些包含新特性的代码直接转换为能够兼容绝大多数环境的代码。
  2. 能过将散落的模块文件再次打包到一起。
  3. 需要去支持不同种类的前端类型,就可以把前端开发过程中所涉及的资源文件都当作模块去使用。

由此,前端模块打包工具就诞生了。

模块打包工具概述

前端领域有一些工具就很好地解决了以上这几个问题,其中最主流的就是Webpack、Parcel和Rollup。

在这里插入图片描述

我们就拿Webpack为例,它的一些和新特性就很好地满足了上面我们所说的需求。

  1. Webpack 作为一个模块打包器(Module bundler),它本身就可以解决模块化JavaScript代码打包的问题。我们通过Webpack就可以将零散的代码打包到同一个JS文件当中。对于有环境兼容问题的代码,我们就可以在打包的过程当中通过模块加载器(Loader)对其进行编译转换。
  2. 其次,Webpack还具备代码拆分(Code Splitting)的能力,它能够将应用当中所有的代码都按照我们的需要去打包。我们可以把应用加载过程当中初次运行时所必需的模块打包到一起,对于其他模块再单独存放,等到应用工作过程当中实际需要到某个模块再异步去加载这个模块,从而实现增量加载。
  3. 最后,对于前端资源模块(Asset Module)的问题,Webpack支持在JavaScript当中以模块化的方式载入任意类型的资源文件。

这是Webpack解决了我们上边所说的这些需求,其他打包工具也都是类似的。总的来说,所有打包工具都是以模块化为目标。

打包工具解决的是前端整体的模块化,并不是指JavaScript模块化。它就可以让我们在开发阶段更好的去享受模块化所带来的优势,同时又不必担心模块化对生产环境所产生的影响,这就是模块化工具的作用。

Webpack 基础

Webpack 快速上手

Webpack作为目前最主流的前端模块打包器,提供了一整套的前端项目模块化方案,而不仅仅是局限于只对JavaScript的模块化。通过Webpack提供的前端模块化方案就可以很轻松地对前端项目开发过程中涉及到的所有的资源进行模块化。

接下来通过我们来通过一个小案例先来了解一下Webpack的基本使用。项目代码

1.我们首先安装项目依赖(自定义的项目可以忽略)

yarn

2.安装serve

yarn add serve --dev

3.通过serve运行项目(正常运行)

yarn serve .

4.引入Webpack,使用项目代码可以直接yarn安装依赖(引用项目代码的话可以忽略)

yarn add webpack webpack-cli --dev

5.打包src下的js代码

yarn webpack

6.修改html引用的js路径,去除type=“module”(引用的项目代码已更改)

<script src="dist/main.js"></script>

7.将webpack命令定义到package.json当中(引用的项目代码已更改)

{
...
"scripts": {
    "build": "webpack"
  },
...
}

8.使用build启动打包

yarn build

Webpack 配置文件

Webpack4以后的版本支持零配置的方式直接启动打包,这个打包过程会按照约定将 'src/index.js' -> 'dist/main.js',但是很多时候我们都需要自定义这些路径。项目代码

例如这个案例当中,入口就是src下的main.js,这时我们就需要专门为webpack添加专门的配置文件webpack.config.js,这个文件是一个运行在node环境当中的js文件,也就是说需要按照Common JS的方式去编写代码

const path = require('path')

module.exports = {
   
  entry: './src/main.js', // 输入
  output: {
   
    filename: 'bundle.js', // 输出
    path: path.join(__dirname, 'output') // 输出目录(绝对路径 通过path转换)
  }
}

Webpack 工作模式

Webpack4新增了一个工作模式的用发,这种用法大大简化了Webpack配置的复杂程度。可以理解成针对不同环境的几组预设配置。项目代码

我们注意到,打包过程中如果不设置mode,终端会打印出一段配置警告,大致意思是说我们没有去设置一个叫做mode的属性,Webpack会默认使用production模式去工作。在这个模式下面,Webpack会自动去启动一些优化插件,例如自动压缩代码,这对实际生产环境是非常友好的,但是打包结果无法阅读。

我们可以通过cli参数去指定打包模式,给webpack命令传入--mode参数。这个属性有三种取值:

  • production生产模式下,会自动启动优化优化打包结果。
  • development开发模式下,Webpack会自动优化打包速度,会添加一些调试过程中需要的辅助到代码当中
  • none模式下,Webpack就是运行最原始状态的打包,不会做任何额外的处理。

具体这三种模式的差异可以在官方文档中找到。

除了使用cli指定工作模式,我们还可以到配置文件中去设置工作模式

module.exports = {
   
  // 这个属性有三种取值,分别是 production、development 和 none。
  mode: 'development',
  ...
}

Webpack 打包结果运行原理

打开项目代码打包过后的bundle.js文件,通过ctrl+K ctrl+0把代码折叠起来以便我们对整体结构的了解

在这里插入图片描述

我们可以看到,整体生成的代码是一个立即执行函数,这个函数是Webpack的工作入口,它接收modules参数,调用时传入一个数组。

展开这个数组,数组当中的每一个元素都是一个参数列表相同的函数,对应的函数就是源代码中的模块。也就是说,每一个模块最终都会被包裹到这样一个函数当中,从而去实现模块的私有作用域。

我们再来展开Webpack的工作入口函数,这个函数内部最开始先定义了一个对象用于去存放(缓存)我们加载过的模块。紧接着定义了一个require函数,顾名思义,这个函数就是用来加载模块的。再往后就是在require这个函数上挂在了一些其他的数据和一些工具函数。函数执行到最后它调用了require这个函数,传入0开始去加载模块。(这个地方的模块id实际就是上面的模块数组当中的元素下标,也就是说这里才开始加载源代码当中所谓的入口模块)

为了可以更好的理解,我们把它运行起来,通过浏览器的开发工具来单步调试一下

在这里插入图片描述
在最开始的位置加上一个断点,然后刷新页面启动调试。

在函数一开始运行的时候,它接受到的应该是两个模块所对应的两个函数
在这里插入图片描述
在这个位置,它加载了id为0的模块,我们进入到这个require函数内部
在这里插入图片描述
require函数内部先去判断模块有没有被加载过,如果加载了,就从缓存里面读,如果没有就创建一个新的对象
在这里插入图片描述
紧接着调用了这个模块相对应的函数,把刚刚创建的模块对象还有导出成员对象以及require函数传入进去。这样在模块内部就可以使用module.xeports导出成员,通过Webpack的require载入模块
在这里插入图片描述
我们进来,在模块内部,它先去调用了一个r函数,这个r函数内部作用就是用来给我们在导出对象上去添加一个标记,我们进去看一下
在这里插入图片描述
进来过后,它实际上就是在导出对象上定义了一个__esModule的一个标记,定义完成过后这个导出对象上面就有了这样一个标记
在这里插入图片描述
用来对外界表面这是一个ES Module,紧接着往下又调用了这个require函数,此时传入的id是1,也就是说去加载第一个模块,这个模块实际上就是我们在代码当中import的header。完成过后再去以相同的道理执行header模块
在这里插入图片描述
最后将header这个模块导出的整体的对象通过require函数return回去
在这里插入图片描述
module中的exports应该是一个对象,因为ES Module里面默认导出它是放在default上面。
在这里插入图片描述
此时将模块的导出对象拿到,然后访问里面的default。这个时候调用这个default函数,内部还是会调用内部模块的代码
在这里插入图片描述
最终将创建完的元素拿到并append到body上面
在这里插入图片描述
在这里插入图片描述
这实际上就是Webpack打包大致的运行过程。

在这里插入图片描述

我们可以看出来Webpack打包过后的代码并不会特别复杂,它只是说帮我们把所有的模块给放到了同一个文件当中。除此之外,它还提供了一些,让我们的模块与模块之间相互依赖的关系还可以保持原来的状态。

Webpack 模块加载器 Loader

Webpack 资源模块加载 (css-loader style-loader)

Webpack 不仅仅是JavaScript模块化打包工具,它更应该算是整个前端项目或者叫前端工程的模块打包工具,这也就是说我们还可以通过Webpack引入我们在前端项目中的任意类型文件。

接下来我们尝试Webpack打包CSS文件,项目代码

Webpack内部默认只会处理JavaScript文件,我们可以通过适当的Loader(加载器)来处理CSS文件,内部的Loader只能处理JS文件,我们可以在去为其它类型的资源文件添加不同的Loader。

  • Loader 是 Webpack 的核心特性
  • 借助于 Loader 就可以加载任何类型的资源

这里需要的是一个css-loader,我们来安装一下

yarn add css-loader --dev

如果只是使用这样一个Loader,我们并不能得到预期的结果。因为css-loader的作用是将CSS文件转换为一个JS模块。只是将css代码push到这样一个由css-loader内部提供的数组当中,整个过程并没有去用到这个数组。

所以我们还需要安装一个style-loader,它的作用就是把css-loader转换过后的结果通过style标签的形式追加到页面上

yarn add style-loader --dev

安装过后需要在配置文件添加相应的配置

module.exports = {
   
  ...
  entry: './src/main.css', // 将css文件作为打包入口
  ...
  module: {
   
    rules: [
      {
   
        test: /.css$/, // 匹配打包过程遇到的文件路径
        use: [ // 指定匹配到的文件使用到loader
          // 当我们配置多个loader 执行顺序从下至上
          'style-loader',
          'css-loader'
        ]
      }
    ]
  }
}

Webpack 导入资源模块

通过以上方法,我们确实可以将CSS文件作为打包入口,不过Webpack打包入口一般还是JavaScript,因为打包入口从某种程度上可以算是应用的运行入口

就目前而言,前端应用的业务一般是由JavaScript来驱动的,正确的做法还是应该把JS文件作为打包入口,在js代码中通过import的方式来引入CSS文件。

在这里插入图片描述

这样的话,css-loader仍然可以正常工作,项目代码

我们先将配置文件的入口改回main.js


module.exports = {
   
  ...
  entry: './src/main.js',
  ...

然后在main.js内部通过import导入main.css

// main.js
import './main.css' // 只需要执行
...

我们在为heading.js添加heading.css样式文件并编写简单样式,在heading.js当中导入

// heading.js
import './heading.css'
...

传统的做法当中,我们是将样式和行为分离开单独去维护,单独去引入。而Webpack又建议我们要在js当中去载入css,这到底是问什么呢?

其实Webpack不仅仅建议我们在js中引入css,而是建议我们在编写代码过程当中去引入任何当前代码所需要的文件

  • 根据代码的需要动态导入资源
  • 真正需要资源的不是应用,而是代码

是这里的代码想要正常工作,就必须要去加载对应的资源,这也正是Webpack的哲学。

JavaScript代码本身是负责完成整个业务的业务功能,放大来看它就是驱动了整个前端应用。而在实现业务功能的过程当中可能需要用到样式或者图片等等一系列资源文件,如果建立了这种依赖关系

  • 逻辑合理,JS确实需要这些资源文件
  • 确保上线资源不缺失,都是必要的

其实学习一个新事物不是说学会它的所有用法你就能提高,因为这些东西照着文档基本上谁都可以。很多时候这些新事物的思想才是突破点,能够搞明白这些新事物为什么这样设计,那你基本上就算是出道了。

Webpack 文件资源加载器 (file-loader)

目前Webpack社区提供了非常多的资源加载器,接下来我们再来尝试一些有代表性的Loader。

首先是文件资源加载器,项目代码

大多数的资源加载器都类似css-loader,都是将资源模块转化为js代码的实现方式去工作。但是还有一些我们经常用到的资源文件,例如项目当中的图片、字体,这些文件没有办法通过js的方式表示。

对于这一类的资源文件,我们需要用到文件资源加载器,也就是file-loader。

我们在项目当中已经添加了一张普通的图片,我们假设这张图片就是我们再去实现某个功能的时候所需要的一个资源。

按照Webpack的思想,我们也应该在用到这个资源的地方通过import去导入这张图片,然后让Webpack去处理资源的加载

// main.js
import icon from './icon.png'
...
const img = new Image()
img.src = icon

document.body.append(img)

安装文件资源加载器file-loader

yarn add file-loader --dev

打开配置文件,添加一个单独的加载规则配置

module.exports = {
   
  ...
  output: {
   
    ...
    publicPath: 'dist/' // 将项目根目录作为网站根目录
  },
  module: {
   
    rules: [
      ...
      {
   
        test: /.png$/,
        use: 'file-loader'
      }
    ]
  }
}

在这里插入图片描述

Webpack URL加载器 (url-loader)

除了file-loader这种通过拷贝物理文件的形式去处理文件资源以外,还有一种通过Data URLs的形式去表示文件的方式。

Data URLs 是一种特殊的URL协议,它可以用来直接去表示一个文件。传统的URL一般要求服务器有一个对应的文件,然后通过请求这个地址得到服务器上对应的文件。

而Data URLs 是一种当前url就可以直接去表示文件内容的方式。

在这里插入图片描述
也就是说这种URL当中的文本就已经包含了文件的内容,在我们去使用这种URL的时候就不会再去发送任何的http请求。

例如这样一段Data URLs,浏览器就能够根据这样一段url解析出来这是一个html类型的文件内容,它的编码是UTF-8,内容是一段包含h1标签的html代码
在这里插入图片描述
在这里插入图片描述
如果说是图片或者是字体这一类无法直接通过文本去表示的二进制类型文件。

在这里插入图片描述

我们可以通过将文件的内容进行base64 编码,以base 64编码过后的结果表示这个文件的内容

在这里插入图片描述
例如这样一段给出的Data URLs,这个url就是表示了一个png类型的文件,文件编码是base64,后面就是这张图片的base64编码

在这里插入图片描述

一般情况下,base64编码会比较长,浏览器同样可以解析出来对应的文件

在这里插入图片描述

在Webpack打包静态资源模块时,同样可以通过这种方式去实现。通过Data URLs,我们就可以以代码的形式去表示任何类型的文件了。项目代码

我们先来安装url-loader

yarn add url-loader --dev

修改配置文件,配合file-loader使用

module.exports = {
   
  ...
  module: {
   
    ...
      {
   
        ...
        use: {
   
          loader: 'url-loader',
          options: {
   
    		// 小于10kb的文件转化为Data URLs 嵌入代码中
    		// 超出10kb的文件单独提取存放
            limit: 10 * 1024 // 10 KB
          }
        }
      }
    ]
  }
}
  • 小文件使用 Data URLs,减少请求次数
  • 大文件单独提取存放,提高加载速度

Webpack 常用加载器分类

Webpack中的资源加载器是用来去处理和加工打包过程遇到的资源文件。除了以上介绍到的加载器,社区当中还有很多其他的加载器。

我们将这些Loader大致分为三类做一个归纳:

  • 编译转换类

这种类型的Loader会把我们加载到的资源模块转换为JavaScript代码。

例如之前用到的css-loader,就是将css代码转化为bundle中的一个JavaScript模块,从而去实现通过JavaScript运行CSS。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值