Webpack 常用 Loader +手写Loader

1. Webpack 常用 Loader

Loader 用于对模块源代码进行转换处理webpack 本身只识别 JS、JSON,其他文件靠 loader 解析。

两类 loader:

  • 普通 loader:返回转换后的代码
  • pitch loader:执行在读取文件之前,可以直接返回,短路后面 loader。

样式类 loader

  1. css‑loader :解析 @importurl(),处理 css 文件中的依赖
  2. style‑loader :把编译后的 css 插入页面 <style> 标签
  3. sass‑loader / sass‑loader:编译 sass/scss
  4. less‑loader:编译 less
  5. postcss‑loader:css 后处理,自动加浏览器前缀、兼容处理,配合 autoprefixer

常见顺序:style-loadercss-loaderpostcss-loadersass‑loader(从右往左执行)

文件资源类 loader

  1. file‑loader:复制文件,输出到输出目录,返回文件路径(webpack4)
  2. url‑loader:小文件转 base64,大文件交给 file‑loader(webpack4)

webpack5 已内置 asset/resourceasset/inline 模块,替代 file‑loader、url‑loader

JS/脚本处理 loader

  1. babel‑loader :转译 ES6+、TS,把高版本 JS 转成 ES5,依赖 @babel/core@babel/preset‑env
  2. ts‑loader :编译 TypeScript;也可以用 babel‑loader + @babel/preset‑typescript

性能增强优化Loader

  1. thread‑loader 作用:开启多线程编译,把耗时 loader 的工作丢到子进程,提升打包速度
    • CPU 是 8 核,workers 设置为 3~4,不要写 os.cpus().length 满开;主线程也要占用一个 CPU。

    • 只给 babel‑loader 套上 thread‑loader(babel 编译最耗 CPU),css、sass 不要套 thread‑loader,收益很低。

    • 开发 watch 模式,设置 poolTimeout: Infinity,避免进程反复销毁重建。

    • 小项目不要上 thread‑loader几百行代码的 demo 项目,创建子进程本身有开销,打包速度反而变差。中大型项目才有收益

    • cache: true, // webpack5持久化缓存,优先开这个,成本最低,比thread‑loader收益更稳定;优化打包优先级顺序:开启 cache > thread‑loader 多线程

⚠️ use 数组顺序:thread‑loader 写在数组最前面,loader 执行从右向左。执行流程:babel‑loader 做转译 → 交给 thread‑loader 丢到子进程。

Vue 相关

  1. vue‑loader :解析 .vue 单文件组件,拆分 template、script、style

其他常用

  1. eslint‑loader:代码校验,打包时做 eslint 检查
  2. raw‑loader :把文件内容原样导出字符串(webpack5 用 asset/source 替代)

webpack.config.js 配置

js 复制代码
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// 通过环境变量区分开发/生产
const isProduction = process.env.NODE_ENV === 'production'

module.exports = {
  mode: isProduction ? 'production' : 'development',
  entry: './src/index.js',
  output: {
    filename: 'js/bundle.[contenthash:8].js',  // ✅contenthash考点
    path: path.resolve(__dirname, 'dist'),
    clean: true, // 每次打包清空dist目录
    publicPath: '/'
  },
  devtool: isProduction ? 'source-map' : 'eval-cheap-module-source-map', //  ✅考点优化构建速度方式之一

  // webpack5 内置持久化缓存,性能优化第一优先级
  cache: {
    type: 'filesystem'
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/, // three等第三方库不转译,加快打包✅考点
        use: [
          // 多线程,只给babel用;开发watch模式不销毁进程
          {
            loader: 'thread-loader', // 多线程加快构建速度✅考点
            options: {
              workers: 3,
              workerParallelJobs: 20,
              poolTimeout: isProduction ? 2000 : Infinity
            }
          },
          {
            loader: 'babel-loader', // 考点✅ 转译js语法
            options: {
              presets: [
                [
                  '@babel/preset-env',  // 考点✅ 负责调度 polyfill。
                  {
                    useBuiltIns: 'usage', // 考点✅ 所以配置 `useBuiltIns: 'usage'` 的时候,**必须手动安装 core‑js@3**,不装就报错 / 警告。
                    corejs: 3,
                    targets: '> 0.25%, not dead'
                  }
                ]
              ]
            }
          }
        ]
      },

      // css处理:生产抽离css文件;开发用style-loader注入页面
      {
        test: /\.css$/,
        // 考点✅ minicssextractplugin 分包,style-loader是pitchloader
        use: [
          isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
          'css-loader',
          'postcss-loader'
        ]
      },

      // scss/sass
      {
        test: /\.s[ac]ss$/,
        use: [
          isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ]
      },

      // webpack5内置资源模块:图片、字体、three模型 glb/gltf/obj
      {
        test: /\.(png|jpe?g|gif|svg|glb|gltf|obj|woff2?|ttf)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024 // 小于8kb转base64内联
          }
        },
        generator: {
          filename: 'assets/[name].[contenthash:8][ext]'
        }
      }
    ]
  },
  // 考点✅ 提高构建速度,缩小查找范围
  resolve: {
    extensions: ['.js', '.json']
  },

  plugins: [
    // 生产环境才实例化抽离css插件
    ...(isProduction
      ? [
          new MiniCssExtractPlugin({
            filename: 'css/[name].[contenthash:8].css'
          })
        ]
      : [])
  ],

  // 开发服务器
  devServer: {
    static: path.resolve(__dirname, 'dist'),
    open: true,
    hot: true,
    compress: true,
    port: 8080
  }
}

⚠️ loader 执行顺序:数组从后往前 / 从下往上执行
生产环境不要用 style-loader ,换成 mini-css-extract-plugin 抽离单独 css 文件:


2. 手写 Webpack Loader

loader本质就是一个函数,接收源代码字符串,处理后返回新代码。

  • 入参:source 源文件内容
  • 返回:处理过后的代码字符串
  • 不能写箭头函数,webpack需要this上下文(this.query、this.callback)

1. 最简单同步loader demo:replace-loader

功能:把源码里的 hello 替换成 Hello Webpack Loader

新建 loaders/replace-loader.js

js 复制代码
// 同步loader
module.exports = function (source) {
  // source:读取到的文件原始内容
  const result = source.replace(/hello/g, 'Hello Webpack Loader')
  // 返回处理完的代码
  return result
}

webpack.config.js 使用自定义loader

自定义loader需要配置 resolveLoader 告诉webpack去哪里找loader

js 复制代码
const path = require('path')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  resolveLoader: {
    // 优先去 ./loaders 目录找自定义loader
    modules: [path.resolve(__dirname, './loaders'), 'node_modules']
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'replace-loader'
      }
    ]
  }
}

src/index.js

js 复制代码
console.log('hello world')

打包后输出变成:console.log('Hello Webpack Loader world')


2. 接收loader配置参数

通过 this.getOptions() 获取 options(webpack5推荐,替代 this.query)

loaders/replace-loader.js

js 复制代码
module.exports = function (source) {
  // 获取loader的options配置
  const options = this.getOptions()
  const from = options.from || 'hello'
  const to = options.to || 'hi'

  const output = source.replace(new RegExp(from, 'g'), to)
  return output
}

webpack配置传参:

js 复制代码
rules: [
  {
    test: /\.js$/,
    use: {
      loader: 'replace-loader',
      options: {
        from: 'hello',
        to: '🎉你好webpack🎉'
      }
    }
  }
]

3. 异步loader this.callback

当内部有异步逻辑(读文件、网络请求),不能return,使用 this.callback(err, content)

loaders/async-replace-loader.js

js 复制代码
module.exports = function (source) {
  // 告诉webpack这是异步loader
  const callback = this.async()

  // 模拟异步操作
  setTimeout(() => {
    const res = source.replace(/hello/g, '异步处理完成')
    // 参数1:错误,参数2:处理后的源码
    callback(null, res)
  }, 100)
}

callback完整参数:callback(error, content, sourceMap, ast)


4. loader分类(重要面试点)

  1. 同步loader:直接 return 结果
  2. 异步loaderthis.async() 获取callback,异步回调返回
  3. pitch loader :loader上写 pitch 方法,从左到右执行,正常loader是从右往左执行。

pitch阶段:在读取文件内容之前执行,可以直接返回,跳过后面loader执行。

pitch简单示例:

js 复制代码
module.exports = function (source) {
  return source
}
// pitch函数
module.exports.pitch = function (remainingRequest) {
  console.log('pitch执行', remainingRequest)
  // 如果这里return字符串,会直接作为结果,后面loader全部跳过
}

执行顺序:pitch(左→右) → 读取文件 → loader执行(右→左)

5. 小实操:简易raw‑loader

读取文件,直接把文件内容导出字符串

js 复制代码
// raw-loader.js
module.exports = function(source) {
  return `module.exports = ${JSON.stringify(source)}`
}

面试考点总结

  1. loader本质是函数,输入源代码,输出转换后的代码;只处理内容,不处理文件读写
  2. 普通loader:从右向左执行 ;pitch:从左向右执行
  3. 同步return;异步使用this.async()拿到callback。
  4. webpack5获取配置:this.getOptions()
  5. loader不能依赖webpack内部API,只操作字符串。

小坑:自定义loader不要用箭头函数,会丢失this上下文!

相关推荐
潍坊老登2 小时前
写了一个某音自动刷视频,通过大模型分析视频主题的Android应用
前端
蔓越莓2 小时前
性能优化:webpack打包层面优化
前端·面试
TJHHH.2 小时前
XSS-labs-master
前端·web安全·xss
风月说与山鬼2 小时前
十二、Vue插件
前端·vue.js
spter。2 小时前
一次 Redis SETNX 的小插曲,我重新理解了原子性
前端·redis·bootstrap
Sayai3 小时前
【无标题】ECharts 实现日志量异常检测:基线 ±Kσ 基带 + 灵敏度切换(Vue2 实战)
前端·javascript·echarts
用户3169353811833 小时前
SpringBoot + Vue 项目生产环境部署完整指南
前端·后端
Csvn3 小时前
🔍 TypeScript `satisfies` 操作符:既要类型安全,又要推断的原始字面量类型
前端
Csvn3 小时前
🚦 手写 Promise 并发控制:如何优雅限制异步请求的并发数?
前端