前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >写一个自定义loader,看完,就会

写一个自定义loader,看完,就会

作者头像
Maic
发布2022-07-28 12:42:20
3720
发布2022-07-28 12:42:20
举报
文章被收录于专栏:Web技术学苑

webpackloader本质上是一个导出的函数,loader runner[1]会调用该函数,在loader函数内部,this的上下文指向是webpack,通常loader内部返回的是一个string或者Buffer。当前loader返回的结果,会传递给下一个执行的loader

今天一起学习一下webpack5中的loader,让我们进一步加深对webpack的理解

正文开始...

开始一个loader

首先我们看下,通常情况下loader是怎么使用的

代码语言:javascript
复制
  module.exports = {
    ...
    module: {
    rules: [
      {
        test: /\.js$/,
        use: [
           {
             loader: 'babel-loader',
             options: {
               presets: ['@babel/env']
             }
           },
        ]
      }
    ]
  },
  }

module.rules下,use是一个数组,数组中是可以有多个loader默认情况loader:'babel-loader'会从node_modules中的lib/index.js中执行内部的_loader函数,然后通过内部@babel/core这个核心库对源代码进行ast转换,最终编译成es5的代码

现在需要自己写个loader,参考官方文档writing loader[2]

我们在新建一个loader目录,然后新建test-loader

代码语言:javascript
复制
module.exports = function (source) {
  console.log('hello world')
  return source;
}

rules中我们修改下

代码语言:javascript
复制
const path = require('path')
module.exports = {
 module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: path.resolve(__dirname, 'loader/test-loader.js'),
          }
        ]
      }
    ]
 }
}

当我运行npm run start时,我们会发现loader中加载的自定义test-loader已经触发了。

但是官方提供另外一种方式

resolveLoader中可以给加载loader快捷的注册路径,这样就可以像官方一样直接写test-loader了,这个是文件名,文件后缀名默认可以省略。

代码语言:javascript
复制
module.exports = {
   module: {
        rules: [
          {
            test: /\.js$/,
            use: [
              {
                loader: 'test-loader',
              }
            ]
          }
        ]
    },
  resolveLoader: {
    modules: ['node_modules', './loader']
  }, 
}

我们知道loader中可以设置options,而在自定义loader是如何获取options的参数呢?

官方提供了loader的一些接口api-loader[3]

getOptions

获取loader传过来的options

代码语言:javascript
复制
// loader/test-loader.js
module.exports = function (source) {
  const options = this.getOptions();
  console.log(options);
  console.log('hello world')
  return source
}

我们可以看到以下options传入的参数

代码语言:javascript
复制
  ...
  use: [
          {
            loader: 'test-loader',
            options: {
              name: 'Maic',
               age: 18
             }
          }
   ]

在官方提供了一个简单的例子,主要是用schema-utils验证options传入的数据格式是否正确

安装schema-utils

代码语言:javascript
复制
npm i schema-utils --save-dev

test-loader中引入schema-utils

代码语言:javascript
复制
// 定义schema字段数据类型
const schema = {
  type: 'object',
  properties: {
    name: {
      type: 'string',
      description: 'name is require string'
    },
    age: {
      type: 'number',
      description: 'age is require number'
    }
  }
}
// 引入validate
const { validate } = require('schema-utils');
module.exports = function (source) {
  // 获取loader传入的options
  const options = this.getOptions();
  validate(schema, options);
  console.log(options);
  console.log('hello world')
  return source
}

当我把rulesoptions修改类型时

代码语言:javascript
复制
{
  use: [
      {
        loader: 'test-loader',
        options: {
          name: 'Maic',
          age: '18'
        }
      }
  ]
}

运行npm run start

直接提示报错了,相当于validate这个方法帮我们验证了loader传过来的options,如果传入的options类型不对,那么直接报错了,我们可以用此来检验参数的类型。

自定义babel-loader

在之前的所有项目中,我们都会使用这个babel-loader,那我们能不能自己实现一个自定义的babel-loader呢?

首先我们要确定,babel转换es6,我们需要安装依赖两个插件,一个是@babel/core核心插件,另一个是@babel/preset-env预设插件

修改rules,我们现在使用一个test-babel-loader插件

代码语言:javascript
复制
...
{
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: 'test-babel-loader',
            options: {
              presets: ['@babel/preset-env'] // 预设
            }
          },
          {
            loader: 'test-loader',
            options: {
              name: 'Maic',
              age: 18
            }
          }
      ]
    }
    ]
  },
  resolveLoader: {
     modules: ['node_modules', './loader']
  },
}

修改test-babel-loader

代码语言:javascript
复制
// 引入@babel/core核心库
const babelCore = require('@babel/core');
module.exports = function (content) {
  // 获取options
  const options = this.getOptions();
  // 必须异步方式
  const callback = this.async();
  // 转换es6
  babelCore.transform(content, options, (err, res) => {
    if (err) {
      callback(err);
    } else {
      callback(null, res.code);
    }
  })

index.js中写入一些es6代码

代码语言:javascript
复制
const sayhello = () => {
  const str = 'hello world';
  console.log(str)
}
sayhello();

然后在package.json写入打包命令

代码语言:javascript
复制
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack server --port=8081",
    "build": "webpack"
  },

我们执行npm run build

test-loadertest-babel-loader都会执行,而且生成的main.js源代码的es6已经被转换成es5了。

写一个自定义markdown-loader

首先我们在loader目录下新建一个markdown-loader.js

代码语言:javascript
复制
// markdown-loader.js
module.exports = function (content) {
  console.log(content)
  return content;
}

然后在rules中加入自定义loader

代码语言:javascript
复制
  {
      test: /\.md$/,
      loader: 'markdown-loader'
  }
  ...

我们需要在src/index.js中引入md文件

代码语言:javascript
复制
import md from '../doc/index.md';

const sayhello = () => {
  const str = 'hello world';
  console.log(str)
}
sayhello();

我们运行npm run build

已经获取到了doc/index.md的内容了

在loader中我需要解析md的内容,此时我们需要借助一个第三方的md解析器marked[4]

代码语言:javascript
复制
npm i marked --save-dev

详细使用文档参考markedjs[5]

代码语言:javascript
复制
const { marked } = require('marked');
module.exports = function (content) {
  // 解析md
  const ret = marked.parse(content)
  console.log(ret);
  return ret;
}

我们运行npm run build

此时依然报错,错误提示You may need an additional loader to handle the result of these loaders.

所以需要解析html,那么此时需要另外一个loader来解决,html-loader

代码语言:javascript
复制
npm i html-loader --save-dev

然后添加html-loader

代码语言:javascript
复制
 {
  test: /\.md$/,
  use: ['html-loader', 'markdown-loader']
 }

我们在看下index.js

代码语言:javascript
复制
import md from '../doc/index.md';
console.log(md)
const sayhello = () => {
  const str = 'hello world';
  console.log(str)
}
sayhello();

我们在index.js打印引入的md就一段html-loader转换过的最终代码

代码语言:javascript
复制
import md from '../doc/index.md';
const sayhello = () => {
  const str = 'hello world';
  console.log(str)
}
sayhello();
const renderMd = () => {
  const app = document.getElementById('app');
  const div = document.createElement('div');
  div.innerHTML = md;
  app.appendChild(div);
}
renderMd();

我么最终就看到md文件就成功通过我们自己写的loader给转换了

本质上就是将md转换成html标签,然后再渲染到页面上了

总结
  • 了解loader的本质,实际上就是一个导出的函数,该函数只能返回字符串或者Buffer,内部提供了很多钩子,比如getOptions可以获取loader中的options
  • loader的执行顺序是从下往上或者从右往左,在后一个loader中的content是前一个loader返回的结果
  • loader有两种类型,一种是同步this.callback,另一种是异步this.async
  • 了解自定义babel转换,通过@bable/core,@babel/preset-env实现es6转换
  • 实现了一个自定义markdown转换器,主要是利用marked.js这个对md文件转换成html,但是html标签进一步需要html-loader
  • 本文示例code-example[6]
参考示例

[1]loader runner: https://github.com/webpack/loader-runner

[2]writing loader: https://webpack.docschina.org/contribute/writing-a-loader/

[3]api-loader: https://webpack.docschina.org/api/loaders/

[4]marked: https://github.com/markedjs/marked

[5]markedjs: https://marked.js.org/

[6]code-example: https://github.com/maicFir/lessonNote/tree/master/webpack/webpack-12-loader

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Web技术学苑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始一个loader
  • getOptions
  • 自定义babel-loader
  • 写一个自定义markdown-loader
  • 总结
  • 参考示例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档