Webpack-loader、plugin简介及自定义

184 阅读4分钟

一、webpack的loader实现特殊资源加载

1. 常见loader

  1. babel-loader: ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,js代码兼容低版本浏览器处理
// 1. 安装: npm install --save-dev babel-loader @babel/core @babel/preset-env
//@babel/core babel的核心模块 
//babel-loader 解析js代码,wepback和babel的桥梁
//@babel/preset-env es6转化成es5插件的集合
//@babel/plugin-transform-runtime   @babel/runtime 去出重复的代码

// 2. babel的配置文件:  .babelrc
// 最基本配置
{
  "presets": [
    [
      "@babel/preset-env"
    ]
  ]
}
//或者是下面这个是优化webpack
//但是需要安装 @babel/plugin-transform-runtime
//@babel/plugin-proposal-decorators
//@babel/plugin-proposal-class-properties
{
    "presets": [
        ["@babel/preset-env",{
            "useBuiltIns":"usage", //只转化使用的api
            "corejs":3  //@babel/pollyfill   转化es6中高版本的api
        }]
    ], //预设(插件的集合)从下往上
    "plugins":[
         "@babel/plugin-transform-runtime",//将重复的方法与类 提取出来。调取这个提取出来方法。从而达到webpac包裹的减少
        ["@babel/plugin-proposal-decorators", { "legacy": true }],//类装饰器
        ["@babel/plugin-proposal-class-properties", { "loose" : true }]//赋值结构的转换
    ]  //一个个插件
}

// 3. webpack配置文件: webpack.config.js
const path = require('path')
module.exports={
    ...
    module:{
         rules: [ // 用于规定在不同模块被创建时如何处理模块的规则数组
            {
                 test: /\.js$/i,
                exclude: '/(node_modules)/', // 排除文件
                loader: 'babel-loader'
              }
         ]
    }
 
}
  1. style-loader: 把 CSS 插入到 DOM 中。
  2. css-loader: 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样
  3. scss-loader: 加载 Sass/SCSS 文件并将他们编译为 CSS

2. 自定义一个简单loader

    1. 创建处理某种资源文件的处理逻辑文件,如(处理md文件资源): markdown-loader.js
module.exports = source => {
	console.log('加载到的模块内容', source)
	// 返回值: 就是最终被打包的内容
	// return 'xxxx'
    // return `export default ${JSON.stringify({a: 1, b: 2})}`
    return `module.exports = ${JSON.stringify({a: 1, b: 2})}`
}

// 备注可以通过: marked 第三方库转换markdown文件为html结构
const { marked } = require("marked");
marked(source) // => 得到md文件的 html 结构(字符串)
    1. 使用loader处理webpack不能处理自定义loader处理的资源
// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
      	test: /\.md$/,
      	use: './markdown-loader' // 直接使用相对路径,加载器可以使用模块的名称;也可以使用模块文件路径
      }
    ]
  }
}

// b.md: 编写markdown内容

// index.js
import b from './b.md'
console.log(b);
    1. 自定义 loader 就能处理 md 文件了
    1. 多个loader配合处理同一种资源文件
// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
      	test: /\.md$/,
      	use: [
      		'html-loader', // 需要安装: npm install --save-dev html-loader (将 HTML 导出为字符串。当编译器需要时,将压缩 HTML 字符串)
      		'./markdown-loader' // 自定义loader
      	]
      }
    ]
  }
}

二、webpack的plugin插件机制横向扩展webpack的构建能力

1. webpack插件机制的目的: 为了增强webpack在项目自动化构建方面的能力

2. 插件的常见应用场景:

  1. 实现自动在打包之前清除dist目录 (clean-webpack-plugin)
  2. 自动生成应用所需要的HTML文件 (html-webpack-plugin)
  3. 根据不同环境为代码注入类似API地址这种可能变化的部分 - 定义全局变量的区分当前环境 (webpack.DefinePlugin)
  4. 拷贝不需要参与打包的资源文件到输入目录 (copy-webpack-plugin)
  5. 压缩Webpack打包完成后输出的文件 (terser-webpack-plugin)
  6. 自动发布打包结果到服务器实现自动化部署 ......

3. 插件的常用使用方式

// clean-webpack-plugin 插件使用
// 安装: npm i clean-webpack-plugin --save-dev // 1. 第三方插件安装
// webpack.config.js
const { cleanWebpackPlugin } = require('clean-webpack-plugin') // 2. 引入
module.exports = {
  ...
  plugins: [
    new cleanWebpackPlugin() // 3. 使用
  ]
}

4. 自定义一个简单webpack插件

  1. 开发一个插件,如: 功能-清除打包生成的bundle.js中每一行前面的注释 /******/
  2. 创建实现该插件功能的文件: remove-comments-plugin.js
class RemoveCommentsPlugin {
  // apply方法会在webpack命令执行时被调用
  apply(compiler) {
    // compiler 包含了此次构建的所有配置信息
    console.log('RemoveCommentsPlugin插件的apply方法触发', compiler)
    // 在不同的钩子函数触发时实现该插件的不同自定义需要实现的功能
    // emit 钩子: 这个钩子会在webpack即将向输出目录输出文件时执行
  }
}
  1. 通过 compiler 对象的 hooks 属性访问到 emit 钩子,再通过 tap 方法注册一个钩子函数
  • 这个方法接收两个参数:
      1. 第一个是插件的名称: RemoveCommentsPlugin
      1. 第二个是要挂载到这个钩子上的函数
class RemoveCommentsPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('RemoveCommentsPlugin', compilation => {
      // compilation: 可以理解为此次打包的上下文
      for(let name in compilation.assets) {
        // name: 输出文件的名称(name 为每一个需要输出文件的名称,就是要输出到dist目录下的文件名称) => 注解1
        // compilation.assets[name].source(): 每一个资源文件的内容 => 注解2
        // console.log(name)
        // console.log(compilation.assets[name].source())
        if(name.endsWith('.js')) {
          // endsWith(): 方法是否以指定子字符串结尾 => 只处理以js结尾的文件
          let contents = compilation.assets[name].source()
          let handleResult = contents.replace(/\/\*{2,}\/\s?/g, '') // 该正则将 /***(两个以上的*)/以及后面的空白字符替换成 ''
          // 最后一步覆盖掉之前的内容,将处理转换之后的内容重新赋值给compilation中当前资源文件
          compilation.assets[name] = {
            source: () => {
              return handleResult
            }, // 返回的处理完成之后的新的资源内容
            size: () => handleResult.length // 返回新内容的大小
          }
        }
      }
    })
  }
}

5. 总结:

  1. webpack为每一个工作环节都预留了合适的钩子
  2. 扩展webpack插件功能时,只需要找到合适的时机(即webpack提供的钩子函数)去做合适的事情
  3. 插件机制又叫做: 面向切面编程-AOP