webpack入门篇

414 阅读9分钟

前言

无论你是使用Vue还是使用React,它们的cli都是选用webpack作为构建工具。而在平常的开发中我们也需要自己配置一些与webpack相关的配置项,所以学习webpack是很有必要的。

1. 什么是webpack

webpack是一个用于现代JavaScript应用程序的静态模块打包工具,其最为核心的功能是解决模块之间的依赖,将各个模块按照特定的规划和顺序组织在一块,最终合并成一个或多个静态文件,以供浏览器加载和运行。

我们可以将webpack理解为一个模块处理工厂,并且我们可以按照自己的想法设计这个工厂。然后将我们编写的源代码交给webpack,它将会按照我们的设想进行加工、拼装处理,产出最终的资源文件。

2. 为什么使用webpack

  1. 快速开发:当一个项目落到你手上时,我们不可能从零开始搭建项目。而是使用成熟的框架和工程化的工具链进行开发,而webpack就是一个优秀的工程化打包工具。
  2. 强大的生态:webpack 拥有一个强大且活跃的生态系统,有大量的插件、加载器和工具可供选择。
  3. 主流框架的脚手架:许多主流的前端框架和工具链,如 React、Vue、Angular 等,都使用 webpack 作为构建工具和打包工具。学习 webpack 可以使你更好地理解和使用这些框架的脚手架工具。

3. 如何使用webpack

接下来我将会讲述有关webpack的一些基础知识,并且一边敲一边编写下面的内容,所以如果你也不是很熟悉的话建议一起敲一遍。

3.1 准备

首先新建一个文件夹webpack-exercise

cd webpack-exercise

初始化项目

npm init -y

安装webpack

npm install webpack@5.87.0 webpack-cli@5.1.4 -D

新建srcconfig文件夹分别做为项目的根目录和有关webpack配置的目录,然后在src下建立一个index.js文件做为程序的入口,在config下建立webpack.config.js文件做为webpack的配置文件。具体目录如下:

|-- package-lock.json',
|-- package.json',
|-- config',
|   |-- webpack.config.js',
|-- src',
    |-- index.js',

3.2 context(上下文)

传送门

type:string

default:配置文件所在目录

作用:指定 webpack 在解析模块时的基础目录。

显然我们这里不能将config目录作为基础目录,而是应该将src作为基本目录。所以我们需要通过context来指定我们的基本目录。代码如下:

// webpack.config.js
const path = require('path');

module.exports = {
  context: path.resolve(__dirname, '../src'),
};

3.3 entry(入口)

传送门

type:string|string[]|object

default:./src/index.js

作用:指定 webpack 构建过程中的入口文件或入口模块的配置选项。它用于告诉 webpack 从哪个文件开始构建依赖图,并生成打包后的输出文件。

entry支持单入口文件多入口文件,这里就不演示多入口文件了。

// index.js
// 编写测试代码
console.log("Hello Webpack!")

配置webpack指定入口文件

// webpack.config.js
const path = require('path');

module.exports = {
  context: path.resolve(__dirname, '../src'),
  entry: './index.js',
};

编写打包命令,

// package.json
"scripts": {
   // webpack是webpack的打包命令,后面的是指定webpack的配置文件位置
  "build": "webpack --config ./config/webpack.config.js"
},

然后执行打包命令,最后会生成一个dist文件夹和一个main.js文件。

npm run build

3.4 占位符与hash值

使用[]括起来的就是占位符,具体占位符如下:

占位符解释
name名称
ext文件后缀名
hash表示构建过程中整个编译的哈希值
chunkhash表示特定块(Chunk)的哈希值
contenthash表示特定文件的哈希值

使用hash值的原因是为了解决浏览器缓存的问题,当文件名没有变化时,浏览器会使用缓存的文件,这在开发中可能导致问题,因为新的更改可能不会被及时加载。

  • hash:任何一个文件改动,整个项目的构建hash值都会改变。
  • chunkhash:每个块的哈希值独立于其他块,只有当该块的内容发生变化时,才会更改对应模块的哈希值。
  • contenthash:只有当文件的内容发生变化时,才会更改对应文件的哈希值。

3.5 output(输出)

传送门

type:object

default:与src目录同级的dist目录

作用:用于指定构建过程中生成的打包文件的配置选项。它告诉 webpack 如何将打包后的代码输出到文件系统中。

常用配置项:

选项描述
path打包后输出的文件目录
filename输出文件的名称
publicPath指定输出文件的公共路径,用于指定在浏览器中引用这些文件的 URL 地址
// webpack.config.js
module.exports = {
  context: path.resolve(__dirname, '../src'),
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, '../build'), // 输出文件目录
    filename: '[name].[hash:6].js',// 输出文件名
  },
}

打包后的文件目录结构如下:

|-- package-lock.json',
|-- package.json',
|-- build',
|   |-- main.cb5bac.js',
|-- config',
|   |-- webpack.config.js',
|-- src',
    |-- index.js',

3.6 loader

传送门

简介:webpcak中的loader是用于对文件进行预处理的。它们用于将不同类型的文件转换为模块,以便在应用程序中使用。

3.6.1 引入css

传送门

作用:css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样。

首先我们在src目录下新建一个css目录,然后再css目录下新建一个index.css文件,并编写测试代码。

// index.css
body {
  margin: 0px;
  padding: 0px;
}

然后在index.js中引入css。

import './css/index.css';

console.log('Hello Webpack!');

然后打包后报如下错误:

image.png

报错的原因是webpack默认只能解析jsjson文件,如果想要解析css文件需要使用对应的loader对其进行预处理。

npm install css-loader style-loader -D

然后配置webpack

  • css-loader:处理 CSS 文件的加载和转换

  • style-loader:将 CSS 代码注入到页面中

const path = require('path');

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,// 匹配到的css文件使用下面loader进行预编译
        use: ['style-loader', 'css-loader'],// 从右向左进行解析
      },
    ],
  },
};

打包之后,可以在与src目录同级下建立一个index.html将打包后的js文件引入然后运行查看css是否生效了。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./build/main.508656.js"></script>
  </head>
  <body></body>
</html>

image.png

3.6.2 引入sass与less

想要sassless都起作用无非就是多做一次预处理。将其转换为css然后由css-loader进行加载和转换,最后由style-loadercss注入到页面中。

npm install less sass -D

src目录下新建index.lessindex.scss文件,并编写测试代码进行测试

// index.less
body{
  font-size: 20px;
}
// index.scss
body{
  font-weight: 800;
}
// index.js
import './css/index.css';
import './css/index.less';
import './css/index.scss';

console.log('Hello Webpack!');
// 目录结构
|-- index.html',
|-- package-lock.json',
|-- package.json',
|-- build',
|   |-- main.47c1b9.js',
|-- config',
|   |-- webpack.config.js',
|-- src',
    |-- index.js',
    |-- css',
        |-- index.css',
        |-- index.less',
        |-- index.scss',

安装lesssass对应的预处理loader

npm install less-loader sass-loader -D

配置webpack

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader'],
      },
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
    ],
  },
};

image.png

3.6.3 打包图片和文字

传送门

webpack5 新增资源模块,它允许使用资源文件(字体,图标等)而无需配置额外 loader

模块类型作用
asset/resource将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能
asset/inline将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能
asset/source将资源导出为源码(source code). 类似的 raw-loader 功能
asset会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource

首先在src目录下新建一个assets文件夹用于存放静态资源,然后在assets分别创建imagesfonts目录用于存放图片和字体资源,并放入一张图片和一个字体资源。 字体资源可以去阿里巴巴图标库中寻找。

引入图片

/* index.css */
body {
  margin: 0px;
  padding: 0px;
  transform: translate(0 , 0);
  background-image: url(../assets/images/logo.jpg);
}

引入字体

// index.js
import './assets/fonts/iconfont.css';
...
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div class="iconfont icon-remen"></div>
  </body>
</html>

配置webpack

module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif)$/,
        type: 'asset',
        generator: {
          // 文件的打包路径的文件名
          filename: 'assets/images/[name][hash:4][ext]',
        },
        parser: {
          dataUrlCondition: {
            //超过4kb不转 base64
            maxSize: 4 * 1024,
          },
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        type: 'asset',
        generator: {
          filename: 'assets/fonts/[name][hash:4][ext]',
        },
        parser: {
          dataUrlCondition: {
            maxSize: 4 * 1024,
          },
        },
      },
    ],
  },
};

打包后的目录结构如下:

|-- index.html
|-- package-lock.json
|-- package.json
|-- build
|   |-- index.html
|   |-- main.e049a3.js
|   |-- assets
|   |   |-- fonts
|   |   |   |-- iconfont201a.ttf
|   |   |-- images
|   |       |-- logo26bd.jpg
|-- config
|   |-- webpack.config.js
|-- src
    |-- index.js
    |-- assets
    |   |-- fonts
    |   |   |-- iconfont.css
    |   |   |-- iconfont.js
    |   |   |-- iconfont.json
    |   |   |-- iconfont.ttf
    |   |   |-- iconfont.woff
    |   |   |-- iconfont.woff2
    |   |-- images
    |       |-- logo.jpg
    |-- css
        |-- index.css
        |-- index.less
        |-- index.scss

image.png

3.6.4 为css添加浏览器前缀

传送门

为了让打包后的css能够兼容多个浏览器,这时候我们可以通过使用插件autoprefixer来兼容其它浏览器。

npm install postcss-loader autoprefixer -D
// index.css
body {
  margin: 0px;
  padding: 0px;
  transform: translate(0 , 0);
}

配置webpack

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  [
                    'autoprefixer',
                    {
                      // css需要加前缀的浏览器版本
                      overrideBrowserslist: [
                        'last 10 Chrome versions',
                        'last 5 Firefox versions',
                        'Safari >= 6',
                        'ie > 8',
                      ],
                    },
                  ],
                ],
              },
            },
          },
        ],
      },
    ],
  },
};

上面的overrideBrowserslist指定的浏览器版本我们还可以在package.json.browserslistrc文件中配置。如下:

// package.json
"browserslist":[
  "last 10 Chrome versions",
  "last 5 Firefox versions",
  "Safari >= 6",
  "ie > 8"
]

还有比较推荐的一种是在与src同级目录下新建一个.browserslistrc文件,在里面进行配置。

last 10 Chrome versions
last 5 Firefox versions
Safari >= 6
ie > 8

image.png

3.6.5 JS兼容

传送门

为了使我们打包后的代码能够兼容低版本的浏览器,所以我们可以通过babel进行处理。

npm install -D babel-loader @babel/core @babel/preset-env

index.js文件中添加需要兼容的js代码

// index.js
import './css/index.css';
import './css/index.less';
import './css/index.scss';

console.log('Hello Webpack!');
console.log([1, 2, 3].map((n) => n + 1));

配置webpack

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        // node_modules不需要进行js转化
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};

image.png

上面的babel-loader只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换 例如(map、promise、Generator、Set、Maps、Proxy等)。此时我们需要借助babel-polyfill来帮助我们转换

npm install -S @babel/polyfill

配置webpack

// webpack.config.js
module.exports = {
  entry: ['@babel/polyfill', './index.js'],
}

打包后

image.png

image.png

3.6.6 CSS兼容

传送门

postcss-preset-env让你可以将现代 CSS 转换成大多数浏览器可以理解的内容,根据您的目标浏览器或运行时环境确定您需要的 polyfill。

npm install postcss-preset-env -D

CSS设置16进制颜色的时候一般跟#加六位数字,但是下面写8位数字在谷歌浏览器中也能生效。但是一些低版本的浏览器却无法解析。

/* index.css */
body {
  color: #12345678;
  ...
}

在与src同级目录下新建postcss.config.js文件,并且配置如下:

// postcss.config.js
module.exports = {
  plugins: [require('postcss-preset-env')],
};

打包后发现将16进制颜色转为了rgba

image.png

postcss-preset-env中自带autoprefixer,验证如下:

npm uninstall autoprefixer

修改webpack.config.js配置

// webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          // {
          //   loader: 'postcss-loader',
          //   options: {
          //     postcssOptions: {
          //       plugins: [['autoprefixer']],
          //     },
          //   },
          // },
        ],
      },
    ],
  },
};

打包查看打包后的css文件,发现也有浏览器前缀。

image.png

3.6.7 引入Vue

传送门

npm install vue@2.6.14
npm install vue-template-compiler@2.6.14 vue-loader@15.9.8
  • vue-template-compiler:用于将Vue模板转换为渲染函数
  • vue-loader:用于解析.vue文件

注意

  1. vue-template-compilervue的版本需要一致
  2. 在编译.vue文件时,还需要一个VueLoaderPlugin插件。而这个插件来自于vue-loader/lib/plugin这个文件下,查看仓库发现自从@16版本开始就没有lib文件夹了。所以这里我选择了@15.9.8版本。

src目录下新建App.vue文件,用于测试

// App.vue
<template>
  <div class="App">
    {{ message }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello World!',
    };
  },
};
</script>

引入

// index.js
import App from './App.vue';
import Vue from 'vue';

new Vue({
  render: (h) => h(App),
}).$mount('#app');

配置webpack

// webpack.config.js

const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
  ],
};

打包后运行

image.png

3.7 plugins

传送门

Webpack 拥有丰富的插件接口。webpack 自身的大部分功能都使用这些插件接口。这使得 webpack 很灵活

3.7.1 html-webpack-plugin

传送门

不便:打包之后的js文件需要我们手动插入到html中这样很麻烦!我们可以通过html-webpack-plugin来准备一个html模板。它会将打包好的JS文件自动插入页面中,还可以灵活的自定义一些模板中的内容。

npm install html-webpack-plugin -D

配置webpack

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({
      // 指定模板
      template: path.resolve(__dirname, '../index.html'),
      // 要用于生成的HTML文档的标题
      title: 'webpack-exercise',
    }),
  ],
};
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body></body>
</html>

打包后发现JS会被自动引入到模板中

image.png

3.7.2 自动清除打包目录

传送门

不便:到这里你应该发现了,每当我们修改index.js文件中的代码,然后打包就会生成新的打包文件。而不是覆盖掉之前打包的文件,为了维护打包后项目的整洁我们每次都是手动删除。

例如我在index.js中新增了一行代码,然后打包。

console.log('这是新增的一行代码');

image.png

使用插件clean-webpack-plugin就能解决上面那一问题。

npm install clean-webpack-plugin -D

配置webpack

// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  plugins: [
    new CleanWebpackPlugin(),
  ],
};

再一次打包发现之前的旧文件就被清除了。

image.png

3.7.3 拆分css

传送门

我们之前引入的css都是被打包到JS文件中,这样使得打包后的文件既有逻辑代码又有CSS代码。则显然不是很好,所以我们可以通过插件mini-css-extract-plugin将css拆分出来。

npm install mini-css-extract-plugin -D

配置webpack

// webpcak.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        // MiniCssExtractPlugin.loader用于提取 CSS 代码到单独文件的加载器
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
      },
      {
        test: /\.scss$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      // 打包在css文件中
      filename: 'css/[name].[hash:6].css',
    }),
  ],
};

打包之后的目录结构如下:

|-- .browserslistrc
|-- index.html
|-- package-lock.json
|-- package.json
|-- build
|   |-- index.html
|   |-- main.e049a3.js
|   |-- assets
|   |   |-- fonts
|   |   |   |-- iconfont201a.ttf
|   |   |-- images
|   |       |-- logo26bd.jpg
|   |-- css
|       |-- main.e049a3.css
|-- config
|   |-- webpack.config.js
|-- src
    |-- index.js
    |-- assets
    |   |-- fonts
    |   |   |-- iconfont.css
    |   |   |-- iconfont.js
    |   |   |-- iconfont.json
    |   |   |-- iconfont.ttf
    |   |   |-- iconfont.woff
    |   |   |-- iconfont.woff2
    |   |-- images
    |       |-- logo.jpg
    |-- css
        |-- index.css
        |-- index.less
        |-- index.scss

3.8 mode

传送门

type:string

作用:用于指定webpack的构建模式

选项描述
development开发者模式,重点是提供更好的开发体验和调试能力
production生产者模式,重点是生成优化的、适用于生产环境的最终输出文件
none不使用任何默认优化选项

你会每次打包都会有一个警告,这个警告就是提示你没有指定webpack的打包模式。

image.png

指定打包模式

// webpack.config.js
module.exports = {
  mode: 'development',
}

3.8.1 devServer

传送门

npm install webpack-dev-server -D

配置打包命令

"scripts": {
   ...
   // webpack serve是启动webpack-dev-server的指令
  "dev": "webpack serve --config ./config/webpack.config.js"
},

执行

npm run dev

image.png

3.8.2 devtool

传送门

作用:用于增强调试过程

devtool的属性值非常多,这里就不一一介绍了。可以去官网上查看每个值的表现,这里只给予个人推荐。

开发环境eval-cheap-module-source-mapeval-cheap-source-map

  • 原因:在开发环境时,注重的是构建速度错误定位。而上面的两个值都比较合适。

生产环境none

  • 原因:none的构建和重构速度最快,并且不希望别人看到源码。

配置webpack

module.exports = {
  ...
  devtool: 'eval-cheap-module-source-map',
};

index.js中报错

// index.js
console.log(a);

image.png image.png

3.8.3 环境区分

在开发中通常分为开发环境生产环境,不同的环境采用不同的打包方式。所以我们需要准备两种相应合适的打包配置。

webpack-merge用于合并webpack的配置文件。

npm install webpack-merge -D

修改打包命令

"scripts": {
  "build": "webpack --config ./config/webpack.prod.js --mode production",
  "dev": "webpack serve --config ./config/webpack.dev.js --mode development"
},

webpack.config.js改为webpack.common.js用于编写公共配置,如下:

// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const isDevMode = process.argv.includes('development');

module.exports = {
  context: path.resolve(__dirname, '../src'),
  entry: ['@babel/polyfill', './index.js'],
  output: {
    path: path.resolve(__dirname, '../build'),
    filename: '[name].[contenthash].js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
      {
        test: /\.css$/,
        use: [
          isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader, 
          'css-loader', 
          'postcss-loader'
        ],
      },
      {
        test: /\.less$/,
        use: [
          isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader, 
          'css-loader', 
          'less-loader', 
          'postcss-loader'
        ],
      },
      {
        test: /\.scss$/,
        use: [
          isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,  
          'css-loader', 
          'sass-loader', 
          'postcss-loader'
        ],
      },
      {
        test: /\.vue$/,
        use: ['vue-loader'],
      },
      {
        test: /\.(jpe?g|png|gif)$/,
        type: 'asset',
        generator: {
          filename: 'assets/images/[name][contenthash][ext]',
        },
        parser: {
          dataUrlCondition: {
            //超过4kb不转 base64
            maxSize: 4 * 1024,
          },
        },
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        type: 'asset',
        generator: {
          filename: 'assets/fonts/[name][contenthash][ext]',
        },
        parser: {
          dataUrlCondition: {
            maxSize: 2 * 1024,
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'webpack-exercise',
      template: path.resolve(__dirname, '../index.html'),
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css',
    }),
    new VueLoaderPlugin(),
  ],
};

在config目录下新建webpack.dev.js用于编写开发环境的配置。

// webpack.dev.js
const commonConfig = require('./webpack.common');
const { merge } = require('webpack-merge');

module.exports = merge(commonConfig, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map',
  // 用于开发中跨域代理
  devServer: {
    hot: true,
    port: 9000,
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:4523',
        changeOrigin: true,
        pathRewrite: {
          '^/api': '',
        },
      },
    },
  },
});

在config目录下新建webpack.prod.js用于编写开发环境的配置。

// webpack.prod.js
const commonConfig = require('./webpack.common');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { merge } = require('webpack-merge');

module.exports = merge(commonConfig, {
  mode: 'production',
  //生产环境需要进行优化构建输出、压缩文件、优化性能等。
});

基础篇就到这里了,进阶篇主要以各种优化为主,用于生产环境之中。