loader的原理探究

前言

loader几乎是webpack配置必备,但是我们用它的时候有没有想过它的原理到底是怎么回事呢?今天就让我们来探究一下它的原理吧。

loader的作用

在探究原理之前,我们先来回顾一下常用的loader与及相关的作用。

如处理css文件的
style-loader, css-loader, sass-loader

module:{
  rules: [
    {
        test: /\.css$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
  ]
}

loade执行顺序是从右往左执行的。至于为什么是从右到左执行而不是从左到右?因为webpack选择了compose这样的函数式编程方式,而从左到右则是pipe式编程。

所以上面例子的执行顺序是遇到.scss的文件就先经过sass-loader 处理再到 css-loader 再到style-loader,我们可以猜测到写的scss文件经过处理得到css字符串,再给css-loader序列化处理,最后给style-loader处理创建style标签插入到html

所以我们可以从这些现象就可以猜出来loader其实就像一个函数,有参数输入,经过处理,然后将处理好的结果输出。

可以打开webpack官网loader部分https://webpack.js.org/api/loaders/,这里详细指出了如何写一个loader。

如写一个 sync-loader.js

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

最后导出一个函数,这个函数有几个参数,其中主要用到的就是content这个参数,它表示读取到的对应的文件的源码内容,类型可以是string 也可以是buffer。我们就可以在这个方法里面对这些源码进行操作了。

laoder实战

弄清楚原理之后,我们自己手写3个loader,创建src/myLoaders目录
创建 my-sass-loader.js、 my-css-loader.js、 my-style-loader.js,


image.png

再配置好webpack.config.js如下

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
    mode: 'development',
    entry: {
        index: './src/index.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    resolveLoader: {
        modules: ['node_modules', './src/myLoaders'],
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.scss$/,
                use: ['my-style-loader', 'my-css-loader', 'my-sass-loader']
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin(), // 输出html模板
        new CleanWebpackPlugin(), // 清除dist目录
    ]
}

注意配置中配置了resolveLoader来自定义loader的存放目录的

 resolveLoader: {
        modules: ['node_modules', './src/myLoaders'],
    }

和scss 的loader配置改为了我们自己写的loader

{
    test: /\.scss$/,
    use: ['my-style-loader', 'my-css-loader', 'my-sass-loader']
}

配置好之后,分别写这三个loader要做的事情,
首先是my-sass-loader, 利用sass的api对scss文件内容进行编译

const sass = require('sass')
module.exports = function(content) {
    const result = sass.renderSync({
        data: content
    })
    return result.css
}

其次是my-css-loader, 对编译后的文件进行序列化

module.exports = function(content) {
    return JSON.stringify(content)
}

最后是my-style-loader, 对css字符串进行创建style标签并插入到head中

module.exports = function(content) {
    return `
    const style = document.createElement('style');
    style.setAttribute('id', 'my-style-loader');
    style.innerHTML = ${content};
    document.head.appendChild(style);
    `
}

为了查看效果,我额外加了一句 style.setAttribute('id', 'my-style-loader');

index.scss代码:

body {
    background: red;
    div {
        background: blue;
        color: #fff;
    }
}

写完了,npm run dev


image.png

打开dist/index.html查看效果


image.png

ok, 没问题,自己实现了三个loader。有人问为什么要三个?一个不行吗?其实一个也可以的,但是这样子的写法不符合函数式编程的规范,不符合官方提倡的写法。官方要求一个loader只做一件事。这样子函数会更加纯粹,纯粹的函数对于可维护性、可扩展性、可重构性大大有利,这才是体现一个程序员的内涵的地方。否则你养成了经常写一坨偶合代码的习惯将会影响日后的成长。

如果要获取参数怎么办?

在loader函数内通过this.query获取;而且因为要使用this ,所以,导出的函数必须是module.exports = function(){}, 不能是箭头函数。

 console.log(this.query)

如果要返回多个结果怎么办?

可以用 this.callback

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

如果要返回异步结果呢?

以上都是同步执行返回loader的处理的结果,同理,业务中可能会有异步处理的结果,就用this.async,如下

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

总结

  • loader就是一个必须有返回值(String | Buffer)的函数
  • loader函数对源文件进行增删改处理,将处理好的结果返回给webpack。
  • loader不能是箭头函数,因为内部可以采用this
  • loader支持同步异步、返回结果
  • loader的参数通过this.query获取
  • resolveLoaders可以配置存放loader的路径

以上是我看法,如有不对请留言讨论,谢谢。

你可能感兴趣的:(loader的原理探究)