Webpack5学习 --- 资源模块类型和常见的plugins

1,345 阅读3分钟

asset module type

在webpack5之前,加载文件资源我们需要使用一些loader,比如raw-loader 、url-loader、file-loader

在webpack5之后,我们可以直接使用资源模块类型(asset module type),来替代上面的这些loader

资源模块名功能
asset/resource发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现
asset/inline导出一个资源的 data URI。之前通过使用 url-loader 实现
asset/source导出资源的源代码。之前通过使用 raw-loader 实现
asset在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现
{
  test: /\.(png|jpe?g|gif|svg)/,
  type: 'asset/inline'
}
{
  test: /\.(png|jpe?g|gif|svg)/,
  type: 'asset/resource',

  generator: {
    // tips: 这里ext是有点的,这和file-loader是不一样的
    filename: 'imgs/[name].[hash:5][ext]' 
  }
}
{
  test: /\.(png|jpe?g|gif|svg)/,
    type: 'asset',

    generator: {
      filename: 'imgs/[name].[hash:5][ext]'
    },

    parser: { 
      // 这里和file-loader的limit配置项是一致的
      dataUrlCondition: {
        maxSize: 100 * 1024
      }
   }
}

除了上述配置外,还有一种可以全局配置资源存放位置和名称的方式

output: { // 在顶层output中进行设置
  path: path.resolve(__dirname, 'dist'),
  filename: 'main.js',
  // 一般不用,因为这是全局配置,一般需要对具体的资源进行单独设置
  // 局部和全局同时存在,局部配置覆盖全局配置
  assetModuleFilename: 'imgs/[name].[hash:5][ext]' 
}

raw-loader

将资源内容解析为字符串,再进行引入,一般适用于在模块中引入txt等格式文件的时候使用

# 安装
$ npm i raw-loader -D
{
  test: /\.txt/,
  loader: 'raw-loader'
}

字体打包

对于一些其它资源,如视频,音频和字体文件,直接使用file-loader打包成链接引入即可,或直接使用asset/resource

{
  test: /\.eot|ttf|woff2?/,
  type: 'asset/resource',

  generator: {
    filename: 'font/[name].[hash:6][ext]'
  }
}

插件

Loader是用于特定的模块类型进行转换

Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入

CleanWebpackPlugin

前面我们练习的过程中,每次修改了一些配置,重新打包时,都需要手动删除dist文件夹

这个操作可以借助于一个插件来帮助我们自动完成,这个插件就是CleanWebpackPlugin

# 安装
$ npm i clean-webpack-plugin -D
// 配置 部分
/*
  1. 绝大部分plugin导出都是使用默认导出,但是CleanWebpackPlugin使用的是普通导出,所以在引入的时候需要注意
  2. 多少情况下,plugin是一个个的类,所以导入的名称首字母是大写的,且使用的时候使用new来创建对应的实例对象
*/
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

// ...... 此处为省略的其它配置

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

HtmlWebpackPlugin

之前练习的时候,我们的HTML文件是编写在根目录下的,而最终打包的dist文件夹中是没有index.html文件的

在进行项目部署时,必然也是需要有对应的入口文件index.html

其次,我们打包后的js文件也是应该自动在index.html中进行引入的,而不是手动引入,因为每一个项目的js名称是不一样的

而且可能会对打包后的文件进行分包处理,所以手动引入显然是不合适的

此时我们就需要一个插件帮助我们自动完成上述功能, 即HtmlWebpackPlugin

# 安装
$ npm i html-webpack-plugin -d
// js配置 --- 部分
const HtmlWebpakcPlugin = require('html-webpack-plugin')

// .... 部分配置省略
plugins: [
  new HtmlWebpakcPlugin() // 如果没有设置html模板,会使用内置的默认html模板(default_index.ejs)
]

有的时候,我们想在自己的模块中加入一些比较特别的内容

  • 如添加一个noscript标签,在用户的JavaScript被关闭时,给予响应的提示
  • 如在开发vue或者react项目时,我们需要一个可以挂载后续组件的根标签
// 自定义配置
const HtmlWebpakcPlugin = require('html-webpack-plugin')

// ....

plugins: [
  new HtmlWebpakcPlugin({
    title: 'webpack tutorial', // 这个配置项会被加入到options中
    // 一般,我们会将一个公共全局的静态资源存放在根目录下的public文件夹中 如index.html,favicon.ico
    template: './public/index.html', // 这个配置项会被作为html模板去进行使用
    
    // 设置于meta中的属性,会被自动添加到head标签中
    meta: {
      'x-version': '1.0.0'
    }
  })
]

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">
  <!-- 模板默认是使用ejs来进行编写的  title的值就是传入的配置中的title的值,默认值是Webpack App -->
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>

</body>
</html>

DefinePlugin

DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装)

// 配置 --- 部分
const { DefinePlugin } =  require('webpack')

// ...

plugins: [
  new DefinePlugin({
    // DefinePlugin中配置的key-value的值会做作为字符串进行解析,所以其值需要被设置成字符串类型
    
    // 注意,其取值的时候,会将引号中的内容单独取出,作为值来使用 (比较特别)
    // 所以'./'会被看成是./ 所以会报错,因此需要写成"'./'"
    BASE_URL: "'./'",
    
    // 配置在DefinePlugin中的key,可以在任何地方被引用,包括vue文件,js文件
    // webpack在编译的时候会对这些变量进行统一的解析和处理
    ENV: JSON.stringify('development')
  })
]

此時我们就可以在全局,也就是任何地方,使用我们在DefinePlugin中定义的全局变量

例如,在index.html模板中

<link rel="icon" href="<%= BASE_URL %>favicon.ico">
 if (ENV === 'development') {
     // 开发环境适用的代码
 } else {
    // 生产环境适用的代码 
 }

CopyWebpackPlugin

如果我们将一些文件放到public的目录下,需要在编译的时候,将这个目录会被复制到dist文件夹下

这个复制的功能,我们可以使用CopyWebpackPlugin来完成

# 安装
$ npm install copy-webpack-plugin -D
// 配置 --- 部分
const CopyWebpackPlugin = require('copy-webpack-plugin')

new CopyWebpackPlugin({
  patterns: [
    {
      from: 'public',
      // to: 'dist', ===> to 可以省略,默认值为打包后存放代码的那个文件夹即output.path的值

      globOptions: { // 对于每一个pattern的option
        ignore: [
          // 忽略 注意如果忽略public下的文件资源的时候,需要加上**/作为前缀,
          // 直接写index.html的时候,CopyWebpackPlugin插件是不会有效的帮助我们忽略index.html文件的
          '**/index.html', // 也不需要复制,因为我们已经通过HtmlWebpackPlugin完成了index.html的生成
          '**/.DS_Store' // mac目录下回自动生成的一个文件,描述当前文件夹的相关信息
        ]
      }
    }
  ]
})

friendly-errors-webpack-plugin

friendly-errors-webpack-plugin可以优化webpack为我们的提示信息,但是friendly-errors-webpack-plugin这个插件本身已不再支持webpack5,所以这里使用的是社区的@soda/friendly-errors-webpack-plugin

    const FriendlyErrorsWebpackPlugin = require('@soda/friendly-errors-webpack-plugin')
    
    // 在node环境中可以获取本机IP地址的包
    const internalIp = require('internal-ip')

    plugins: [
      new FriendlyErrorsWebpackPlugin({
        compilationSuccessInfo: {
          messages: [
            `You application is running here http://localhost:${devServer.port}`,
            `You application is running here http://${internalIp.v4.sync()}:${devServer.port}`
          ]
        }
      })
    ]

internal-ip

internal-ip是一个在node环境下运行的库

const internalIp = require('internal-ip');

(async () => {
  console.log(await internalIp.v6());
  //=> 'fe80::1'

  console.log(await internalIp.v4());
  //=> '10.0.0.79'
})();

console.log(internalIp.v6.sync())
//=> 'fe80::1'

console.log(internalIp.v4.sync())
//=> '10.0.0.79'