打包工具webpack(一)

195 阅读8分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

一.webpack的使用

  • 初始化项目:yarn init -yes

  • 安装webpack核心模块和cli模块yarn add webpack webpack-cli --dev

  • 安装完成后,node_modules下的bin目录就有一个webpack-cli

  • 那么就可以通过yarn这个命令快速去找到并运行它

  • yarn webpack --version

  • yarn webpack 这样webpack就会从src目录下的index开始打包

  • 会生成一个dist的目录文件,这个时候,就可以改一下src中的html文件script

  • 可以在package.json中添加一个script

"scripts":{
  "build":"webpack"
}
  • 这样就能直接进行yarn build了

二. webpack配置文件

  • webpack4.0之后,支持0配置直接打包
  • 但如果需要具体的配置,需要建立一个webpack.config.js文件
const path = require('path')
//这个文件会导出一个对象,属性就是配置选项
module.exports = {
  // 输入文件的位置
  entry:'./src/main.js',//如果是相对路径,那么./不能省略
  // 输出文件的位置
  output:{
    filename:'bundle.js',//指定打包后的文件名字
    path:path.join(__dirname, 'output')//指定输出的路径
  },
  mode:'development'//指定打包的模式,一共有三种模式
}
  • webpack默认会使用prod的模式去工作,会压缩代码

  • yarn webpack --mode development//启用开发模式
    开发模式会优化打包的速度和一些代码调试

  • yarn webpack --node none//none模式只会进行原始的打包,不会进行任何的处理

三. webpack打包结果运行原理

  • 先使用yarn webpack --mode none进行原始打包

  • 查看dist目录下的main.js代码

    • 这是一个立即执行函数,这个函数是webpack的工作入口
    • 接受一个参数modules
    • 调用的时候传入一个数组,指最后两行。
  • 展开参数数组

    数组里面是两个参数列表相同的函数

    展开函数,对应源代码里面的模块,也就是说,每个模块都会被包裹到这个函数当中,从而去实现模块的私有作用域

  • 再展开webpakc工作入口的函数

    • 里面的内容并不复杂,且都有注释
    • 首先第一个是一个对象,用来缓存加载过的模块
    • 然后是一个require function,顾名思义,就是用来加载模块的
    • 再往后,就是在require 函数上挂载一些其他的数据和一些工具函数,这些并不重要
    • 拉到最后

      这个函数调用了webapck_require函数,并传入了一个参数,0。开始去加载我们的模块,
      这里的模块id就是加载模块数组中的元素下标。其实在这里才开始加载源代码中的所谓入口模块。
    • 为了更好的理解这个过程,可以先运行起来,然后通过浏览器的单目调试一下
    • 先找到source面板下的bundle.js文件
    • 在开始的地方打上断点(第三行),然后刷新页面,开始调试
    • 那个这个函数参数对应的就是加载的两个模块
    • 对于不重要的环节直接往下点,直到return
    • 进入require函数内部,先判断有没有加载过,加载过就从缓存里面读,没有的话就创建一个新的对象。
    • 创建完成之后,去调用了这个模块所创建的函数。把我们刚刚创建的模块对象,还有导出成员对象,还有require函数,给他传入进去。这样的话就可以在模块内部去使用module.export去导出成员,通过webpakc require去载入模块了
    • 点进去,可以看到首先调用了一个r函数。它的内部作用十分简单,就是给导出对象添加一个标记,进去看一下
    • 进去之后呢,其实就是通过obejct.defineProperty定义了一个标记__esModule,定义完了之后呢。导出对象就有一个这样的标记。用来对外界表明这是一个esmodule
    • 从这函数里出来,继续往下走,就会发现,又调用了require函数,但此时的id是1,说明用来去加载第一个模块

    • 这个模块实际上就是我们定义的第一个模块,head。然后就以相同的步骤去执行head模块,最后把head模块导出的整体对象,通过require函数,把它return回去。module.export通常moduel里面的export是一个对象,默认的导出是放在default上面。然后我们把这个对象的访问函数拿到,去访问里面的default.

    • 然后去调用default函数,然后就去调用内部模块的代码

    • 总结:webpack打包后的代码并不会特别复杂,它只是把所有的模块都放在一个文件里面。除了放置基础的代码,它还可以保持模块间原本的相互依赖关系

四. webpack资源加载

  • webpack内部默认只会去处理JavaScript文件\
  • 那么就需要为其他类型的文件,添加不同的loader\
  • 安装css-loader,yarn add css-loader --dev
  • 安装完成后,需要添加对应的配置
//修改入口地址为css
entry:'./src/main.css
// 具体配置是在module属性当中,去添加一个rules数组
module:{
  // rules就是针对其他加载资源模块的一个规则配置
  // 每个规则对象都有两个属性
  rules:[
    {
      // 一个是test属性,是一个正则表达式
      // 用来匹配打包过程中的文件路径
      test:/.css$/,
      // use属性,用来指定匹配到的文件使用的loader
      use:'css-loader'
    }
  ]
}
  • css-loader就是将我们的css文件,转换成一个模块。具体实现就是将这样一个css代码,push到数组当中。这个数组是由css-loader内部定义的。但整个文件并没有地址去用到这个数组,所以还需要一个style-loader
  • yarn add style-loader --dev//把css-loader转换的结果,通过style标签追加到页面上
module:{
  rules:[
    {
      test:/.css$/,
      // 这里需要把use改成一个数组
      // 如果配置了多个loader,它是从后往前去执行
      // 所以一定要将css-loader放在最后
      use:[
        'style-loader',
        'css-loader'
      ]
    }
  ]
}
  • 打包编译完成后,可以看到多了两个模块

  • 其中在最后一个模块的insert函数当中,通过style的形式,把样式代码,挂载到了页面当中。

  • loader是webpack前端模块化的核心,通过loader就可以去加载任何雷子的资源

1.导入资源模块

  • 通常还是把js文件作为打包的入口,通过在js文件中import的方式,这样的话,css-loader 仍然可以工作
  • 将配置文件中的入口地址改回main.js
const path = require('path')
module.exports = {
  entry:'./src/main.js',
  output:{
    filename:'bundle.js',
    path:path.join(__dirname,'dist')
  },
  module:{
    rules:[
      test:/.css$/,
      use:[
        'style-loader',
        'css-loader'
      ]
    ]
  }
}
  • 然后在main.js中去导入css
// 因为只需要去执行,所以直接导入即可
import './main.css'
  • 完成后去运行打包命令
  • webpack建议我们去动态的引入当前代码需要的任何文件,因为真正需要资源的不是应用,而是现在正编写的代码。是这里的代码,需要正常工作,就必须加载对应的资源。这就是webpack的哲学。
  • 小的来看,是完成了业务需求,大的来看,驱动了整个前端应用
  • 学习一个新事物,并不是学会它的用法就结束。学会它的思想,这才是重点和突破点。能够搞明白它为什么要这样设计,那么就算出道了

2.文件资源加载器

  • 大多数loader都是将资源文件转化为js的方式去工作

  • 但还有一些经常用到但文件,比如图片或者字体。这些文件是没有办法通过js去表示的

  • 对于这一类的文件,就需要用到文件资源加载器,file-loader

  • 先添加一个图片文件,在main.js中导入

import icon from './icon.png'
  • 这里需要接受一个这个资源文件的默认导出,因为导出成员就是打包过后的资源路径,有了这个路径之后,我们就可以创建一个image对象
const img = new Image()
img.src = icon
// 最后再将这个元素append到body当中
document.body.append(img)
  • 安装对于的loader,yarn add file-loader --dev
  • 在为png文件添加相应的配置,那么webpack在打包时就会以file-loader去处理png文件了
{
  test:/.png$/,
  use:'file-loader'
}
  • 运行打包命令,可以看到dist目录下会多一个图片文件,不过文件名称发现了改变

  • 看看图片文件在bundle.js是如何体现的,打开bundle文件,找到最后一个模块,因为处理图片是最后加载的,所以一般就是最后一个。代码非常简单,就是把刚刚生成的名称,导出出去了

  • 在第一个加载模块当中,就是去使用了这个文件路径,并没有复杂的地方

  • 添加一个publicPath路径,去表示网站资源加载的路径

publicPath:''//表示资源放在根目录下
publicPath:'dist/'//表示资源放置dist目录下,/不能省略
  • 总结:根据配置文件中的配置,匹配到文件加载器。先是将导出的文件,拷贝到输出的目录。再将文件拷贝到输出目录的路径,作为当前模块的返回值返回。

3.URL加载器

  • 除了可以用file-loader去解析文件,还能用dataURLs去表示文件

  • dataURLs是一种特殊的url,可以表示文件

  • 传统的url需要服务器上有一个这样的文件,然后请求这个地址,获得这个文件

  • 然而dataURLs是当前url就可以表示文件内容的方式。这种url的文本,就已经包含文件的内容。我们在使用这种url的时候,就不会去发送任何的http请求。

  • 比如这一段url,浏览器就能解析出这是html的内容,类型是UTF-8,内容是一串文本。
data:text/html;charset=UTF-8,<h1>html content</h1>
  • 所以,如果是图片或者字体这种无法通过文本表示的二进制文件。可以将文件的内容进行base64编码。一base64编码后的结果,就是一串字符串,来表示文件的内容。\
data:image/png;base64,iVBORw0KGg0AAAAANSUhE...SuQmCC
  • base64编码可能会比较长,但浏览器同样能解析出文件

  • 所以,我们就能以dataURLs的方式表示任何类型的文件了

  • 安装url-loader,yarn add url-loader --dev

  • 打开配置文件,找到之前的file-loader,进行替换

{
  test:/.png$/,
  use:'url-loader'
}
  • 进行打包编译,这个时候,dist目录下就不会有图片文件了,同时bundle文件内也不是文件路径了,而是文件内容

  • 这种比较适合体积比较小的资源,如果体积比较大,那么打包出来的文件也会比较大。那么会影响到我们的速度。

  • 最佳实践:小文件直接转换成dataURLs,减少http请求的次数。大文件单独提取存放,提高加载速度。url-loader支持通过配置的方式去实现这种情况。

{
  test:/.png$/,
  use:{
    loader:'url-loader',
    options:{
      // 因为这里的单位是字节,所以需要乘1024
      // 这里就只会将小于10KB的文件进行dataURLs
      // 大文件继续单独存放
      // 注意的是,如果使用这种方式,还是要安装file-loader
      // 因为use-loader会调用file-loader
      limit:10 * 1024 //10kB 
    }
  }
}

4.常用的加载器分类

  • 编译转换类:把使用到的资源,转换成js代码

    • css-loader
  • 文件操作类:将资源模块拷贝到资源目录,同时输出访问路径

    • file-loader
  • 代码质量检查类:统一代码风格,提高代码质量

    • eslint-loader

5.webpack与ES2015

  • 虽然可以在webpack种使用import和export,但webpack并不是支持es6的新特性

  • 因为模块需要,所以处理import和export

  • 如果需要处理,那么就需要一个额外的编译型loader

  • yarn add babel-loader @babel/core @babel/preset-env --dev

  • 修改配置文件

{
  test:/.js$/,
  use:{
    loader:'bable-laoder',
    options:{
      presets:['@babel/preset-env']
    }
  }
}

6.webpack加载资源的方式

  1. 遵循ES6标准的import声明

  2. 遵循CommonJS标准的require函数

    • 但CommonJS需要通过导出结果的default属性去获取
const creatHeading = require('./heading.js').default
  1. 遵循AMD标准的define函数和require函数也是同样支持的
  • 总结:尽量不要去混合使用这些标准,只需要统一使用一个标准。
  1. 样式代码中的@import指令和url函数

    • css文件会用css-loader去处理它,处理的时候会发现有url载入图片,那么就会将这个图片作为资源模块加入到打包过程
    • 然后webpack会根据相应的配置,找到处理这个问题,图片的loader。
  1. HTML代码中图片标签的src属性,a标签的href等

    • HTML导出默认会导出字符串,所以需要导出和write
import footerHtml from './footer.html'
document.write(footerHtml)
-   然后还得去配置对于的laoder,否则webpack是不认识html模块的
-   yarn add html-loader --dev



-   配置webpack.config.js
test:/.html$/,
use:{
  loader:'html-loader',
  // html-loader默认只会处理img:src的模块引入
  // 其他的情况比如a标签的href,需要进行配置
  options:{
    attrs:['img:src','a:href']
  }
}

总结:几乎所有的资源加载方式,都会被webpack找出来,并有对应的loader去处理,进行输出。webpack就是基于这样的一个特点,去实现项目的模块化。