浅谈一下 webpack 以及 loader 和 plugin

简介: 浅谈一下 webpack 以及 loader 和 plugin

话说,前端练习时长也快两年了,但是关于 webpack 的东西好像也没怎么研究过 😅


🚩一是没有这方面的需求:回想一下,关于 webpack 的配置相关工作,也就只有自己配置过一次 loader「使用 svg-sprite-loader、svgo-loader 优化 svg symbols」,还是摸着石头过河;


🚩二是大部分的配置工作脚手架都已经做好了,这很可能导致一个问题,就是别人问你 webpack 相关的知识的时候,阿巴阿巴阿巴... 🤕️


确实,大多数情况下,前端开发人员可能不需要深入了解 webpack,但了解 webpack 的基本概念和用法对于前端开发仍然是很有益的。话不多说,开搞!🤓️

1. webpack

先让我们 👀 一下 webpack 官网 的解释:


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


怎么感觉所有框架或工具的官网定义都不是那么的通俗易懂?🤔


通俗点讲,就是当我们开发应用时,无论你用什么框架也好,都会在项目内部有一个入口文件,比如 Vue 和 React 项目的 `main.ts` 文件,其他模块的代码一般会分散在多个文件中,这些文件可能包含不同的功能、库或模块。为了能在浏览器中运行这些代码,我们需要将它们打包成一个或多个文件,比如我们平时打包出来的 `dist` 或 `build` 目录,这就是 webpack 的作用 🤷‍♂️

webpack 的主要功能包括:


模块打包:webpack 将应用程序的各个模块作为输入,通过解析模块之间的依赖关系,将它们打包成一个或多个静态资源文件。➡️ `pnpm run build`


资源转换:webpack 支持加载各种类型的文件,并且可以通过加载器(Loaders)对它们进行转换。比如,可以使用 Babel-loader 将 ES6/ES7 的 JavaScript 代码转换为浏览器可识别的 ES5 代码。➡️ loader 加载器


插件系统:webpack 提供了丰富的插件系统,开发者可以使用插件来扩展和定制打包过程。比如,可以使用 UglifyJS 插件来压缩 JavaScript 代码,或者使用 HtmlWebpackPlugin 插件生成 HTML 文件。➡️ plugin 插件


此外,webpack 还提供了许多优化功能,如代码压缩、代码拆分、懒加载等,以优化应用性能 🐂🍺

2. loader

loader,顾名思义,加载器。


webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱即用自带的能力。loader 让 webpack 拥有能够去处理其他类型的文件都能力,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。比如刚才提到的,可以使用 Babel-loader 将 ES6/ES7 的 JavaScript 代码转换为浏览器可识别的 ES5 代码。


🚩一句话概括:loader 就是协助 webpack 打包处理特定的文件模块。


在更高层面,在 webpack 的配置中,loader 有两个属性:


test 属性,识别出哪些文件会被转换。

use 属性,定义出在进行转换时,应该使用哪个 loader。

// webpack.config.js
const path = require('path');
module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
};

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:test 和 use。这告诉 webpack 编译器(compiler) 如下信息:


“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。” 🐒


是不是还挺简单的?去看文档!🙄️


下面简单看一下 webpack 常见的 loader 🤔

2.1 babel-loader

作用:将高级 JS 语法转化成低级语法 → 才能运行在 IE

webpack 只能打包处理部分高级 JS 语法,对于无法处理的需借助 babel-loader 打包,比如:

class Person {
    // 通过 static 关键字,为 Person 类定义了一个静态属性 info
    // webpack 无法打包处理“静态属性”这个高级语法
    static info = 'person info'
}
// 安装 babel-loader 相关的包
npm i babel-loader @babel/core @babel/plugin-proposal-class-properties
// 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则:
module: [
  rules: [
    {
      test: /.js$/,            // 匹配的文件类型
      exclude: /node_modules/, // 排除项
      use: {                   // 对应要调用的loader
        loader: "babel-loader",
        options: { // 参数项
          // 声明一个babel插件,此插件用来转化class中的高级语法
          plugins:['@babel/plugin-proposal-class-properties']
        }
      }
    }
  ]
]

2.2 ts-loader

作用:把 TS 转变成 JS,并提示类型错误

// 安装
// npm install ts-loader typescript --save-dev
// 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则:
module: {
  rules: [
    // `.ts/.cts/.mts/.tsx` extension files will be handled by `ts-loader`
    { test: /\.([cm]?ts|tsx)$/, loader: "ts-loader" }
  ]
}

2.3 less/sass-loader、postcss-loader、css-loader、style-loader

less/sass-loader: 将 less/sass 转化成 css

postcss-loader: 优化 css (如:加前缀) → 最好放 css-loader 之前

css-loader: 将 css 转化成 JS 字符串

style-loader: 将 JS 字符串转化成 style 标签

// 安装 css 相关的 loader 的包
npm install style-loader css-loader less/sass-loader less/sass -D
// 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则:
module: {
  rules: [
    {
      test : /.css$/,
      use : ['style-loader', 'css-loader', 'postcss-loader', 'less/sass-loader']
    } // 多个 loader 的调用顺序是:从后往前调用
  ]
}

多个 loader 的调用顺序是从后往前的?其实不然,官方文档 有这样一段描述:


loader 总是 从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch 方法。

2.4 url-loader、file-loader

file-loader:一个简单的文件加载器,它会将源文件复制到输出目录,并返回文件的最终路径。它通常用于处理像图片、字体等文件类型,可以将这些文件复制到输出目录,并根据需要生成正确的 URL 地址供应用程序使用。


例如,在 webpack 配置中使用 file-loader 处理图片文件:

// 安装相关的 loader 的包
npm i file-loader
// 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则:
module: {
  rules: [
    {
      test: /\.(png|jpg|gif)$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: '[name].[hash].[ext]', // 输出文件名的格式
            outputPath: 'images/' // 输出文件的目录
          }
        }
      ]
    }
  ]
}

这个配置会将匹配到的图片文件复制到输出目录中的 images/ 目录,并生成一个对应的文件名。


url-loader:基于 file-loader 的封装,并增加了一些额外的功能。它可以根据文件大小将文件转换为 Data URL 或将其保留为文件,并返回相应的 URL 地址。这样做的好处是,对于小文件,可以将其转换为 Data URL,避免额外的网络请求,而对于大文件,则可以保留为文件。

// 安装相关的 loader 的包
npm i url-loader
// 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则:
module: {
  rules: [
    {
      test : /.jpg|png|gif$/,
      use : { // 带参数项的 loader 可以通过对象的方式进行配置
        loader: "url-loader",
        options: {
                    limit: 10240, // limit 指定图片的大小,单位是字节(byte)
                    name: '[name].[hash].[ext]', // 输出文件名的格式
                    outputPath: 'images/' // 输出文件的目录
                }
      } // 只有 <= limit大小的图片,才会被转为 base64格式的图片
    }   // 配了 url-loader 在配置里面就不要再给图片配 file-loader 了
  ]     // 因为 url-loader 默认会使用 file-loader 来处理图片的路径关系的
}

2.5 svg-sprite-loader、svgo-loader

svg-sprite-loader:官方解释是:一个用于创建 svg 雪碧图的 Webpack 加载器。这个加载器现在已经被 JetBrains 公司收录和维护了。通俗的讲:svg-sprite-loader 会把你引入的 svg 塞到一个个 symbol 中,合成一个大的 svg,最后将这个大的 svg 放入 body 中。symbol 的 id 如果不特别指定,就是你的文件名。


svgo-loader:是基于 SVG Optimizer 的一个加载器,而 SVG Optimizer 是一个基于node.js 的工具,用于优化 SVG 矢量图形文件,它可以删除和修改SVG元素,折叠内容,移动属性等。

// 安装相关的 loader 的包
npm i svg-sprite-loader svgo-loader --dev
// 在 webpack.config.js 的 module -> rules 数组中,添加 loader 规则:
module: {
  rules: [
    {
      test : /\.svg$/,
      use : [
                { loader: 'svg-sprite-loader', options: {} },
                { loader: 'svgo-loader', options: {
                    plugins: [{
                        name: 'removeAttrs', // 必须指定name!
                        params: {attrs: 'fill'}
                    }]
                }
            ]
    }
  ]
}

ps:对这个 loader 感兴趣的话可以参考 使用 svg-sprite-loader、svgo-loader 优化 svg symbols

3. plugin

plugin,顾名思义,插件。


通过安装和配置第三方插件,可以扩展 webpack 的能力,从而让 webpack 用起来更方便。


loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。


🚩一句话概括:plugin 是用于扩展和定制 webpack  功能的工具。没用过浏览器插件吗?🤷‍♂️


想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件
module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

在上面的示例中,html-webpack-plugin 为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。


是不是还挺简单的?去看文档!🙄️


下面简单看一下 webpack 常见的 plugin 🤔


一些常用的 Webpack 插件:


HtmlWebpackPlugin:用于生成 HTML 文件,并将打包生成的资源文件自动注入到 HTML 文件中。


MiniCssExtractPlugin:用于将 CSS 代码提取为独立的文件,而不是内联到 JavaScript 文件中。


CleanWebpackPlugin:用于清理输出目录中的旧文件,以便在每次构建之前保持输出目录的干净。


OptimizeCSSAssetsPlugin:用于优化和压缩 CSS 代码。


DefinePlugin:用于定义全局常量,可以在应用程序的代码中直接使用。


可以根据官网给出的步骤配置插件:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
  module: { rules: [ /* 添加 Loader 的规则 */ ] },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new MiniCssExtractPlugin({
      filename: 'styles.css'
    }),
    new CleanWebpackPlugin()
  ]
};

4. loader vs plugin

如果看完上面的解释,还是不知道 webpack 的 loader 和 plugin 的区别的话,那我们举个 🌰


假如你是一名厨师,你有一些食材(模块文件)需要处理,并且需要一些工具来做完这道菜。


🚩 Loader 就像你的各种厨房工具。例如,切菜刀、搅拌器、炉灶等,这些工具帮助你对食材进行加工和转换,以便制作出美味的菜肴。在 webpack 中 loader 的作用也是一样的,它们负责将不同类型的文件进行处理和转换,比如:将 ES6 代码转换为 ES5 代码,将 CSS 文件转换为浏览器可识别的样式等。


🚩 Plugin 则像你的特殊调料和烹饪技巧。假设你想给菜肴增添特殊的风味或实现特定的效果。你可能会使用辣椒酱增加辣味,柠檬汁增添酸味,或者使用烘烤技巧让菜表面金黄酥脆。在 webpack 中 plugin 的作用也是一样的,它们可以在构建过程中监听事件,并执行一些特殊的操作。例如,你可以使用 HtmlWebpackPlugin 生成一个带有引入资源的 HTML 文件,使用 UglifyJSPlugin 压缩和混淆 JavaScript 代码,或者使用 ExtractTextPlugin 将 CSS 提取为独立的文件。


总结来说,loader 是用于处理和转换文件的工具,类似于厨房中的各种工具,而 plugin 则是用于扩展和定制构建过程的工具,类似于特殊的调料和烹饪技巧。它们共同协作,使得 Webpack 能够处理各种文件类型、进行模块化开发,并通过插件机制进行灵活的定制和优化。🎉

5. 自己写一个 plugin

Webpack 插件就是一个 JavaScript 对象,通过扩展或修改 webpack 的功能来实现特定的构建需求。它可以在 webpack 的构建过程中干预并做出相应的处理。基本的 webpack 插件结构如下:

class MyPlugin {
  constructor(options) {
    // 在构造函数中接收插件的配置选项
    this.options = options;
  }
  apply(compiler) {
    // 在 apply 方法中定义插件的逻辑
    // compiler 对象代表了完整的 webpack 环境配置
    // 可以通过 compiler 对象来访问 webpack 的各种钩子函数
    // 注册钩子函数,以在 webpack 构建过程中执行特定操作
    compiler.hooks.someHook.tap('MyPlugin', () => {
      // 在这里执行你的插件逻辑
    });
  }
}

👆这是一个最基本的 webpack 插件结构示例,webpack 插件的结构包括一个 apply 方法和一些钩子函数。apply 方法在插件被应用时被调用,接受一个 compiler 参数,该参数代表了完整的 webpack 环境配置。通过 compiler 对象,插件可以访问 webpack 的各种钩子函数并注册自己的逻辑。


钩子函数是 webpack 在构建过程中的特定时间点触发的函数。插件可以根据需求选择合适的钩子函数,并在这些函数中执行自定义的逻辑。例如,在构建开始前可以使用 beforeRun 或 run 钩子,在构建完成后可以使用 done 钩子。


下面是一些常用的 webpack 钩子函数:


beforeRun:在 webpack 构建启动之前执行。

run:在开始构建之前执行。

beforeCompile:在编译之前执行。

compile:在开始编译之前执行。

compilation:在每次新的编译创建之前执行。

emit:在生成资源并输出到输出目录之前执行。

afterEmit:在资源输出到输出目录之后执行。

done:在构建完成时执行。

不止这些钩子吧?去看文档!🙄

插件可以使用这些钩子函数来执行各种任务,如修改、添加、删除资源,生成额外的文件,提取公共代码,优化输出等等。


OK,举个


假如我们比较关心项目在构建完成后产出的文件的路径和大小,go!

const fs = require('fs');
class MyPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) { // 构建时只会执行一次
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      const outputPath = stats.compilation.outputOptions.path;
      const outputFileName = stats.compilation.outputOptions.filename;
      const filePath = `${outputPath}/${outputFileName}`;
      const fileSize = fs.statSync(filePath).size;
      console.log(`Built file: ${filePath}`);
      console.log(`File size: ${fileSize} bytes`);
    });
  }
}

在上述示例中,我们在 webpack 构建完成后的 done 钩子中获取构建输出文件的路径和大小,并将其输出到控制台。


🚩 为什么说 apply 只会执行一次?🤔


我在实践手写这个插件的时候,为了调试,在 apply 内部写了一个日志 log,我发现日志仅在第一次编译的时候可以执行,在热更新的时候,日志并没有打印,这是怎么回事?


还是刚才那个例子,apply 方法相当于你炒菜的点火动作,具体到了什么时机以及我们需要具体做什么,是需要提前注册具体的逻辑的。这里为了方便记忆,你也可以类比成 dom 的 addEventListener,具体的注册动作只发生一次,但是监听到事件之后是每次都会执行的!


最后,要使用此插件,还需要在 webpack 配置文件中引入并实例化它

const MyPlugin = require('./path/to/MyPlugin');
module.exports = {
  // ...其他配置项
  plugins: [
    new MyPlugin({
      // 插件的配置选项
    })
  ]
};
目录
相关文章
|
16天前
|
测试技术 开发者
如何确保 Webpack plugin 与其他插件的兼容性?
【10月更文挑战第23天】确保 Webpack plugin 与其他插件的兼容性需要从多个方面进行考虑和努力。通过遵循规范、进行充分测试、保持沟通协作等方式,
|
16天前
|
算法 测试技术 开发者
编写 Webpack plugin 时需要注意什么?
【10月更文挑战第23天】 编写 Webpack plugin 需要综合考虑多个方面的因素。只有在充分理解和掌握这些要点的基础上,才能编写出高质量、可靠且实用的 Plugin,为 Webpack 构建过程带来更多的价值和便利。
|
16天前
|
搜索推荐 测试技术 开发者
写一个 webpack plugin
【10月更文挑战第23天】编写一个 Webpack plugin 需要对 Webpack 的工作原理和机制有深入的了解,同时需要具备良好的编程能力和逻辑思维。通过合理设计和实现,Plugin 可以为我们的 Webpack 构建过程带来更多的灵活性和个性化。
|
28天前
|
前端开发 UED
Webpack 中处理 CSS 和图片资源的多 Loader 配置
【10月更文挑战第12天】 处理 CSS 和图片资源是 Webpack 配置中的重要部分。通过合理选择和配置多个 Loader,可以实现对这些资源的精细处理和优化,提升项目的性能和用户体验。在实际应用中,需要不断探索和实践,根据项目的具体情况进行灵活调整和优化,以达到最佳的处理效果。通过对 Webpack 中多 Loader 处理 CSS 和图片资源的深入了解和掌握,你将能够更好地应对各种复杂的资源处理需求,为项目的成功构建和运行提供坚实的基础。
52 1
|
28天前
|
前端开发 JavaScript
Webpack 中多个 Loader 的配置
【10月更文挑战第12天】使用多个 Loader 进行配置是 Webpack 中常见的操作,可以实现对各种资源的精细处理和优化。在配置时,需要根据具体需求合理选择和排列 Loader,并注意它们之间的顺序和交互关系。同时,不断了解和掌握新的 Loader 以及它们的特性,有助于更好地发挥 Webpack 的强大功能,提升项目的开发效率和质量。通过深入理解和熟练运用多个 Loader 的配置方法,你将能够更加灵活地处理各种资源,满足项目的多样化需求。
41 2
|
28天前
|
前端开发 JavaScript
Webpack 常用 Loader 和 Plugin
【10月更文挑战第12天】Webpack 是一个强大的模块打包工具,能够将各种资源模块进行打包和处理。Loader 用于转换模块的源代码,如 `babel-loader` 将 ES6+ 代码转换为 ES5,`css-loader` 处理 CSS 文件等。Plugin 扩展 Webpack 功能,如 `HtmlWebpackPlugin` 自动生成 HTML 文件,`UglifyJsPlugin` 压缩 JavaScript 代码。通过合理配置和使用 Loader 和 Plugin,可以构建高效、优化的项目。
21 2
|
1月前
|
移动开发 JavaScript 前端开发
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
这篇文章主要介绍了如何通过配置Webpack的插件,如HtmlWebpackPlugin、uglifyjs-webpack-plugin和webpack-dev-server,来简化前端开发流程。
34 0
webpack学习四:使用webpack配置plugin,来使用HtmlWebpackPlugin、uglifyjs-webpack-plugin、webpack-dev-server等插件简化开发
|
2月前
|
设计模式 前端开发 JavaScript
webpack实战之手写一个loader和plugin
该文章详细讲解了如何从零开始编写一个自定义的Webpack Loader和Plugin,包括它们的工作原理、开发步骤以及如何将自定义的Loader和Plugin集成到Webpack配置中。
webpack实战之手写一个loader和plugin
|
3月前
|
缓存 JSON JavaScript
Webpack 传递给 Loader 的原始内容是一个 UTF-8 格式编码的字符串
本文详细介绍了Webpack中Loader的概念及其重要性。Webpack仅支持处理JS和JSON文件,而对于CSS、图片等其他类型文件,则需要Loader来转换。文章列举了多种常见Loader,如css-loader、style-loader、babel-loader等,并提供了具体配置示例。此外,还介绍了如何自定义Loader,包括初始化项目、实现基本功能及处理异步操作等内容。通过本文,读者可以全面了解Loader的作用及其实现方法。
31 3
|
4月前
|
缓存 JSON JavaScript
用Webpack写一个Loader
在Webpack写一个Loader
23 1