webpack loader

92 阅读3分钟

一、loader概念

loader用来帮助webpack将不同类型的文件转换为webpack可识别的模块

二、loader执行顺序

  1. loader分类

    • pre:前置loader
    • normal:普通loader
    • inline:内联loader
    • post:后置loader
  2. 执行顺序

    • 优先级:pre > normal > inline > post
    • 相同优先级的loader执行顺序:从右到左,从下到上

此时loader执行顺序:loader3 -> loader2 -> loader1

    rules: [
      { test: /\.js$/, loader: 'loader1' },
      { test: /\.js$/, loader: 'loader2' },
      { test: /\.js$/, loader: 'loader3' }
    ]

此时loader执行顺序:loader1 -> loader2 -> loader3

    rules: [
      { enforce: 'pre', test: /\.js$/, loader: 'loader1' },
      { test: /\.js$/, loader: 'loader2' }, // enforce默认值为normal
      { enforce: 'post', test: /\.js$/, loader: 'loader3' }
    ]
  1. 使用loader的方式

    • 配置方式:在webpack.config.js中指定loader的enforce
    • 内联方式:在每个import语句中显示指定loader(inline loader)
  2. inline loader

用法:import Styles from 'style-loader!css-loader?modules!./styles.css'

含义:

  • 使用css-loader和style-loader处理styles.css文件
  • 通过!将loader分开

inline loader可以通过添加不同前缀,跳过其他类型的loader:

  1. !跳过normal loader:import Styles from '!style-loader!css-loader?modules!./styles.css'
  2. -!跳过pre和normal loader:import Styles from '-!style-loader!css-loader?modules!./styles.css'
  3. !!跳过pre、normal和post loader:import Styles from '!!style-loader!css-loader?modules!./styles.css'

三、一个简单的loader

loaders/test-loader.js

/*
  loader就是一个函数,当webpack解析资源时,会调用相应的loader去处理,loader接收到文件内容作为参数,返回内容出去
    content:文件内容
    map:sourceMap
    meta:其他数据
*/

module.exports = function (content, map, meta) {
  console.log(content);
  return content;
};

四、loader分类

同步loader

module.exports = function (content, map, meta) {
  return content;
};

this.callback更灵活,允许传递多个参数,而不仅是content

module.exports = function (content, map, meta) {
  this.callback(null, content, map, meta);
  return; // 当调用callback函数时,总是返回undefined
};

异步loader

由于同步loader计算过于耗时,在nodejs这样的单线程运行环境下进行此操作并不是好的方案,尽可能使loader异步化。但如果计算量很小,使用同步loader也是可以的

module.exports = function (content, map, meta) {
  const callback = this.async();
  setTimeout(() => {
    callback(null, result, map, meta);
  }, 1000);
};

raw loader

默认情况下,资源文件会被转化为utf-8字符串,然后传给loader。通过设置raw为true,loader可以接收原始的buffer

module.exports = function (content, map, meta) {
  // content是一个buffer数据
  return content;
};
module.exports.raw = true; // 开启raw loader

pitching loader

webpack会先从左到右执行loader链中每个loader上的pitch方法(如果有),然后再从右到左执行loader链中每个loader上的普通loader方法

loader1.png 在这个过程中,如果某个pitch有返回值,则loader链中断,webpack会跳过后面的pitch和loader,直接进入上一个loader

loader2.png

module.exports = function (content, map, meta) {
  return content;
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
  console.log('do somethings');
};

五、loader API

方法名含义用法
this.async异步回调loader,返回this.callbackconst callback = this.async()
this.callback可以同步或者异步调用,并返回多个结果的函数this.callback(err, content, sourceMap?, meta?)
this.getOptions(schema)获取loader配置项中的optionsthis.getOptions(schema)
this.emitFile产生一个文件this.emitFile(name, content, sourceMap)
this.utils.contextify返回一个相对路径this.utils.contextify(context, request)
this.utils.absolutify返回一个绝对路径this.utils.absolutify(context, request)

六、手写clean-log-loader

作用:清除console.log()

loaders/clean-log-loader.js

module.exports = function (content) {
  return content.replace(/console\.log\(.*\);?/g, '');
};

main.js

console.log('main')

webpack.config.js

    rules: [
      {
        test: /\.js$/,
        use: ['./loaders/clean-log-loader']
      }
    ]

执行npx webpack,打包后的js文件中的console.log(...)会被替换为''

七、手写banner-loader

作用:给js代码添加文本注释

loaders/banner-loader/sehema.json

规则:

  1. options是一个对象
  2. options中有个属性author,并且属性值为字符串
  3. 不能再有其他属性
{
  "type": "object",
  "properties": { "author": { "type": "string" } },
  "additionalProperties": false
}

loaders/banner-loader/index.js

  1. this.getOptions用于获取loader配置项中的options属性
  2. schema是这个属性按照什么规则去写
const schema = require('./schema.json');

module.exports = function (content) {
  const options = this.getOptions(schema);

  const prefix = `
    /*
      author: ${options.author}
      年龄: ${options.age}
    */
  `;

  return `${prefix} \n ${content}`;
};

webpack.config.js

      {
        test: /\.js$/,
        loader: './loaders/banner-loader/index',
        options: { author: '吴小明' }
      }

效果:

动图.gif

八、手写babel-loader

作用:将es6+编译成es5-

下载包

cnpm i @babel/core @babel/preset-env -D

loaders/babel-loader/schema.js

module.exports = {
  type: 'object',
  properties: { presets: { type: 'array' } },
  additionalProperties: true
};

loaders/babel-loader/index.js

const schema = require('./schema');
const babel = require('@babel/core');

module.exports = function (content) {
  const options = this.getOptions(schema);
  const callback = this.async();
  babel.transform(content, options, function (err, res) {
    callback(err || null, res.code);
  });
};

main.js

const sum = (...rest) => {
  return rest.reduce((a, b) => a + b, 0);
};

console.log(sum(1, 2, 3));

webpack.config.js

      {
        test: /\.js$/,
        loader: './loaders/babel-loader/index',
        options: { presets: ['@babel/preset-env'] }
      }

效果:

image.png

九、手写file-loader

作用:将文件原封不动输出出去

下载包

cnpm i loader-utils -D

loaders/file-loader.js

const loaderUtils = require('loader-utils');

const schema = {
  type: 'object',
  properties: { filePath: { type: 'string' } }
};

function fileLoader(content) {
  const options = this.getOptions(schema);
  // 根据文件内容生成文件名
  let filename = loaderUtils.interpolateName(this, '[hash].[ext]', {
    content
  });
  filename = `${options.filePath}/${filename}`; // 输出到指定目录下
  this.emitFile(filename, content); // 输出文件

  return `module.exports = '${filename}'`;
}

fileLoader.raw = true; // 图片是buffer数据

module.exports = fileLoader;

webpack.config.js

      {
        test: /\.(png|jpe?g|gif)$/,
        loader: './loaders/file-loader',
        options: { filePath: 'images' },
        type: 'javascript/auto' // 阻止webpack默认处理图片资源,只使用file-loader处理
      }

main.js

import './images/1.jpeg'
import './images/2.png'
import './images/3.gif'

执行npx webpack,dist目录下会多出3张图片资源

image.png

十、手写style-loader

作用:动态创建style标签,插入js中的样式代码,使样式生效

下载包

cnpm i css-loader -D

loaders/style-loader.js

const styleLoader = () => {};

styleLoader.pitch = function (remainingRequest) {
  // remainingRequest值:D:\desktop\forward\webpack5\loader\node_modules\_css-loader@6.8.1@css-loader\dist\cjs.js!D:\desktop\forward\webpack5\loader\src\css\index.css
  const relativeRequest = remainingRequest // 如果在配置项中不写css-loader,这里remainingRequest就拿不到css-loader的路径
    .split('!')
    .map(n => this.utils.contextify(this.context, n))
    .join('!');
  // 处理后:../../node_modules/_css-loader@6.8.1@css-loader/dist/cjs.js!./index.css
  const script = `
    import style from '!!${relativeRequest}' // relativeRequest是css-loader处理后的资源,!!跳过pre、normal和post loader
    const styleEl = document.createElement('style')
    styleEl.innerHTML = style
    document.head.appendChild(styleEl)
  `;
  return script; // 终止css-loader、style-loader中normal方法的执行
};

module.exports = styleLoader;

webpack.config.js

      {
        test: /\.css$/,
        // use: ['style-loader', 'css-loader']
        use: ["./loaders/style-loader", "css-loader"],
      }

效果:

image.png