1. loader #

loader是导出为一个函数的node模块。该函数在loader转换资源的时候调用。给定的函数将调用loader API,并通过this上下文访问。

1.1 配置loader #

1.1.1 匹配(test)单个 loader #

{
  test: /\.js$/
  use: [
    {
      loader: path.resolve('path/to/loader.js'),
      options: {/* ... */}
    }
  ]
}
const loaderUtils = require('loader-utils');
const validateOptions = require('schema-utils');
module.exports = function (source) {
    const options = loaderUtils.getOptions(this);
    let schema = {
        "type": "object",
        "properties": {
            "content": {
                "type": "string"
            }
        }
    }
    validateOptions(schema, options, '参数校验!');
    console.log(this.context);
    console.log(this.resource);
    console.log(this.resourcePath);
    console.log(this.resoureQuery);
    console.log(this.loadModule);
    console.log(this.resolve);
    console.log(this.addDependency);
    console.log(this.addContextDependency);
    console.log(this.clearDependencies);
    this.cacheable && this.cacheable();
    this.emitFile('myfile.txt', '文件内容');
    let callback = this.async();

    console.log(source);
    // setTimeout(function () {
    //     callback(null, '');
    // }, 100);
    console.log('========================' + options.content + '==================================');
    this.callback(null, 'let a;');
}
//module.exports.raw = true;

1.1 2 匹配(test)多个 loaders #

resolveLoader: {
   modules: [path.resolve('node_modules'), path.resolve(__dirname, 'src', 'loaders')]
},
var less = require('less');
module.exports = function (source) {
    less.render(source, (err, output) => {
        this.callback(err, output.css);
    });
}
var less = require('less');
module.exports = function (source) {
    console.log(source);
    return `let style = document.createElement('style');style.innerHTML = ${JSON.stringify(source)};document.head.appendChild(style);`;
}

1.2 loader用法 #

1.2.1 单个loader用法 #

1.2.2 单个loader用法 #

如果是处理顺序排在最后一个的 Loader,那么它的返回值将最终交给webpack的require,换句话说,它一定是一段用字符串来存储可执行的JavaScript脚本

1.3 . 用法准则 #

1.4 API #

1.4.1 缓存结果 #

webpack充分地利用缓存来提高编译效率

 this.cacheable();

1.4.2 异步 #

当一个 Loader 无依赖,可异步的时候我想都应该让它不再阻塞地去异步

// 让 Loader 缓存
module.exports = function(source) {
    var callback = this.async();
    // 做异步的事
    doSomeAsyncOperation(content, function(err, result) {
        if(err) return callback(err);
        callback(null, result);
    });
};

1.4.3 raw loader #

默认的情况源文件是以 UTF-8 字符串的形式传入给 Loader,设置module.exports.raw = true可使用 buffer 的形式进行处理

module.exports.raw = true;
`

1.4.4 获得 Loader 的 options #

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  // 获取到用户给当前 Loader 传入的 options
  const options = loaderUtils.getOptions(this);
  return source;
};

1.4.5 返回其它结果 #

Loader有些场景下还需要返回除了内容之外的东西。

module.exports = function(source) {
  // 通过 this.callback 告诉 Webpack 返回的结果
  this.callback(null, source, sourceMaps);
  // 当你使用 this.callback 返回内容时,该 Loader 必须返回 undefined,
  // 以让 Webpack 知道该 Loader 返回的结果在 this.callback 中,而不是 return 中 
  return;
};
this.callback(
    // 当无法转换原内容时,给 Webpack 返回一个 Error
    err: Error | null,
    // 原内容转换后的内容
    content: string | Buffer,
    // 用于把转换后的内容得出原内容的 Source Map,方便调试
    sourceMap?: SourceMap,
    // 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,
    // 以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
    abstractSyntaxTree?: AST
);

1.4.6 同步与异步 #

Loader 有同步和异步之分,上面介绍的 Loader 都是同步的 Loader,因为它们的转换流程都是同步的,转换完成后再返回结果。 但在有些场景下转换的步骤只能是异步完成的,例如你需要通过网络请求才能得出结果,如果采用同步的方式网络请求就会阻塞整个构建,导致构建非常缓慢。

module.exports = function(source) {
    // 告诉 Webpack 本次转换是异步的,Loader 会在 callback 中回调结果
    var callback = this.async();
    someAsyncOperation(source, function(err, result, sourceMaps, ast) {
        // 通过 callback 返回异步执行后的结果
        callback(err, result, sourceMaps, ast);
    });
};

1.4.7 处理二进制数据 #

在默认的情况下,Webpack 传给 Loader 的原内容都是 UTF-8 格式编码的字符串。 但有些场景下 Loader 不是处理文本文件,而是处理二进制文件,例如 file-loader,就需要 Webpack 给 Loader 传入二进制格式的数据。 为此,你需要这样编写 Loader:

module.exports = function(source) {
    // 在 exports.raw === true 时,Webpack 传给 Loader 的 source 是 Buffer 类型的
    source instanceof Buffer === true;
    // Loader 返回的类型也可以是 Buffer 类型的
    // 在 exports.raw !== true 时,Loader 也可以返回 Buffer 类型的结果
    return source;
};
// 通过 exports.raw 属性告诉 Webpack 该 Loader 是否需要二进制数据 
module.exports.raw = true;

1.4.8 缓存 #

在有些情况下,有些转换操作需要大量计算非常耗时,如果每次构建都重新执行重复的转换操作,构建将会变得非常缓慢。 为此,Webpack 会默认缓存所有 Loader 的处理结果,也就是说在需要被处理的文件或者其依赖的文件没有发生变化时, 是不会重新调用对应的 Loader 去执行转换操作的。

module.exports = function(source) {
  // 关闭该 Loader 的缓存功能
  this.cacheable(false);
  return source;
};

1.4.9 其它 Loader API #

1.3 BannerLoader #

const path = require('path');
const fs = require('fs');
module.exports = function (source) {
    let callback = this.async();
    let banner = path.resolve(__dirname,'banner.js');
    this.addDependency(banner);
    fs.readFile(banner, 'utf8', (err, banner) => {
        if (err) return callback(err);
        callback(null, banner + '\n\n' + source);
    });
}

1.4 html-layout-loader #

1.4.1 loader-utils #

{
  test: /\.html$/,
     use: {
          loader: 'html-layout-loader',
          options: {
              layout: path.join(__dirname, 'src', 'layout.html'),
              placeholder: '{{__content__}}'
     }
  }
}

plugins: [
        new HtmlWebpackPlugin({
            template: './src/login.html',
            filename: 'login.html'
        }),
        new HtmlWebpackPlugin({
            template: './src/home.html',
            filename: 'home.html'
        })
]
const path = require('path');
const fs = require('fs');
const loaderUtils = require('loader-utils');
const defaultOptions = {
    placeholder: '{{__content__}}',
    decorator: 'layout'
}
module.exports = function (source) {
    let callback = this.async();
    this.cacheable && this.cacheable();
    const options = Object.assign(loaderUtils.getOptions(this), defaultOptions);
    const { placeholder, decorator, layout } = options;
    fs.readFile(layout, 'utf8', (err, html) => {
        html = html.replace(placeholder, source);
        callback(null, `module.exports = ${JSON.stringify(html)}`);
    })
}
const path = require('path');
const fs = require('fs');
const loaderUtils = require('loader-utils');
const defaultOptions = {
    placeholder:'{{__content__}}',
    decorator:'layout'
}
module.exports = function(source){
    let callback = this.async();
    this.cacheable&& this.cacheable();
    const options = {...loaderUtils.getOptions(this),...defaultOptions};
    const {placeholder,layout,decorator} = options;
    const layoutReg = new RegExp(`@${decorator}\\((.+?)\\)`);
    let result = source.match(layoutReg);
    if(result){
        source = source.replace(result[0],'');
        render(path.resolve(this.context,result[1]), placeholder, source, callback)
    }else{
        render(layout, placeholder, source, callback);
    }

}
function render(layout, placeholder, source, callback) {
    fs.readFile(layout, 'utf8', (err, html) => {
        html = html.replace(placeholder, source);
        callback(null, `module.exports = ${JSON.stringify(html)}`);
    })
}