muwoo / blogs

📚一个前端的博客。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

编写webpack loader

muwoo opened this issue · comments

commented

关于webpack loader

Loader 是支持链式执行的,如处理 sass 文件的 loader,可以由 sass-loader、css-loader、style-loader 组成,由 compiler 对其由右向左或者从下向上执行,第一个 Loader 将会拿到需处理的原内容,上一个 Loader 处理后的结果回传给下一个接着处理,最后的 Loader 将处理后的结果以 String 或 Buffer 的形式返回给 compiler。

{
  test: /\.js/,
  use: [
    'bar-loader',
    'foo-loader'
  ]
}

无状态(Stateless)

虽然loader支持链式调用,但是请确保 loader 在不同模块转换之间不保存状态。每次运行都应该独立于其他编译模块以及相同模块之前的编译结果。

编写一个 loader

loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。给定的函数将调用 loader API,并通过 this 上下文访问。那我们便可以这样来定义一下最基本的loader:

// 当一个 loader 在资源中使用,这个 loader 只能传入一个参数 - 这个参数是一个包含包含资源文件内容的字符串
module.exports = function(source) {
  return source;
};

上面使用返回 return 返回,是因为是同步类的 Loader 且返回的内容唯一,如果你希望将处理后的结果(不止一个)返回给下一个 Loader,那么就需要调用 Webpack 所提供的 API。 一般来说,构建系统都会提供一些特有的 API 供开发者使用。Webpack 也如此,提供了一套 Loader API,可以通过在node module中使用 this 来调用,如 this.callback(err, value…),这个 API 支持返回多个内容的结果给下一个 Loader 。

module.exports = function(source) {
  let other = '';
  return this.callback(null, source, other);
};

更多

1. 缓存

从提高执行效率上,如何处理利用缓存是极其重要的。 Mac OS 会让内存充分使用、尽量占满来提高交互效率。回到 Webpack,Hot-Replace 以及 React Hot Loader 也充分地利用缓存来提高编译效率。 Webpack Loader 同样可以利用缓存来提高效率,并且只需在一个可缓存的 Loader 上加一句 this.cacheable(); 就是这么简单:

module.exports = function(source) {
    this.cacheable();
    return source;
};

2. 异步

异步并不陌生,当一个 Loader 无依赖,可异步的时候我想都应该让它不再阻塞地去异步。在一个异步的模块中,回传时需要调用 Loader API 提供的回调方法 this.async(),使用起来也很简单:

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

3. raw loader

默认的情况,原文件是以 UTF-8 String 的形式传入给 Loader,而在上面有提到的,module 可使用 buffer 的形式进行处理,针对这种情况,只需要设置 module.exports.raw = true; 这样内容将会以 raw Buffer 的形式传入到 loader 中了

module.exports = function(content) {
 
};
module.exports.raw = true;

4. loader 工具库

充分利用 loader-utils 包。它提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项。schema-utils 包配合 loader-utils,用于保证 loader 选项,进行与 JSON Schema 结构一致的校验。这里有一个简单使用两者的例子:

const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils')

const schema = {
  type: 'object',
  properties: {
    test: {
      type: 'string'
    }
  }
}

export default function(source) {
  const options = getOptions(this);

  validateOptions(schema, options, 'Example Loader');

  this.callback(null, source);
}

一些例子

比如编写一个处理Vue组件上的style,把px转成rem:

const { getOptions } = require('loader-utils');
const validateOptions = require('schema-utils')

const schema = {
    type: 'object',
    properties: {
        remUnit: {
            type: 'number'
        },
        forcePxProperty: {
            type: 'array'
        }
    }
}

module.exports = function(source) {
    const options = getOptions(this);
    validateOptions(schema, options, 'style2rem Loader');

    source = source.replace(/<template>(.|\n)*?<\/template>/ig, function (content) {
        return content.replace(/style="(.|\n)*?"/ig, function (styleStr) {
            let start = 0
            return styleStr.replace(/:(.|\n)*?px/ig, function (px, num, end) {
                let key = ''
                let need = true
                for (let i = start; i < end; i++) {
                    if (styleStr[i] !== ';' && styleStr[i]!==' ') {
                        key += styleStr[i]
                    }
                }
                options.forcePxProperty.forEach((property) => {
                    if(key.indexOf(property) !== -1) {
                        need = false
                    }
                })
                start = end
                return px && need ? `: ${(parseInt(px.substring(1)) / options.remUnit).toFixed(6)}rem` : px
            })
        })
    })
    this.cacheable();
    this.callback(null, source);
};

然后我们需要添加到webpack loader中:

module: {
        rules: [
            {
                test: /\.vue$/,
                use: [{
                    loader: "vue-loader",
                    options: {
                        preserveWhitespace: false
                    }
                }, {
                    loader: path.join(__dirname, './style2rem'),
                    options: {
                        remUnit: 37.5,
                        forcePxProperty: ['font-size']
                    }
                }]
            }
           ...
        ]
     ...
}

参考资料

如何开发一个 Webpack Loader ( 一 )