学习webpack loader,这一篇文章足够了。从原因、原理、用法、自定义loader等方面解析,讲的明明白白!

如果你想学到更多实用前端知识。

可以关注我的公众号:【前端驿站Lite】,一个不止分享前端的地方 ᕦ( •̀∀•́)ᕤ

阅读收获

阅读完本篇文章,你将会有以下收获:

  1. 为什么会有loader机制。
  2. loader是什么,有什么作用。
  3. loader的分类、执行顺序是怎样的。
  4. 同步、异步loader的区别。
  5. 如何自定义一个loader。
  6. 12个常见loader介绍与用法。

为什么会有loader

本文开篇,我们先来聊一下,webpack为什么会引入loader机制。

webpack只能处理.js.json文件,但打包过程中遇到其他类型文件,如.vue.ts图片.css等,webpack就无能为力了。

面对这一问题,webpack提供了loader机制。

loader的出现,让webpack拥有了处理其他类型文件的能力。

比如,我们经常会看到下面这样的配置:

js 复制代码
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /.less$/i, // 匹配.less结尾的文件
                use: [
                    "style-loader",
                    "css-loader",
                    'less-loader'
                ],
            }
        ],
    }
    // ...
};
js 复制代码
//js文件
import "./style.less";

js代码中,使用import导入了一个.less文件,webpack碰到.less后缀的文件,不知所措了,因为它只能处理以.js.json结尾的文件。

这时候就需要loader登场了,有了loader的赋能,webpack便有了处理.less文件的能力。

  1. 比如,根据上面配置,webpack一旦碰到.less后缀结尾的文件,webpack会先将文件内容发送给less-loader处理,less-loader会将所有less语法转换成普通css语法。
  2. 普通的css样式继续发送给css-loader处理,css-loader最主要的功能是解析css语法中的@import图片路径,处理完后导入的css合并在了一起。
  3. 合并后的css文件再继续传递,发送给style-loader处理,它最终将样式内容插入到了html头部的style标签下,页面也因此添加了样式。

从上面的案例我们看出,每个loader的职责都是单一的,自己只负责自己的那一小块.但不管什么格式的文件,只要将特定功能的loader组合起来,它就能增强webpack的能力,使各种稀奇古怪的文件都能被正确识别并处理。

什么是loader

简单了解loader作用后,那loader到底是什么呢?

loader其实就是一个内容转换器,将webpack不能识别的文件,转换为标准的js模块,交给webpack处理。

loader的本质是一个导出一个函数的 JavaScript 模块,这也正体现了 webpack 中一切皆模块的思想。

webpack内部的loader runner会调用此函数,然后将上一个loader产生的结果或者资源文件传入进去。函数中的this作为上下文会被webpack填充。

js 复制代码
/**
 * @param {string/Buffer} content源文件的内容
 * @param {object}[map] sourcemap相关的数据
 * @param {any} [meta] 元数据,可以是任何内容
 */
module.exports = function (content, map, meta) {
    // 将webpack不能识别的文件内容content,转换处理后,进行返回
    return content
}

loader分类

在 Webpack 中,loader 可以被分为 4 类,分别是:

  • pre 前置 loader
  • normal 普通 loader
  • inline 内联 loader
  • post 后置 loader

其中 prepost loader,可以通过 rule 对象的 enforce 属性来指定,不指定时,默认为normal loader。

js 复制代码
// webpack.config.js
const path = require("path");

module.exports = {
  module: {
    rules: [
      {
        test: /.txt$/i,
        use: ["a-loader"],
        enforce: "post", // post loader
      },
      {
        test: /.txt$/i,
        use: ["b-loader"], // normal loader
      },
      {
        test: /.txt$/i,
        use: ["c-loader"],
        enforce: "pre", // pre loader
      },
    ],
  },
};

inline loader

说完,上面三种loader后,我们再简单介绍下 inline loader

webpack允许在引入模块的时候直接指定loader,这样指定loader的方式称之为inline loader,具体如下所示:

js 复制代码
import common from 'loader-a!loader-b!loader-c?type=abc!./common.js'

每个loader之前同!隔开,允许携带query参数,最后的模块也使用!隔开

内联的 inline loader 不在webpack的配置文件中配置,它仅在业务代码中配置且使用不多。我们不过多讲解。

loader执行顺序

那loader执行顺序是怎样的呢?

1. 相同优先级

首先我们要知道,loader执行过程分为两个阶段,分别是 pitchingnormal 阶段。

js 复制代码
// a-loader.js
const loader = function (content, map, meta) {
 console.log("a-loader执行");
 return content;
};

loader.pitch = function () {
 console.log("a-loader pitch执行");
};

module.exports = loader;

// b-loader.js

const loader = function (content, map, meta) {
 console.log("b-loader执行");
 return content;
};

loader.pitch = function () {
 console.log("b-loader pitch执行");
};

module.exports = loader;

// c-loader.js

const loader = function (content, map, meta) {
 console.log("c-loader执行");
 return content;
};

loader.pitch = function () {
 console.log("c-loader pitch执行");
};

module.exports = loader;

// 打印结果
// a-loader pitch执行
// b-loader pitch执行
// c-loader pitch执行
// c-loader执行
// b-loader执行
// a-loader执行

所以,对相同优先级的loader来说,

  • 先按从上到下从左到右的顺序执行每个loader上的pitch方法。
  • 再按从右到左从下到上的顺序执行每个loader函数。

2. 不同优先级

对于不同优先级来说,pre(前置) => normal(普通) => inline(内联) => post(后置)

pitching阶段

上面loader的执行顺序中,提到了loader的pitching阶段,那再来说一下这个pitching阶段到底是怎么一回事。

webpack规定,在每个loader上可以有一个pitch属性,该属性指向一个函数。

js 复制代码
// a-loader.js
const loader = function (content, map, meta) {
 console.log("a-loader执行");
 return content;
};

loader.pitch = function () {
 console.log("a-loader pitch执行");
};

module.exports = loader;

loader.pitch没有返回值时,按照上面的执行顺序执行。

那如果picth loader有返回值呢?

js 复制代码
function loader2(content, map, meta) {
  console.log("loader2 的 normal 阶段")
  return content + "//(loader2)";
}

loader2.pitch = function() {
  console.log("loader2 的 pitching 阶段")
  return 'Jolyne'
}

module.exports = loader2

在 Pitching 阶段,如果当前 Loader.pitch 有返回值,就直接结束当前 loaderPitching 阶段,并直接跳到当前 Loader 执行 pitching 阶段时的 前一个 loader 的normal 阶段,然后继续执行。

同步、异步loader

1.同步loader

如果我们loader中没有异步操作,那这就是一个同步loader。

这个loader必须通过return或者this.callback来返回结果,交给下一个loader来处理。this.callback方法则更加灵活,因为它允许传递多个参数,不仅仅是content。

js 复制代码
module.exports = function (content,map,meta){
  return content;
}

module.exports = function (content,map,meta){
  this.callback(null,content,map,meta)
  // 第一个参数必须是error或者null
  // 第二个参数是一个string 或者 Buffer
  // 第三个和第四个参数是可选的
}

2.异步loader

那么,有时候我们使用loader时会进行一些异步的操作;我们希望在异步操作完成后,再返回这个loader处理的结果,这个时候就要使用异步的loader。

对于异步loader,使用this.async来获取callback函数,等后端操作完成后,再调用callback函数。

js 复制代码
module.exports = function(content,map,meta) {
  const callback = this.async();
  
  setTimeOut(function(){
    console.log("loader执行完成",content);
    callback(null,content);
   },2000)
}

自定义loader

我们在日常开发中,会遇到一些特殊的需求,需要自定义loader来处理。

比如,我们想将px单位转换成vw单位,就可以自定义一个loader来处理。

js 复制代码
// 创建jsxPxToVwLoader.js文件自定义loader
const defaultOptions = {
  viewportWidth: 375, //  设计稿视口宽度
  unitPrecision: 5, //  转换后保留精度
}
module.exports = function (source) {
  // 拿到options参数
  const option = this.getOptions();
  // 合并参数
  const options = { ...defaultOptions, ...option };
  // 匹配以数字开头,中间可能带小数点的,并且以px结尾的
  const reg = /\b(\d+(.d+)?)px\b/g;
  if (reg.test(source)) {
    const { viewportWidth, unitPrecision } = options;
    const p = 100 / viewportWidth; // 1px = (val)vw
    return source.replace(reg, (data, val) => {
      let value = p * val;
      value = parseFloat(value.toFixed(unitPrecision))
      return value === 0 ? value : value + 'vw'
    })
  } else {
    return source;
  }
}
js 复制代码
// webpack.config.js引入
module: {
    rules: [
      {
        test: /.jsx/,
        exclude: /node_modules/,
        use: [{
          loader: path.resolve(__dirname, './jsxPxToVwLoader.js'),
          options: {
            viewportWidth: 375, //  设计稿视口宽度
            unitPrecision: 3, //  转换后保留精度
          }
        }]
      }
    ]
}

常见loader

下面是我们开发中,一些常见的loader介绍与用法,让我们一起来看一下吧。

1. file-loader

file-loader 将打包过程中遇到的文件,复制到对应输出目录,并返回文件的路径。

shell 复制代码
npm install --save-dev file-loader
js 复制代码
rules: [
    // ...,
    {
        test: /.(png|jpe?g|gif)$/,
        use: {
            loader: "file-loader",
            options: {
                // placeholder 占位符 [name] 源资源模块的名称
                // [ext] 源资源模块的后缀
                name: "[name]_[hash].[ext]",
                //打包后的存放位置
                outputPath: "./images",
                // 打包后文件的 url
                publicPath: './images',
            }
        }
    }
]

2. url-loader

url-loader 可以将打包过程中用到的文件转换为 base64 格式字符串,这样可以减少网络请求次数。

js 复制代码
npm install --save-dev url-loader
js 复制代码
rules: [
  // ...,
 {
  test: /.(png|jpe?g|gif)$/,
    use: {
      loader: "url-loader",
      options: {
        // placeholder 占位符 [name] 源资源模块的名称
        // [ext] 源资源模块的后缀
        name: "[name]_[hash].[ext]",
        //打包后的存放位置
        outputPath: "./images",
        // 打包后文件的 url
        publicPath: './images',
        // 小于 100 字节转成 base64 格式
        limit: 100
      }
    }
 }
]

3. raw-loader

raw-loader 提取文件的内容,转换为字符串形式进行返回。

js 复制代码
npm install --save-dev raw-loader
js 复制代码
module.exports = {
    // ...,
    module: {
        rules: [
            {
                test: /.(txt|md)$/,
                use: 'raw-loader'
            }
        ]
    }
}

4. style-loader

style-loader 可以将 css 代码注入到 js 中,运行时,通过创建 style 标签,将样式插入到 head 中。

js 复制代码
npm install --save-dev style-loader
js 复制代码
module.exports = {
    // ...,
    module: {
        rules: [
            {
                test: /.css$/,
                use: 'style-loader'
            }
        ]
    }
}

5. css-loader

分析 css 模块之间的关系,并合成⼀个 css

shell 复制代码
npm install --save-dev css-loader
js 复制代码
rules: [
    // ...,
    {
        test: /.css/,
        use: {
            loader: "css-loader",
            options: {
                // 启用/禁用 url() 处理
                url: true,
                // 启用/禁用 @import 处理
                import: true,
                // 启用/禁用 Sourcemap
                sourceMap: false
            }
        }
    }
]

6. less-loader

将 less 转换为 css,sass-loader 同理

shell 复制代码
npm install --save-dev less-loader
js 复制代码
rules: [
    // ...,
    {
        test: /.less$/,
        use: [
            "style-loader",
            {
                loader: "css-loader",
                options: {
                    // 启用/禁用 url() 处理
                    url: true,
                    // 启用/禁用 @import 处理
                    import: true,
                    // 启用/禁用 Sourcemap
                    sourceMap: false
                }
            },
            "less-loader"
        ]
    }
]

7. postcss-loader

postcss-loader 可以将 css 代码转换为兼容性更好的 css 代码,如添加浏览器前缀等。

autoprefixercssnano都是 postcss-loader 的插件,autoprefixer用来自动添加浏览器前缀,cssnano用来压缩 css 代码。

shell 复制代码
npm install postcss-loader autoprefixer cssnano --save-dev

项目根目录创建postcss.config.js,也可以在postcss-loader的options中配置

js 复制代码
module.exports = {
 plugins: [
  require('precss'),
  require('autoprefixer')({
   'browsers': [
    'defaults',
    'not ie < 11',
    'last 2 versions',
    '> 1%',
    'iOS 7',
    'last 3 iOS versions'
   ]
  })
 ]
}

添加 postcss-loader

js 复制代码
rules: [
    // ...,
    {
        test: /.css$/,
        use: [
            "style-loader",
            "css-loader",
            "postcss-loader"
        ]
    }
]

8. html-loader

html-loader 可以将 html 文件转换为字符串形式,我们有时候想引入一个html页面代码片段赋值给DOM元素内容使用。

shell 复制代码
npm install --save-dev html-loader
js 复制代码
import Content from "../template.html"

document.body.innerHTML = Content
js 复制代码
rules: [
    // ...,
    {
        test: /.html$/,
        use: 'html-loader'
    }
]

9. babel-loader

babel-loader 可以将 ES6+ 代码转换为 ES5 代码,以便浏览器可以兼容。

shell 复制代码
npm install --save-dev babel-loader @babel/core @babel/preset-env
js 复制代码
module.exports = {
    // ...,
    module: {
        rules: [
            {
                test: /.js$/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: [
                            [
                                "@babel/preset-env",
                                {
                                    // 按需加载
                                    useBuiltIns: "usage",
                                    // 指定core-js版本
                                    corejs: 3
                                }
                            ]
                        ]
                    }
                }
            }
        ]
    }
}

10. ts-loader

ts-loader 可以将 TypeScript 代码转换为 JavaScript 代码。

shell 复制代码
npm install --save-dev ts-loader typescript
js 复制代码
module.exports = {
    // ...,
    module: {
        rules: [
            {
                test: /.ts$/,
                use: 'ts-loader'
            }
        ]
    }
}

11. eslint-loader

eslint-loader 可以将 eslint 代码检查集成到 webpack 中。

shell 复制代码
npm install --save-dev eslint-loader eslint
js 复制代码
module.exports = {
    // ...,
    module: {
        rules: [
            {
                test: /.js$/,
                use: {
                    loader: "eslint-loader",
                    options: {
                        // 自动修复
                        fix: true
                    }
                }
            }
        ]
    }
}

12. vue-loader

vue-loader 用来转换 .vue 文件。

shell 复制代码
npm install -D vue-loader vue-template-compiler
js 复制代码
// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
    module: {
        rules: [
            // ... 其它规则
            {
                test: /.vue$/,
                loader: 'vue-loader'
            }
        ]
    },
    plugins: [
        // 请确保引入这个插件!
        new VueLoaderPlugin()
    ]
}

以上loader配置仅为参考,具体还要与时俱进,以官方文档为准。

完结撒花,你又进步了一点点。

ᕦ( •̀∀•́)ᕤ

相关推荐
谢尔登24 分钟前
【Next】路由处理
服务器·javascript·css
一雨方知深秋28 分钟前
WEB APIS(DOM对象,操作元素内容,属性,表单属性,自定义属性,定时器)
开发语言·前端·javascript
EasyNTS28 分钟前
视频流媒体播放器EasyPlayer.js网页直播/点播播放器:如何清除浏览器缓存
开发语言·javascript·缓存
三金1213840 分钟前
整站使用Vue(工程化)
前端·javascript·vue.js
什么都什么1 小时前
YonBuilder移动开发鸿蒙版本编译教程
javascript·app·移动开发·harmonyos·yonbuilder·纯血鸿蒙·apicloud
blzlh2 小时前
手把手教你做网易云H5页面,进大厂后干的第一件事
前端·javascript·css
梅子酱~3 小时前
Vue 学习随笔系列十七 -- 表格样式修改
javascript·vue.js·学习
刺客-Andy3 小时前
React第四节 组件的三大属性之state
前端·javascript·react.js
黄毛火烧雪下3 小时前
React 表单Form 中的 useWatch
前端·javascript·react.js
爱健身的小刘同学3 小时前
钉钉免登录接口
前端·javascript·钉钉