webpack基础篇

255 阅读10分钟

webpack

cxy.jpg

1.webpack 介绍

webpack是一个工具,可以打包 JavaScript 应用程序 (支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如: images , fonts 和 stylesheets 。 webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载和 预先加载代码文件,以便为你的项目和用户提供最佳体验。

image-20220503161337227

运行npm init ,初始化一个项目。初始化成功之后就会出现package.json文件

image-20220503192624503

安装webpcak

npm install --save-dev webpack
# 或指定版本
npm install --save-dev webpack@<version>

提示: 是否使用 --save-dev 取决于你的应用场景。假设你仅使用 webpack 进行构建 操作,那么建议你在安装时使用 --save-dev 选项,因为可能你不需要在生产环 境上使用 webpack。如果需要应用于生产环境,请忽略 --save-dev 选项

如果你使用 webpack v4+ 版本,并且想要在命令行中调用 webpack ,你还需要安装 CLI。

npm install --save-dev webpack-cli

2.第一次允许webpack

运行webpack

我们先创建一些html和js文件。

创建的目录如下:

image-20220503191932531

在js中写一些函数,然后在html中引用。

index.js

function hello(){
  console.log("hello webpack")
}
hello();

index.html

<body>
  <script src="./index.js"></script>
</body>

运行webpack

先进入项目目录,我的就是1.运行webpack这个目录。如果是全局安装的webpack,可以使用 webpack命令。如果只是局部安装,用下面命令

npx webpack

看见如下截图

image-20220503193654060

可以看见项目目录多了一个文件夹dist

image-20220503193748436

自定义 Webpack 配置

实际上, webpack-cli 给我们提供了丰富的终端命令行指令,可以通过 webpack -- help 来查看:

$  webpack -- help 
Usage: webpack [entries...] [options]
Alternative usage to run commands: webpack [command] [options]
​
The build tool for modern web applications.
​
Options:
  -c, --config <value...>                Provide path to a webpack configuration file e.g. ./webpack.config.js.
  --config-name <value...>               Name of the configuration to use.
  -m, --merge                            Merge two or more configurations using 'webpack-merge'.
  --env <value...>                       Environment passed to the configuration when it is a function.
  --node-env <value>                     Sets process.env.NODE_ENV to the specified value.
  --progress [value]                     Print compilation progress during build.
  -j, --json [value]                     Prints result as JSON or store it in a file.
  -d, --devtool <value>                  Determine source maps to use.
  --no-devtool                           Do not generate source maps.
  --entry <value...>                     The entry point(s) of your application e.g. ./src/main.js.
  --mode <value>                         Defines the mode to pass to webpack.
  --name <value>                         Name of the configuration. Used when loading multiple configurations.
  -o, --output-path <value>              Output location of the file generated by webpack e.g. ./dist/.
  --stats [value]                        It instructs webpack on how to treat the stats e.g. verbose.
  --no-stats                             Disable stats output.
  -t, --target <value...>                Sets the build target e.g. node.
  --no-target                            Negative 'target' option.
  -w, --watch                            Watch for files changes.
  --no-watch                             Do not watch for file changes.
  --watch-options-stdin                  Stop watching when stdin stream has ended.
  --no-watch-options-stdin               Do not stop watching when stdin stream has ended.Global options:
  --color                                Enable colors on console.
  --no-color                             Disable colors on console.
  -v, --version                          Output the version number of 'webpack', 'webpack-cli' and
                                         'webpack-dev-server' and commands.
  -h, --help [verbose]                   Display help for commands and options.
​
Commands:
  build|bundle|b [entries...] [options]  Run webpack (default command, can be omitted).
  configtest|t [config-path]             Validate a webpack configuration.
  help|h [command] [option]              Display help for commands and options.
  info|i [options]                       Outputs information about your system.
  serve|server|s [entries...]            Run the webpack dev server. To see all available options you need to
                                         install 'webpack', 'webpack-dev-server'.
  version|v [commands...]                Output the version number of 'webpack', 'webpack-cli' and
                                         'webpack-dev-server' and commands.
  watch|w [entries...] [options]         Run webpack and watch for files changes.
​
To see list of all supported commands and options run 'webpack --help=verbose'.
​
Webpack documentation: https://webpack.js.org/.
CLI documentation: https://webpack.js.org/api/cli/.
Made withby the webpack team.

是命令行不方便也不直观,而且还不利于保存配置的内容。因此,webpack 还给 我们提供了通过配置文件,来自定义配置参数的能力。我们在项目的目录下新建一个webpack.config.js来自定义配置webpack.

module.exports = {
  // entry为文件入口
  entry: './src/index.js',
  // 为打包输出出口
  output: {
    // 自定义输出的名字
    filename: 'bundle.js',
    // 输出文件夹必须定义为绝对路径
    path: './dist'
  },
  // 开发模式
  mode: 'none'
}

entry:是定义打包文件的入口

output: 是打包文件的出口,里面包含了filename,定义的输出打包的文件名称,path将打包文件放在那里。

在用 npx webpack来进行运行,发现报错

image-20220503205431356

这里输出的路径需要用到绝对路径,我们用path模块来获取文件的真实路径。先引入path模块,在path.resolve(__dirname,'./dist')来获得真实的路径。

const path = require("path")
module.exports = {
  // entry为文件入口
  entry: './src/index.js',
  // 为打包输出出口
  output: {
    // 自定义输出的名字
    filename: 'bundle.js',
    // 输出文件夹必须定义为绝对路径
    path: path.resolve(__dirname,'./dist')
  },
  // 开发模式
  mode: 'none'
}

再运行npx webpack,就会发现成功了。

image-20220503205927256

然后去html中将打包的index引入,运行html成功。

3.自动引入资源

到目前为止,我们都是在 index.html 文件中手动引入所有资源,然而随着应用程 序增长,如果继续手动管理 index.html 文件,就会变得困难起来。然而,通过一 些插件可以使这个过程更容易管控。通过插件来达到让打包的js文件自动引入到index.html中。

插件

插件 是 webpack 的 核心 功能。插件可以用于执行一些特定的任务,包括:打包优 化,资源管理,注入环境变量等。Webpack 自身也是构建于你在 webpack 配置中 用到的 相同的插件系统 之上! 想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。 多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而 多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。 Webpack 提供很多开箱即用的 插件。怎么去寻找这些插件呢?

[点击这里去]  www.webpackjs.com/plugins/ 

使用 HtmlWebpackPlugin

HtmlWebpackPlugin就是可以打包html文件,并且可以自动将js文件引入到html文件中。

创建2.HtmlWebpackPlugin项目,将1中的文件复制进2中。

安装插件

npm install --save-dev html-webpack-plugin

我们先将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>
</head>
<body>
  <script src="./index.js"></script>
  hello webpack
</body>
</html>

调整 webpack.config.js 文件:将插件HtmlWebpackPlugin引入项目。然后通过plugins属性引入。然后实例化插件

const path = require("path")
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist')
  },
  mode: 'none',
  plugins:[
    // 实例化插件
    new HtmlWebpackPlugin()
  ]
}

然后运行npx webpack

image-20220503212044310

打包后,我们发现这个 dist/index.html 似乎与先前的 index.html 并没有关 系, HtmlWebpackPlugin 会默认生成它自己的 index.html 文件,并且所有的 bundle(bundle.js) 会自动添加到 html 中。 能否基于原有的 index.html 文件打包生成新的 index.html 呢?可以通过阅读 HtmlWebpackPlugin 插件提供的全部的功能和选项来找到答案。

image-20220503212218205

怎么才能让打包的html不是默认的,而是我们自己的呢?

修改webpack.config.js,修改plugins中的内容

const path = require("path")
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist')
  },
  mode: 'none',
  plugins:[
    // 实例化插件
    new HtmlWebpackPlugin({
      template: './index.html', // 打包生成的html模板
      filename: 'index.html', // 打包生成的文件名,默认为app.html
      inject: 'body' // 设置资源文件注入的位置,也就是引入js引入到那个位置。
    })
  ]
}

运行npx webpack

image-20220503214152861

可以看见生成了app.index,并且这个文件就是我们之前自己写的文件。

清理dist

仔细留意一下,我们发现 dist/index.html 仍旧存在,这个文件是上次生成的残留 文件,已经没有用了。可见,webpack 将生成文件并放置在 /dist 文件夹中,但是 它不会追踪哪些文件是实际在项目中用到的。通常比较推荐的做法是,在每次构建前 清理 /dist 文件夹,这样只会生成用到的文件。让我们使用 output.clean 配置项 实现这个需求。

output: {
    // 打包前清理 dist 文件夹
    clean: true
}

4.搭建开发环境

截止目前,我们只能通过复制 dist/index.html 完整物理路径到浏览器地址栏里访 问页面。现在来看看如何设置一个开发环境,使我们的开发体验变得更轻松一些.

mode

我们之前一直忽略了一个参数,属性,这个属性就是就是mode,用来设置开发环境的。

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist'),
    clean: true
  },
  // 设置开发模式
  mode: 'development',
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html', 
      filename: 'app.html', 
      inject: 'body'
    })
  ]
}

使用 source map

当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代 码中的原始位置。例如,如果将三个源文件( a.js , b.js 和 c.js )打包到一个 bundle( bundle.js )中,而其中一个源文件包含一个错误,那么堆栈跟踪就会直 接指向到 bundle.js 。你可能需要准确地知道错误来自于哪个源文件,所以这种提 示这通常不会提供太多帮助。为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以 将编译后的代码映射回原始源代码。如果一个错误来自于 b.js ,source map 就会 明确的告诉你。 在本篇中,我们将使用 inline-source-map 选项:

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist'),
    clean: true
  },
  mode: 'development',
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html', 
      filename: 'app.html', 
      inject: 'body'
    })
  ],

  // 追踪错误
  devtool: 'inline-source-map'
}

使用 watch mode(观察模式)

在每次编译代码时,手动运行 npx webpack 会显得很麻烦。 我们可以在 webpack 启动时添加 "watch" 参数。如果其中一个文件被更新,代码将 被重新编译,所以你不必再去手动运行整个构建。

npx webpack --watch

这样就可以实现事实打包更新。唯一的缺点是,为了看到修改后的实际效果,你需要刷新浏览器。如果能够自动刷新 浏览器就更好了,因此接下来我们会尝试通过 webpack-dev-server 实现此功能。

webpack-dev-server 为你提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。先安装:

npm install --save-dev webpack-dev-server

修改配置文件,告知 dev server,从什么位置查找文件:

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist'),
    clean: true
  },
  mode: 'development',
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html', 
      filename: 'app.html', 
      inject: 'body'
    })
  ],
  devtool: 'inline-source-map',

  // 自动刷新
  devServer:{
    static: './dist'
  }
}

以上配置告知 webpack-dev-server ,将 dist 目录下的文件作为 web 服务的根目 录。提示: webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文 件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样.

运行

npx webpack serve -open

image-20220503220601246

通过 http://localhost:8080/app.indexl来访问,然后我们修改index.js中的

image-20220503221036422

通过访问返现,打包的文件也同步更新了。

image-20220503221119524

5.资源模块

目前为止,我们的项目可以在控制台上显示 "Hello world~~~"。现在我们尝试混合 一些其他资源,比如 images,看看 webpack 如何处理。 在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具来处理资源,并 将它们从 /src 文件夹移动到 /dist 或 /build 目录中。webpack 最出色的功能 之一就是,除了引入 JavaScript,还可以内置的资源模块 Asset Modules 引入任何 其他类型的文件。资源模块(asset module)是一种模块类型,它允许我们应用Webpack来打包其他资 源文件(如字体,图标等) 资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些loader:

  • asset/resource 发送一个单独的文件并导出 URL。
  • asset/inline 导出一个资源的 data URI。
  • asset/source 导出资源的源代码。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。

Resource 资源

修改webpack.config.js配置

// 配置资源文件
module: {
    rules: [{
        test: /.png/,
        type: 'asset/resource'
    }]
},
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist'),
    clean: true
  },
  mode: 'development',
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html', 
      filename: 'app.html', 
      inject: 'body'
    })
  ],
  devtool: 'inline-source-map',
  devServer:{
    static: './dist'
  },

  // 资源模块
  module: {
    rules:[
      {
        test: /.\jpg/,
        type: 'asset/resource'
      }
    ]

  }
  
}

修改index.js文件,引入一张图片资源。创建assets文件夹,将图片引入。

image-20220504100141901

import imgUrl from '../assets/cxy.jpg'
function hello(){
  console.log("hello webpack hello world")
}
hello();

let img = document.createElement("img")
img.src = imgUrl
document.body.appendChild(img)

然后运行npx webpack serve --open,我们可以看见图片资源也被加载进来了。看浏览器也有图片了

image-20220504100600343

image-20220504100759965

  • 自定义输出文件名:

默认情况下, asset/resource 模块以 contenthash[query] 文件名 发送到输出目录。 可以通过在 webpack 配置中设置 output.assetModuleFilename 来修改此模 板字符串:

 output: {
    // 设置输出图片名称
    assetModuleFilename: 'images/[contenthash][ext][query]'

  },

image-20220504101331629

同样的也可以在rules里面设置

 // 资源模块
  module: {
    rules:[
      {
        test: /.\jpg/,
        type: 'asset/resource',
        // 优先级会高于assetModuleFilename
        generator: {
          filename: 'img/[contenthash][ext][query]'
          }
      }
    ],
  }
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname,'./dist'),
    clean: true,
    // 设置输出图片名称
    assetModuleFilename: 'images/[contenthash][ext][query]'
​
  },
  mode: 'development',
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html', 
      filename: 'app.html', 
      inject: 'body'
    })
  ],
  devtool: 'inline-source-map',
  devServer:{
    static: './dist'
  },
​
  // 资源模块
  module: {
    rules:[
      {
        test: /.\jpg/,
        type: 'asset/resource',
        // 优先级会高于assetModuleFilename
        generator: {
          filename: 'img/[contenthash][ext][query]'
          }
      }
    ],
  }
}

image-20220504103521894

inline 资源

修改 webpack.config.js 配置:

  module: {
    rules:[
     
      {
        test: /.\svg/,
        type: 'asset/inline',
      }
    ],
  }

source 资源

source资源,导出资源的源代码。修改配置文件,添加:

module: {
    rules: [
        test: /.txt/,
        type: 'asset/source'
    ]
}

在assets里创建一个 hello.txt 文件

image-20220504105356896

在index.js入口文件里引入一个 .txt 文件,添加内容:

import helloTxt from '../assets/hello.txt'
const block = document.createElement('div')
block.style.cssText = `width: 200px; height: 200px; background:
aliceblue`
block.textContent = helloTxt
document.body.appendChild(block)

启动打开浏览器

image-20220504110008181

资源已经加载出来了。

通用资源类型

通用资源类型 asset , 在导出一个 data URI 和发送一个单独的文件之间自动选择。 修改配置文件:

// 资源模块
  module: {
    rules:[
      {
        test: /.jpg/,
        type: 'asset',
      }
    ],
  }

现在,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择: 小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类 型。 可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件:

// 资源模块
  module: {
    rules:[
      {
        test: /.jpg/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
          maxSize: 4 * 1024 // 4kb
          }
          }
      }
    ],
  }

6.管理资源

在上一章,我们讲解了四种资源模块引入外部资源。除了资源模块,我们还可以通过 loader引入其他类型的文件.

什么是loader

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

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader。
 module: {
    rules:[
      // loader
      {
        test: /.txt$/,
        use: 'raw-loader',
      }
    ],
  }

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属 性: test 和 use 。这告诉 webpack 编译器(compiler) 如下信息: “嘿,webpack 编译器,当你碰到「在 require() / import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”

加载CSS

为了在javaScript模块中加载css,你在添加style-loader和css-loader,并在module中添加这个loader。

npm install --save-dev style-loader css-loader

修改配置文件

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

模块 loader 可以链式调用。链中的每个 loader 都将对资源进行转换。链会逆序执 行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。 最后,webpack 期望链中的最后的 loader 返回 JavaScript。 应保证 loader 的先后顺序: 'style-loader' 在前,而 'css-loader' 在后。如果 不遵守此约定,webpack 可能会抛出错误。webpack 根据正则表达式,来确定应该 查找哪些文件,并将其提供给指定的 loader。在这个示例中,所有以 .css 结尾的 文件,都将被提供给 style-loader 和 css-loader 。 这使你可以在依赖于此样式的 js 文件中 import './style.css' 。现在,在此模块 执行过程中,含有 CSS 字符串的

image-20220504144726740

.hello{
  background: #000;
  width: 100px;
  height: 100px;
}

将css引入到index.js中

import './index.css'
document.body.classList.add("hello")

启动服务器发现背景加上去了npx webpack serve --open。现有的 loader 可以支持任何你可以想到的 CSS 风格 - sass 和 less 等。安装lessloader:

npm install less less-loader --save-dev

修改配置文件:

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

抽离和压缩CSS

在多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还 可以将CSS文件抽离成一个单独的文件。实现这个功能,需要 mini-css-extractplugin 这个插件来帮忙。安装插件:

npm install mini-css-extract-plugin --save-dev

本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文 件,并且支持 CSS 和 SourceMaps 的按需加载。 本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作。 之后将 loader 与 plugin 添加到你的 webpack 配置文件中:

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
​
 plugins:[
    new MiniCssExtractPlugin()
   
  ],
module: {
    rules:[
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader,'css-loader']
      }
    ],
  }

进行编译:

npx webpack

image-20220504151422346

单独的 mini-css-extract-plugin 插件不会将这些 CSS 加载到页面中。这里 htmlwebpack-plugin 帮助我们自动生成 link 标签或者在创建 index.html 文件时使 用 link 标签。

image-20220504151512814

这时, link 标签已经生成出来了,把我们打包好的 main.css 文件加载进来。我们 发现, main.css 文件被打包抽离到 dist 根目录下,能否将其打包到一个单独的文 件夹里呢?修改配置文件:

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

然后运行可以发现css文件已经单独出来了。

image-20220504151937475

打开这个打包的css文件,发现并没有压缩和优化 。

/*!******************************************************************!*\
  !*** css ../node_modules/css-loader/dist/cjs.js!./src/index.css ***!
  ******************************************************************/
.hello{
  background: #000;
  width: 100px;
  height: 100px;
}

/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3NzLzMxZjI5NDM5YWNjZjNhMDk3NDQxLmNzcyIsIm1hcHBpbmdzIjoiOzs7QUFBQTtFQUNFLGdCQUFnQjtFQUNoQixZQUFZO0VBQ1osYUFBYTtBQUNmLEMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9zcmMvaW5kZXguY3NzIl0sInNvdXJjZXNDb250ZW50IjpbIi5oZWxsb3tcbiAgYmFja2dyb3VuZDogIzAwMDtcbiAgd2lkdGg6IDEwMHB4O1xuICBoZWlnaHQ6IDEwMHB4O1xufSJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==*/

为了压缩输出文件,我们用css-minimizer-webpack-plugin插件来压缩

npm install -S-D css-minimizer-webpack-plugin

修改配置文件

// 压缩css
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
 // 优化配置
optimization: {
    minimizer: [
        new CssMinimizerPlugin(),
    ],
}

再次运行发现,css已经被压缩了

image-20220504152854131

加载 images 图像

假如,现在我们正在下载 CSS,但是像 background 和 icon 这样的图像,要如何处 理呢?在 webpack 5 中,可以使用内置的 Asset Modules,我们可以轻松地将这些 内容混入我们的系统中,这个我们在"资源模块"一节中已经介绍了。这里再补充一个 知识点,在 css 文件里也可以直接引用文件,修改 index.css 和入口 index.js :

index.css

.hello{
  /* background: #000; */
  width: 100px;
  height: 100px;
  background-image: url(../assets/cxy.jpg);
}

启动服务,打开浏览器

加载 fonts 字体

那么,像字体这样的其他资源如何处理呢?使用 Asset Modules 可以接收并加载任 何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文 件,也包括字体。让我们更新 webpack.config.js 来处理字体文件:

module: {
    rules: [
        {
            test: /.(woff|woff2|eot|ttf|otf)$/i,
            type: 'asset/resource',
        },
    ]
}

加载数据

此外,可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json' 默认将正常运行。要导入 CSV、TSV 和 XML,你可以使用 csvloader 和 xml-loader。让我们处理加载这三类文件:

npm install --save-dev csv-loader xml-loader

添加配置

module: {
    rules: [
        {
            test: /.(csv|tsv)$/i,
            use: ['csv-loader'],
        },
        {
            test: /.xml$/i,
            use: ['xml-loader'],
        },
    ]
}

现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所 导入的 Data 变量,将包含可直接使用的已解析 JSON

7.使用 babel-loader

前面的章节里,我们应用 less-loader 编译过 less 文件,应用 xml-loader 编译过 xml 文件,那 js 文件需要编译吗?我们来做一个实验,修改 index.js 文 件:

function getString() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello world~~~')
        }, 2000)
    })
}
async function helloWorld() {
    let string = await getString()
    console.log(string)
}

我们编译执行之后发现es6的代码原样输出了。

image-20220504155429523

webpack 自身可以自动加载JS文件,就像加载JSON文件一样,无需任何 loader。可 是,加载的JS文件会原样输出,即使你的JS文件里包含ES6+的代码,也不会做任何的 转化。这时我们就需要Babel来帮忙。Babel 是一个 JavaScript 编译器,可以将 ES6+转化成ES5。在Webpack里使用Babel,需要使用 babel-loader 。

安装babel-loader

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

babel-loader : 在webpack里应用 babel 解析ES6的桥梁

@babel/core : babel核心模块

@babel/preset-env : babel预设,一组 babel 插件的集合

在 webpack 配置中,需要将 babel-loader 添加到 module 列表中,就像下面这 样:

module: {
    rules: [
        {
            test: /.js$/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env']
                }
            }
        }
    ]
}

运行之后可以看出 async/await 的ES6语法被 babel 编译了。

regeneratorRuntime 插件

regeneratorRuntime 是webpack打包生成的全局辅助函数,由babel生成,用于兼 容async/await的语法。 regeneratorRuntime is not defined 这个错误显然是未能正确配置babel。 正确的做法需要添加以下的插件和配置

# 这个包中包含了regeneratorRuntime,运行时需要
npm install --save @babel/runtime
# 这个插件会在需要regeneratorRuntime的地方自动require导包,编译时需要
npm install --save-dev @babel/plugin-transform-runtime
# 更多参考这里
https://babeljs.io/docs/en/babel-plugin-transform-runtime
module.exports = {
    //...
    // 配置资源文件
    module: {
        rules: [{
            test: /.js$/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['@babel/preset-env'],
                    plugins: [
                        [
                            '@babel/plugin-transform-runtime'
                        ]
                    ]
                }
            }
        },
                //...
               ],
    },
    //...
} 

8.代码分离

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。 常用的代码分离方法有三种:

入口起点:使用 entry 配置手动地分离代码。

防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。

动态导入:通过模块的内联函数调用来分离代码

入口起点

这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一 些隐患,我们将会解决这些问题。先来看看如何从 main bundle 中分离 another module(另一个模块):

我们在src下创建另一个js文件

import _ from 'lodash'
console.log(_.join(['Another', 'module', 'loaded!'], ' '))

这个模块依赖了 lodash ,需要安装一下:

npm install lodash --save-dev

修改配置文件:

module.exports = {
    entry: {
        index: './src/index.js',
        another: './src/another-module.js',
    },
    output: {
        filename: '[name].bundle.js'
    },
​
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname,'./dist'),
        clean: true,
        assetModuleFilename: 'images/[contenthash][ext][query]'
    },
}

将其他的模块打包到anthor模块。

编译执行:

npx webpack

可以看见,dist里面多了一个anthor.js

image-20220504161813904

如果我们在index.js中也倒入lodash这个模块,我们在打包的时候,就会重复的打包,这样做肯定是不符合我们的要求的。如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。 以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash ,这样就在两个 bundle 中造成重复引用。

防止重复

  • 入口依赖 配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块
module.exports = {
    entry: {
        index: {
            import: './src/index.js',
            dependOn: 'shared',
        },
        another: {
            import: './src/another-module.js',
            dependOn: 'shared',
        },
        shared: 'lodash',
    }
}
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
  // entry: './src/index.js',
  // entry:{
  //   index: './src/index.js',
  //   anthor: './src/an.js'
  // },
​
  entry:{
    index:{
      import: './src/index.js',
      dependOn: 'shared'
​
    },
    anthor:{
      import: './src/an.js',
      dependOn: 'shared'
    },
    shared:"lodash"
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname,'./dist'),
    clean: true,
    assetModuleFilename: 'images/[contenthash][ext][query]'
  },
  mode: 'production',
  plugins:[
    new HtmlWebpackPlugin({
      template: './src/index.html', 
      filename: 'app.html', 
      inject: 'body'
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[contenthash].css'
    }),
  
   
  ],
  devtool: 'inline-source-map',
  devServer:{
    static: './dist'
  },
  module: {
    rules:[
      {
        test: /.jpg/,
        type: 'asset/resource',
        generator: {
          filename: 'img/[contenthash][ext][query]'
          }
      },
      {
        test: /.svg/,
        type: 'asset/inline',
      },
      {
        test: /.txt/,
        type: 'asset/source',
      },
      {
        test: /.jpg/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
          maxSize: 4 * 1024 // 4kb
          }
          }
      },
      // {
      //   test: /.css$/,
      //   use:  ['style-loader', 'css-loader','less-loader'],
      // },
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader,'css-loader']
      },
      {
        test: /.js$/,
        // exclude: /node_modules/,
        // use: {
        //     loader: 'babel-loader',
        //     options: {
        //         presets: ['@babel/preset-env']
        //     }
        // },
        // 
      //   use: {
      //     loader: 'babel-loader',
      //     options: {
      //         presets: ['@babel/preset-env'],
      //         plugins: [
      //             [
      //                 '@babel/plugin-transform-runtime'
      //             ]
      //         ]
      //     }
      // }
    }
    ],
  },
    optimization: {
      minimizer: [
        new CssMinimizerPlugin(),
      ],
      },
    
}

运行发现就会多一个打包的js出来,将全部模块公共的东西放在这个js中

image-20220504162916117

  • SplitChunksPlugin SplitChunksPlugin

插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重 复的 lodash 模块去除:

entry: {
    index: './src/index.js',
    another: './src/another-module.js'
},
//提取公共的部分    
    optimization: {
        splitChunks: {
            chunks: 'all',
        },
    },

image-20220504163225332

9.缓存

我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个 可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中 的内容部署到 server 上,client(通常是浏览器)就能够访问此 server 的网站及其 资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而, 如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就 会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘 手。本节通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文 件内容变化后,能够请求到新的文件。

输出文件的文件名

我们可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的 名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,通 过带括号字符串来模板化文件名。其中, [contenthash] substitution 将根据资源 内容创建出唯一 hash。当资源内容发生变化时, [contenthash] 也会发生变化。

修改配置文件:

module.exports = {
    output: {
        filename: '[name].[contenthash].js',
    },
};

缓存第三方库

将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较 推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上 步骤,利用 client 的长效缓存机制,命中缓存来消除请求,并减少向 server 获取资 源,同时还能保证 client 代码和 server 代码版本一致。 我们在 optimization.splitChunks 添加如下 cacheGroups 参数并构建:

optimization:{
    splitChunks: {
        cacheGroups: {
            vendor: {
                test: /[\/]node_modules[\/]/,
                    name: 'vendors',
                        chunks: 'all',
            },
        },
    },
}

将 js 文件放到一个文件夹中

目前,全部 js 文件都在 dist 文件夹根目录下,我们尝试把它们放到一个文件夹中, 这个其实也简单,修改配置文件:

output: {
    filename: 'scripts/[name].[contenthash].js',
},
​

10.拆分开发环境和生产环境配置

现在,我们只能手工的来调整 mode 选项,实现生产环境和开发环境的切换,且很多 配置在生产环境和开发环境中存在不一致的情况,比如开发环境没有必要设置缓存, 生产环境还需要设置公共路径等等。 本节介绍拆分开发环境和生产环境,让打包更灵活。

publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所 有资源的基础路径。 基于环境设置 在开发环境中,我们通常有一个 assets/ 文件夹,它与索引页面位于同一级 别。这没太大问题,但是,如果我们将所有静态资源托管至 CDN,然后想在生 产环境中使用呢? 想要解决这个问题,可以直接使用一个 environment variable(环境变量)。假设 我们有一个变量 ASSET_PATH

import webpack from 'webpack';
// 尝试使用环境变量,否则使用根路径
const ASSET_PATH = process.env.ASSET_PATH || '/';
export default {
    output: {
        publicPath: ASSET_PATH,
    },
    plugins: [
        // 这可以帮助我们在代码中安全地使用环境变量
        new webpack.DefinePlugin({
            'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH),
        }),
    ],
}

Automatic publicPath

有可能你事先不知道 publicPath 是什么,webpack 会自动根据 import.meta.url 、 document.currentScript 、 script.src 或者 self.location 变量设置 publicPath。你需要做的是将 output.publicPath 设为 'auto' :

module.exports = {
    output: {
        publicPath: 'auto',
    },
};

请注意在某些情况下不支持 document.currentScript ,例如:IE 浏览器,你 不得不引入一个 polyfill,例如 currentScript Polyfill 。

环境变量

webpack 命令行 环境配置 的 --env 参数,可以允许你传入任意数量的环境变量。而 在 webpack.config.js 中可以访问到这些环境变量。例如, --env production 或 --env goal=local 。

npx webpack --env goal=local --env production --progress

对于我们的 webpack 配置,有一个必须要修改之处。通常, module.exports 指向 配置对象。要使用 env 变量,你必须将 module.exports 转换成一个函数:

//...
module.exports = (env) => {
    return {
        //...
        // 根据命令行参数 env 来设置不同环境的 mode
        mode: env.production ? 'production' : 'development',
        //...
    }
}

拆分配置文件

目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到 不同的配置文件中。如 webpack.config.dev.js (开发环境配置)和 webpack.config.prod.js (生产环境配置)。在项目根目录下创建一个配置文件 夹 config 来存放他们。

webpack.config.dev.js 配置如下:

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extractplugin')
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
module.exports = {
    entry: {
        index: './src/index.js',
        another: './src/another-module.js'
    },
    output: {
        filename: 'scripts/[name].js',
        path: path.resolve(__dirname, './dist'),
        clean: true,
        assetModuleFilename: 'images/[contenthash][ext]'
    },
    mode: 'development',
    devtool: 'inline-source-map',
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'app.html',
            inject: 'body'
        }),
        new MiniCssExtractPlugin({
            filename: 'styles/[contenthash].css'
        })
    ],
    devServer: {
        static: './dist'
    },
    module: {
        rules: [
            {
                test: /.png$/,
                type: 'asset/resource',
                generator: {
                    filename: 'images/[contenthash][ext]'
                }
            },
            {
                test: /.svg$/,
                type: 'asset/inline'
            },
            {
                test: /.txt$/,
                type: 'asset/source'
            },
            {
                test: /.jpg$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        maxSize: 4 * 1024 * 1024
                    }
                }
            },
            {
                test: /.(css|less)$/,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'lessloader']
            },
            {
                test: /.(woff|woff2|eot|ttf|otf)$/,
                type: 'asset/resource'
            },
            {
                test: /.(csv|tsv)$/,
                use: 'csv-loader'
            },
            {
                test: /.xml$/,
                use: 'xml-loader'
            },
            {
                test: /.toml$/,
                type: 'json',
                parser: {
                    parse: toml.parse
                }
            },
            {
                test: /.yaml$/,
                type: 'json',
                parser: {
                    parse: yaml.parse
                }
            },
            {
                test: /.json5$/,
                type: 'json',
                parser: {
                    parse: json5.parse
                }
            },
            {
                test: /.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        plugins: [
                            [
                                '@babel/plugin-transform-runtime'
                            ]
                        ]
                    }
                }
            }
        ]
    },
    optimization: {
        splitChunks: {
            cacheGroups: {
                vendor: {
                    test: /[\/]node_modules[\/]/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    }
}

合并配置文件

npm install webpack-merge -D
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const productionConfig = require('./webpack.config.prod.js')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
    switch(true) {
        case env.development:
            return merge(commonConfig, developmentConfig)
        case env.production:
            return merge(commonConfig, productionConfig)
        default:
            throw new Error('No matching configuration was found!');
    }}