18 深入Loader

深入Loader

Loader的作用就是用于解析处理那些webpack不能识别的非JavaScript模块资源。Loader对Webpack的构建起着至关重要的作用,增加了Webpack构建的可能行,本节将对loader进行详细的介绍。

Loader的使用

Loader有三种方式进行配置使用:

配置文件中配置(推荐):在webpack.config.js文件中指应Loader。

module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test: /\.css$/,
                use: [
                    {
    
     loader: 'style-loader' },
                    {
    
    
                        loader: 'css-loader',
                        options: {
    
    
                            modules: true
                        }
                    }
                ]
            }
        ]
    }
};

内联:在每个import语句中显式指定Loader。

import 'style-loader!css-loader?modules!./styles.css';

CLI:在Shell命令中指定它们。
webpack --module-bind jade-loader --module-bind ‘css=style-loader!css-loader’

通过开发实践,推荐使用配置文件来配置Loader,该方式可以通过代码逻辑来控制相关模块的解析,更适用于复杂的场景。

Loader特性

Loader有用很多特性,了解Loader的特性,对使用好Loader有着极大的帮助。

  • Loader支持链式传递。能够对资源使用流水线(pipeline)。一组链式的Loader将按照相反的顺序执行。Loader链中的第一个Loader返回值给下一个Loader。在最后一个Loader,返回Webpack所预期的JavaScript。
  • Loader可以是同步的,也可以是异步的。
  • Loader运行在Node.js中,并且能够执行任何可能的操作。
  • Loader接收查询参数。用于对Loader传递配置。
  • Loader也能够使用options对象进行配置。
  • 除了使用package.json常见的main属性,还可以将普通的Npm模块导出为loader,做法是在package.json里定义一个Loader字段。
  • 插件(Plugin)可以为Loader带来更多特性。 Loader能够产生额外的任意文件。

Loader的本质通过导出是一个预处理函数:

// loader基础结构
module.exports = function (content, map, meta) {
    
    
    return content;
};

可以在该函数中更加灵活地引入细粒度逻辑,例如压缩、打包、语言翻译等等。

解析Loader

Loader遵循标准的模块解析。多数情况下,Loader将从模块路径(通常将模块路径认为是 node_modules)解析。

Loader 模块需要导出为一个函数,并且兼容Node.js的 JavaScript编写。通常使用Npm进行管理,但是也可以将自定义Loader作为应用程序中的文件。按照约定,Loader通常被命名为xxx-loader(例如 css-loader)。

Loader API

所谓的Loader就是一个导出结果为函数的JavaScript模块,Loader runner会调用这个函数,实现对应资源相关的转换操作。大量的Loader API供导出函数内部使用,以此来和Webpack结合实现许多魔法功能。

同步loader

使用return或者this.callback都可以同步地返回转换后的content内容。

使用return:

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

使用this.callback:

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

this.callback相对于return更加灵活,除了传递content,还可以传入其他参数,完整的传参如下:

this.callback(
    err: error | null,
    content: string | buffer,
    sourceMap ?: sourceMap,
    meta ?: any
);
  • 第一个参数必须是error或者null
  • 第二个参数是一个string或者buffer。
  • 可选的:第三个参数必须是一个可以被这个模块解析的source map。
  • 可选的:第四个选项,会被Webpack忽略,可以是任何东西。 可以将抽象语法树(abstract syntax tree -
    AST)(例如 ESTree)作为第四个参数,如果你想在多个Loader之间共享通用的AST,这样做有助于加速编译时间。

异步Loader

过使用this.async来获取callback,可以实现一个异步Loader:

module.exports = function (content, map, meta) {
    
    
    var callback = this.async();
    someAsyncOperation(content, function (err, result) {
    
    
        if (err) return callback(err);
        callback(null, result, map, meta);
    });
};

在Node.js这样的单线程环境下进行耗时长的同步计算不是个好主意,建议尽可能地使你的Loader异步化。但如果计算量很小,同步Loader也是可以的。

Row

默认情况下,资源文件会被转化为UTF-8字符串,然后传给Loader。通过设置Raw,Loader可以接收原始的Buffer。每一个Loader都可以用String或者Buffer的形式传递它的处理结果。Compiler将会把它们在Loader之间相互转换。

module.exports = function (content) {
    
    
    // 类型断言
    assert(content instanceof Buffer);
    return someSyncOperation(content);
    // 返回值也可以是一个 `Buffer`
    // 即使不是 raw loader 也没问题
};
module.exports.raw = true;

Pitching loader

当使用多个Loader的时候,Loader的默认执行顺序是从右到左,从下到上的,可以通过配置Enforce指定Loader的执行顺序。实际在Loader执行之前,会从左到右调用Loader的pitch方法。

比如下面的Loader配置:

module.exports = {
    
    
    module: {
    
    
        rules: [
            {
    
    
                test: /\.render$/,
                use: [
                    'a-loader',
                    'b-loader',
                    'c-loader'
                ]
            }
        ]
    }
};

涉及到的Loader运行步骤可以抽象成如下所示:
在这里插入图片描述
使用Loader的pitch的时候,传递给pitch方法的data,会在执行阶段暴露在this.data下面,并且可以用于在循环时,捕获和共享前面的信息

module.exports = function (content) {
    
    
    return someSyncOperation(content, this.data.value);
};

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
    
    
    data.value = Render;
};

当在某个Loader在pitch方法中返回一个结果,那么当前Loader就会跳过执行剩下Loader的任何方法,比如在b-loader中的pitch返回了一些东西:

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

module.exports.pitch = function (remainingRequest, precedingRequest, data) {
    
    
    if (someCondition()) {
    
    
        return "module.exports = require(" + JSON.stringify("-!" + remainingRequest) + ");";
    }
};

现在所有的Loader执行步骤抽象成如下所示
在这里插入图片描述
更多Loader API请前往https://webpack.js.org/api/loaders/查阅。

加载本地Loader

通过module.rules配置,我们可以使用通过Npm下载的Loader模块,当在本地编写好Loader,需要让Webpack识别并且使用该Loader,下面介绍两种Webpack中加载本地Loader的方法。

ResolveLoader

ResolveLoader用作模块解析规则,用于配置如何寻找Loader模块。为了让webpack去加载本地Loader,需要修改 resolveLoader.modules。

假设本地编写的Loader都放在项目的local-loaders目录下:

module.exports = {
    
    
    resolveLoader: {
    
    
        modules: ['node_modules', path.resolve(__dirname, './local-loaders')]
    }
}

默认情况下modules的值为[‘node_modules’], 表示在node_modules目录下去寻找Loader模块。当前配置表示首先去node_modules目录下去寻找Loader,没有找到在去./local-loaders目录下去寻找。

Npm link

Npm link是专门用于快发开发和调试本地Npm模块的,可以将本地正在开发的一个模块的源码连接到项目的node_modules目录下。

实现Npm link的步骤如下:

  • 需要正确配置本地开发模块的package.json。
  • 在本地的开发模块目录下执行npm link,把该模块注册到全局。
  • 在开发的项目下执行npm link
    module-name,将第二步注册到全局的模块链接到本项目的node_modules下,module-name为第一步中开发模块的package.json文件中配置的模块名称。

如此以来就可以像使用npm下载的模块一样使用本地开发的模块。

编写自己的Loader

当现有Loader无法满足项目需求时,需要开发者自己编写解决问题的loader,本节将简单介绍一下如何编写Loader。

当我们解析Less文件的时候,我们需要安装less-loader,css-loader,style-loader这三个Loader模块,下面我们就简单实现一下上述的三个Loader。

实现less-loader

在这里,我们需要安装less模块,使用less模块来解析less文件:

var less = require("less");
function loader(content) {
    
    
    var callback = this.async();
    less.render(content, (err, result) => {
    
    
        if (err) {
    
    
            return callback(err);
        }
        callback(null, result.css)
    })
}
module.exports = loader;

实现css-loader

function loader(source) {
    
    
    let reg = /url\((.+?)\)/g;
    let pos = 0;
    let content;
    let arr = ['let list=[]'];
    // url地址引用替换成require引用,webpack才能处理
    while (content = reg.exec(source)) {
    
    
        let [matchUrl, g] = content;
        let last = reg.lastIndex - matchUrl.length;
        arr.push(`list.push(${
      
      JSON.stringify(source.slice(pos, last))})`);
        pos = reg.lastIndex;
        arr.push(`list.push('url('+require(${
      
      g})+')')`);
    }
    arr.push(`list.push(${
      
      JSON.stringify(source.slice(pos))})`);
    arr.push(`module.exports = list.join('')`);

    return arr.join('\r\n');
}
module.exports = loader;

实现style-loader

const loaderUtils = require('loader-utils');
function loader(content) {
    
    
    content = content.replace(/\n/g, '\\n');
    let style = `
let style = document.createElement('style');
style.innerHTML = ${
      
      JSON.stringify(content)};
document.head.appendChild(style);
`
    return style;
}

loader.pitch = function (other) {
    
    
    let style = `
let style = document.createElement('style');
style.innerHTML = require(${
      
      loaderUtils.stringifyRequest(this,
        '!!' + other)});
document.head.appendChild(style);
`
    return style;
}
module.exports = loader;

现在使用编写的这三个Loader就可以实现less文件的转换,并正确显示效果。

本章节提供案例源码下载:https://gitee.com/mvc_ydb/webpack/blob/master/loader.zip

おすすめ

転載: blog.csdn.net/sinat_41212418/article/details/121687744
D18