Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

编写webpack loader #15

Open
muwoo opened this issue May 2, 2018 · 0 comments
Open

编写webpack loader #15

muwoo opened this issue May 2, 2018 · 0 comments

Comments

@muwoo
Copy link
Owner

muwoo commented May 2, 2018

关于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 ( 一 )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant