likes
comments
collection
share

在?大白话跟你唠明白Webpack(基础篇)

作者站长头像
站长
· 阅读数 11

大家好我是来蹭饭,一个会点儿吉他和编曲,绞尽脑汁想傍个富婆的摸鱼大师。

上一篇文章点赞明显已经过了万。

在?大白话跟你唠明白Webpack(基础篇)

所以今天起,咱们正式挖坑webpack系列的相关内容。争取用最白的大白话跟你唠明白它到底是个什么玩意儿。

一. 前言

webpack对于每个前端工程师的意义不言而喻,既然如此咱就不多言了。

我们看下这个系列的规划,以及读完后你能学到什么。本系列将分为以下六篇文章全方位解析webpack助你构建系统的知识体系:

  1. 基础篇——了解webpack的相关概念,能够配置webpack

  2. 进阶篇——能够优化webpack的相关配置

  3. 实战篇——使用webpack从0到1搭建React的开发环境

  4. 原理篇——剖析loader从概念到手写到原理

  5. 原理篇——剖析plugin从概念到手写到原理

  6. 原理篇——剖析webpack打包原理,手写小型webpack

本篇为该系列的基础篇,本系列基于webpack5讲解相关内容。相关案例代码已全部上传至git,欢迎自取,方便的话欢迎点个star,不胜感激。

下面我们将从相关概念基础配置等章节来讲解本篇内容,大家坐稳扶好,我们发车!

二. 相关概念

2.1 webpack

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

我们结合官网的banner图,用大白话来解读这段定义:

在?大白话跟你唠明白Webpack(基础篇)

  • webpack是一个打包工具,一个资源中转站。它接收一些文件,经过内部处理后,输出对应的静态资源

  • 它从入口文件出发,找到入口依赖的模块,再递归寻找依赖的依赖,直到所有模块被加载进来

  • 它把开发者使用方便但浏览器不能直接识别的资源(如less,scss,ES Next等),处理成浏览器可直接识别的资源(css,js等)

打个比方,webpack就像一位厨师,浏览器就像顾客。有一天顾客进饭店点了一盘辣椒炒肉,后厨如果端半斤生猪肉和辣椒给顾客,这单生意肯定要黄。厨师需要把生的食材处理成菜才可上桌。那问题就来了,这盘菜要怎么做?

首先明确要炒的是辣椒炒肉而不是辣子鸡丁,将辣椒炒肉作为入口。找到主要的食材,猪肉,辣椒。——确定入口文件,寻找入口依赖的模块

找到后厨师想了想“吃肉不吃蒜,等于没吃蒜”。于是他拿了一头大蒜,紧跟着又拿了点大葱。——寻找依赖的依赖

接着就是炒制工作了,按顺序放入食材,加入调味料,出锅装盘,这一盘菜就能上桌了。——内部处理后,打包成浏览器可直接识别的资源

这就是webpack,它具体如何处理接收到的文件呢?

本篇中我们仅需知道webpack依据webpack.config.js这个配置文件的内容,进行对应的打包处理即可。

webpack.config.js会根据配置的内容指导webpack的工作。如同厨师的菜谱指导厨师做菜。所以本阶段学习webpack的使用,实际上就是学习webpack.config.js的配置。下面我们将结合实践,理解与之相关的一些概念。

2.2 基础配置

本章我们会搭建一个webpack的练习环境,结合示例理解webpack.config.js中的概念。本案例中webpack的版本为:5.73.0,cli版本为:4.9.2

2.2.1 环境搭建

  • 步骤1:  创建项目,安装webpack与webpack-cli

yarn add webpack webpack-cli -D

  • 步骤2:  创建src文件夹,新建index.js作为入口文件,并填充如下内容

在?大白话跟你唠明白Webpack(基础篇)

至此我们的环境搭建完成,接下来我们打开终端,在案例中输入webpack看看会发生什么?

在?大白话跟你唠明白Webpack(基础篇)

目录中多出了一个dist文件夹,这个文件夹就是webpack打包输出后的内容

有人可能会疑惑,这菜谱webpack.config.js配置文件都没有,厨师怎么自己做上菜了?

这里需要注意。从webpack v4.0.0开始,webpack 可以不用再引入一个配置文件来打包项目,webpack会内置一些默认配置供开发者直接使用。不过实际开发中,因为打包和优化等需求,webpack.config.js还是需要加入到项目中。

至此我们已跑通了最简单的webpack练习环境,接下来我们将结合示例理解webpack中entry,output,loader,plugin,mode这五大概念。

2.3 entry——入口

入口起点(entry point)  指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

我们现在大多数开发的是单页面应用,这里我们以单入口为例,具体配置可去文档中查看

我们修改目录结构,新增webpack.config.js文件(注意该文件必须与src同级,且不能更改命名。后续我们可以通过更改script脚本命令来打破这些限制。)与add.js文件。

├─ src
│  ├─ add.js
│  └─ index.js
├─ .gitignore
└─ README.md
└─ webpack.config.js
└─ package.json

webpack.config.js文件配置如下,entry配置的是相对路径:

module.exports = {
  entry: './src/index.js'
}

add.js内容如下:

const add = (a,b) => {
  return a + b
}

export default add

index.js引入这个add函数并输出:

import add from './add.js'

const hello = () => {
  console.log('hello webpack')
}

hello()
console.log(add(2,3))

接下来打开终端输入webpack回车看看它是如何打包文件的。

在?大白话跟你唠明白Webpack(基础篇)

可以看到刚刚引入的add函数也被打包输出。注意这个add函数输出的形式,webpack帮我们直接输出了函数的运算结果。

只要资源被其他模块引入并使用,webpack就会帮我们打包这些资源

2.4 output——输出

可以通过配置 output 选项,告知 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。

我们更改webpack.config.js的配置如下:

const path = require('path')

const resolvePath = _path => path.resolve(__dirname, _path)

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

  output: {
    path: resolvePath('./dist'),
    filename: 'scripts/[name].js'
  }
}

这里讲解一下output中这2个属性:

  • path: 文件输出的路径,必须是个绝对路径
  • filename: 文件输出的名称。可以在名称前指定一个输出路径,让文件输出在这个路径下。同时也可以使用占位符[name]指定不同的文件名称

终端输入webpack打包,我们查看打包后的程序

在?大白话跟你唠明白Webpack(基础篇)

新的main.js被打包到我们在filname中指定的scripts文件夹下。

这里有个新的问题待我们解决,之前被打包的main.js没有被清除,我们希望每次打包都能生成最新的文件,并清除上一次的打包结果。此时在output中配置clean:true即可解决问题,更改output配置如下:

output: {
    path: resolvePath('./dist'),
    clean: true,
    filename: 'scripts/[name].js'
  }

再次打包:

在?大白话跟你唠明白Webpack(基础篇)

效果实现,之前的文件已被清除。这里需要注意,webpack 5.0以下的版本,需要另外引入插件clean-webpack-plugin进行配置,过程较为复杂,不展开赘述,了解即可。webpack 5.0以上仅需在output中配置clean的值为true即可实现同样的效果

2.5 loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

我们用大白话翻译一下就是:loader是一个翻译,把webpack不能直接处理的资源,翻译成能直接处理的

那什么是webpack不能直接处理的资源呢?我们尝试打包一个css资源,调整目录结构如下:

├─ src
│  ├─ css
│  │  └─ index.css
│  └─ add.js
│  └─ index.js
├─ .gitignore
└─ README.md
└─ webpack.config.js
└─ package.json

index.css中添加如下样式:

body {
  background-color: red;
}

index.js引入index.css后,进行打包,查看结果如下:

在?大白话跟你唠明白Webpack(基础篇)

报错提示: webpack不识别样式的写法,我们需要一个合适的loader来处理这种类型的文件。

这就是webpack不能直接处理的资源。也就是说除了webpack开箱自带支持的JS,JSON文件,其余文件都不能被webpack直接处理。此时就需要我们引入loader帮助webpack翻译翻译,什么TMD叫惊喜。

接下来我将围绕样式资源,图片资源,ES Next等跟大家讲解loader的具体使用方法。

2.5.1 样式loader

加载css文件——css-loader,style-loader

  • 步骤1: 识别基础css资源需要安装2个loader

yarn add css-loader style-loader -D

loader作用如下:

css-loaderstyle-loader
将css文件处理成commonJs模块放入js中创建style标签将js中的css插入到html中
  • 步骤2: 下载完成后,配置webpack.config.js使用loader
module.exports = {
  // ...

  module: {
    rules:[{
      test: /\.css$/,
      use:[
        'style-loader',
        'css-loader'
      ]
    }]
  }
}

这里我们新增了module配置项,它用来配置不同loader的使用规则。module.rules 允许你在 webpack 配置中指定多个 loader。

rules中每一个对象即为处理一种类型文件的方式,具体字段释义如下:

testuse
loader匹配的规则对应文件使用的loader

这里的配置是为所有css文件匹配使用style-loadercss-loader。注意loader的执行顺序是从下到上,从右到左。先执行css-loader处理css,再执行style-loader将css通过style标签插入页面。

  • 步骤3: 执行webpack查看结果:

在?大白话跟你唠明白Webpack(基础篇)

编译成功,我们在dist根目录下新建index.html,并引入打包后的main.js查看结果:

在?大白话跟你唠明白Webpack(基础篇)

打包后的js和样式全部生效了,我们看看样式插入的方式:

在?大白话跟你唠明白Webpack(基础篇)

样式通过style标签被插入页面。到这里基础的样式loader使用就已经成功。但实际开发中我们多以less,scss开发为主,处理这些样式文件需要使用对应的less-loadersass-loader

加载less文件——less-loader

我们调整src/css文件结构,新增一个less文件,如下所示:

├─ css
│  ├─ index.css
│  └─ rectangle.less

less文件样式如下:

@blue: #4285f4;

.webpack-rectangle{
  width: 100px;
  height: 60px;
  background-color: @blue;
}

src/index.js引入less文件,进行打包

import add from './add.js'
import './css/index.css'
import './css/rectangle.less'

// ...

查看结果,发现报错:

在?大白话跟你唠明白Webpack(基础篇)

报错信息: 不支持less文件的打包,需要一个loader处理该类文件。

与css报错的信息基本一致,这时请出我们的less-loader。

  • 步骤1: 安装less,less-loader

yarn add less less-loader -D

  • 步骤2: 配置webpack.config.js
// ...
module.exports = {
  // ...
  module: {
    rules:[{
      test: /\.css$/,
      use:[
        'style-loader',
        'css-loader'
      ]
    },{
      // 匹配less文件
      test: /\.less$/,
      // loader的使用顺序 less-loader,css-loader,style-loader
      use:[
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    }]
  },

  mode:'development'
}

这里新增mode:'development',防止webpack报一些提示信息的错误。然后在rules数组中新增处理less文件的loader对象。

这里的处理顺序如下所示:

  1. less-loader将less文件转义成css文件
  2. css-loader将css文件处理成commonJs模块
  3. style-loader将css通过style标签插入页面中
  • 步骤3: 在dist根目录下再次新建一个index.html引入main.js查看刚刚写的less是否生效,这里为了观察方便我们把原来的红色背景改成灰色背景。
<body>
  <div class="webpack-rectangle"></div>
  <script src="./scripts/main.js"></script>
</body>

在?大白话跟你唠明白Webpack(基础篇)

ok生效!大功告成。接下来我们处理sass,scss文件。

加载sass文件——sass-loader

调整src/css文件结构,新增一个scss文件,如下所示:

├─ css
│  ├─ index.css
│  └─ circle.scss
│  └─ rectangle.less

scss文件样式如下:

$baseCls:"webpack-circle";

.#{$baseCls} {
  width: 100px;
  height: 100px;
  border: 1px solid #4285f4;
  border-radius: 50%;
}

src/index.js引入scss文件,进行打包

import add from './add.js'
import './css/index.css'
import './css/rectangle.less'
import './css/circle.scss'

// ...

这里与处理less-loader的方法大同小异

  • 步骤1: 安装sass,sass-loader

yarn add sass sass-loader -D

  • 步骤2:  配置webpack.config.js的rules:
rules:[
    // ...
    {
      test: /\.s[ac]ss$/,
      use:[
        'style-loader',
        'css-loader',
        'sass-loader'
      ]
  }]

解析规则与less-loader相似,在此不多赘述。

  • 步骤3: 执行webpack,dist根目录下创建index.html引入main.js查看样式
<body>
  <div class="webpack-rectangle"></div>
   <div class="webpack-circle"></div>
  <script src="./scripts/main.js"></script>
</body>

在?大白话跟你唠明白Webpack(基础篇)

大功告成,到这里我们的webpack就可以正常解析less和sass了。

如果我们希望样式被抽离成单独的css文件以link标签的形式引入页面,需要借助插件实现。下一章节plugin中我们跟大家进行详细讲解。

2.5.2 资源loader

处理图片资源

webpack4中处理图片使用file-loaderurl-loader,webpack5中,这2个loader已被内置到webpack里,我们只需激活配置即可。

在开始工作之前我们把src做个备份,然后继续开发。

  • 步骤1: 调整src结构
├─ src
│  ├─ assets
│  │  └─ img.png
│  │  └─ iron-man.gif
│  │  └─ mk50.jpeg
│  ├─ css
│  │  └─ index.scss
│  └─ add.js
│  └─ index.js
  • 步骤2: 配置webpack.config.js的rules,里面的字段含义我们稍后讨论
rules:[
    // ...
    {
      test: /\.(jpe?g|png|gif|webp|svg)$/,
      type: 'asset',
      generator: {
        filename: 'assets/img/[hash:10][ext]'
      }
    }]
  • 步骤3: 修改index.scss
$baseCls:"webpack-img";

.#{$baseCls} {
  display: flex;
  justify-content: space-between;
  div{
    width: 230px;
    height: 230px;
    &.box1{
      background: url(../assets/img/img.png) no-repeat center /contain;
    }
    &.box2{
      background: url(../assets/img/iron-man.gif) no-repeat center /contain;
    }
    &.box3{
      background: url(../assets/img/mk50.jpeg) no-repeat center /contain;
    }
  }
}
  • 步骤4: 执行webpack,dist根目录下创建index.html引入main.js查看样式
<body>
  <div class="webpack-img">
    <div class="box1"></div>
    <div class="box2"></div>
    <div class="box3"></div>
  </div>
  <script src="./scripts/main.js"></script>
</body>

查看结果:

在?大白话跟你唠明白Webpack(基础篇)

成功运行,我们看看dist目录生成的文件

在?大白话跟你唠明白Webpack(基础篇)

新生成了assets文件夹,里面包含hash名称的图片资源,再看看刚刚的loader中有这样的配置。

generator: {
    filename: 'assets/img/[hash:10][ext]'
}

这下应该明白了,filename可以指定图片资源的输出路径,其他字段释义如下:

[hash:10][ext]
取hash的前10位为文件名称文件扩展名,之前是什么值,打包后仍然是什么值

可以看出,打包前和打包后,文件扩展名是未发生修改的。到这里图片资源的处理就完成了。

2.5.3 babel-loader

将 ES6+ 语法编写的代码转义为向下兼容的js代码,使低版本浏览器能正常运行程序。

在使用babel-loader前,我们备份一下src,调整目录结构如下:

├─ src
│  └─ index.js
├─ .gitignore
└─ README.md
└─ webpack.config.js
└─ package.json

在index.js中编写一段ES6代码:

class GirlFriend {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  say () {
    console.log(`我叫${this.name},我今年${this.age}岁。很高兴认识你`)
  }
}

const girl = new GirlFriend('Alice',22)

girl.say()

使用webpack打包查看结果:

在?大白话跟你唠明白Webpack(基础篇)

可以发现,ES6的代码被原封不动地打包了,这样的打包结果,对低版本浏览器并不友好。所以我们需要引入babel-loader解决这个问题,下面我们开始配置babel-loader。

  • 步骤1: 安装依赖
yarn add babel-loader @babel/core @babel/preset-env -D
@babel/core@babel/preset-env
babel核心库智能预设,允许开发者使用最新的js
  • 步骤2: 配置webpack.config.js的rules:
rules: [
    // ...
    
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }]

当然我们也可以在项目根目录中新增.babelrcbabel.config.js等文件,设置一些babel的使用规则。关于babel更详细地使用这里不多做展开,请查阅官方文档了解。

  • 步骤3: 执行webpack查看打包结果

在?大白话跟你唠明白Webpack(基础篇)

在?大白话跟你唠明白Webpack(基础篇)

可以看到我们的ES6代码已被转义为ES5代码。

到这里常见loader的配置就学习完毕了,接下来我们进入plugin的学习。

2.6 plugin——插件

plugins 选项用于以各种方式自定义 webpack 构建过程。它监听webpack的打包过程,执行对应的生命周期回调,拓展webpack的功能。

下面我们来了解最常见的2个plugin

2.6.1 打包html页面——HtmlWebpackPlugin

之前我们打包js,css后,总要在dist根目录下新建一个html,引入打包的js并进行一些设置,才能在页面中看到打包后的效果。这种方式非常不方便,因此我们通过HtmlWebpackPlugin这一插件,让webpack帮我们自动打包出一个index.html。

这里我们提一个需求,我们希望webpack能打包一个html页面,这个页面生成时带有这样的内容<h2>Hello CengFan!</h2>,并引入打包后的js资源。

需求有了接下来我们一步步实现

  • 步骤1: src文件夹下新建一个html,内容如下
<body>
  <h2>Hello Cengfan!</h2>
</body>
  • 步骤2: 安装依赖

yarn add html-webpack-plugin -D

  • 步骤2: 配置webpack.config.js
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin')

const resolvePath = _path => path.resolve(__dirname, _path)

module.exports = {
  // ...

  plugins: [
    new HtmlWebpackPlugin({
      template: resolvePath('./src/index.html'),
    })
  ],

  mode: 'development'
}

template指定以哪个html为入口文件,值是一个绝对路径。

plugin都要遵循先引用,然后使用new关键字进行实例化调用的原则。

  • 步骤3: 执行webpack查看打包结果

在?大白话跟你唠明白Webpack(基础篇)

符合预期,js也正常运行

在?大白话跟你唠明白Webpack(基础篇)

2.6.2 打包独立的css文件——MiniCssExtractPlugin

还记得之前在样式loader一节中我们遇到的问题吗,css无法被单独打包成文件只能通过style标签被插入页面。到这里这个问题就能被解决了。

我们恢复之前备份的资源loader文件夹,把它设置成src,并添加index.html。下面我们正式开始。

  • 步骤1: 安装插件

yarn add mini-css-extract-plugin -D

  • 步骤2: 配置webpack.config.js
// 引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  // ...

  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    }),
  ],

  mode: 'development'
}

这里的filenameoutput中的类似,不多赘述。

module.exports = {
  //...

  module: {
    rules: [
    //...
    {
      test: /\.s[ac]ss$/,
      use: [
        MiniCssExtractPlugin.loader,
        'css-loader',
        'sass-loader'
      ]
    }
    //...
    ]
  },

  //...
}

由于样式是用scss写的,所以我们还需更改与之匹配的loader。如果不更改对应的loader,最后样式还是会通过style标签被插入页面。

  • 步骤3: 配置html的内容
<body>
  <div class="webpack-img">
    <div class="box1"></div>
    <div class="box2"></div>
    <div class="box3"></div>
  </div>
  <h2>Hello Cengfan</h2>
</body>
  • 步骤4: 执行webpack查看打包结果

在?大白话跟你唠明白Webpack(基础篇)

在?大白话跟你唠明白Webpack(基础篇)

在?大白话跟你唠明白Webpack(基础篇)

可以看出样式文件已被单独打包。至此常用plugin介绍完毕。

2.7 mode模式

mode主要分为开发/生产两种环境,不同环境打包出来的代码不同。

developmentproduction
开发环境生产环境

首先看看开发环境下打包的js代码:

在?大白话跟你唠明白Webpack(基础篇)

再看看生产环境下打包的js代码:

在?大白话跟你唠明白Webpack(基础篇)

生产环境下我们还可以配合其他插件,压缩生产环境的代码。

2.8 其他配置

2.8.1 devServer

之前我们每次对项目进行修改,都要重新执行webpack打包,然后手动开启服务检查打包结果,这是非常不方便的。我们希望webpack能自动化处理这些事情,这时就需要使用devServer解决这些问题。

  • 步骤1: 安装webpack-dev-server

yarn add webpack-dev-server -D

  • 步骤2: 配置webpack.config.js
//...
module.exports = {
  //...
  devServer: {
    host: 'localhost',
    port: 8080,
    open: true,
    hot: true,
  },

  mode: 'development'
}

devServer的字段释义如下:

hostportopenhot
服务器名称端口号是否自动打开浏览器是否开启热更新
  • 步骤3: 终端输入命令webpack serve查看结果

在?大白话跟你唠明白Webpack(基础篇)

此时页面就以8080端口被启动起来了。

devServer启动的开发服务器,存在于内存中,不会往硬盘写入文件,没有输出内容。所以即使我们删除dist目录,页面还是会照常运行。

三. 尾巴

至此webpack常用的配置已列举完毕,如果你也跟着一路看下来相信基本的配置应该不成问题了。受限于篇幅,更全的配置还是推荐大家去官网查看。

后续的进阶篇,实战篇,原理篇已经在新建文件夹了,希望能尽快跟大家见面。

不知道通篇的大白话是否助你入门了webpack,如果有帮助,希望大家点赞,评论,收藏,你们的支持是我创作的最大动力。

我是来蹭饭,一个会点儿吉他和编曲,绞尽脑汁想傍个富婆的摸鱼大师,希望本次的分享对你有帮助。

在?大白话跟你唠明白Webpack(基础篇)