3分钟学会写一个webpack loader

287 阅读1分钟

自定义webpack loader主要用于处理文件内容,比如解析自定义格式的文件(最常见的如解析.vue文件或jsx文件),或者对文件内容进行替换。

案例:解析自定义格式的样式文件

这里假设有一个文件:index.uss,格式为yaml,期望通过这个文件生成原子级样式, 文件内容如下:

space:
  unit: 
    px: 0, 4, 8, 16, 24, 48, 120, 240
    full: 100%
fontSize:
  px: 12, 14, 16, 18, 24, 36, 48
  base: 14

预期能解析为

.m-0 { margin: 0px; } 
.p-0 { padding: 0px; } 
.m-4 { margin: 4px; } 
.p-4 { padding: 4px; }
.f-48 { font-size: 48px; } 
.f-base { font-size: 14px; }
...

这种自定义格式的文件的解析需求,非常符合loader的使用场景。

loader实现

我们可以在项目中定义loader如下:

const YAML = require('yaml')


module.exports = function (source) {
  
  
    const config = YAML.parse(source)


    const spaceUnit = config.space.unit.px.split(',');
    const full = config.space.unit.full;
    const fontSize = config.fontSize.px.split(',');
    const fontBase = config.fontSize.base;
    let content = '';
    let spaceItem = [['m','margin'],['p','padding']];
    for(let i=0; i<spaceUnit.length; i++){
        let unit = spaceUnit[i];
        spaceItem.forEach(([sort,name])=>{
            content+=`.${sort}-${unit.trim()} { ${name}: ${unit}px; } \n`
        })
    
        
    }
    spaceItem.forEach(([sort,name])=>{
        content+=`.${sort}-full { ${name}: ${full}; } \n`
    })
    fontSize.forEach((size,index)=>{
        content+=`.f-${size.trim()} { font-size: ${size}px; } \n`
    })
    content+=`.f-base { font-size: ${fontBase}px; } \n`;
    return content;
}

更改webapck配置

该文件被自定义的loader处理完后,需要交给css-loader和style-loader进行后续处理:

 module: {
    rules: [{
      test: /\.uss$/i,
      use: ['style-loader', 'css-loader', path.resolve('./ucss-loader.js')]
    },
  ...
  },

这里有个需要注意的是,如果是项目本地的loader,需要通过path.resolve引入(当然也可以设置resolveLoader的配置项,但并不如这种方式直接)

最终效果

最终如预期一样,生成了原子样式: image.png

loader的其他常用api

可以在loader的fucntion中通过this访问其他api

this.async

返回一个callback,在loader中处理异步任务

import path from 'path';

export default function (source) {
  var callback = this.async();
  var headerPath = path.resolve('header.js');

  this.addDependency(headerPath);

  fs.readFile(headerPath, 'utf-8', function (err, header) {
    if (err) return callback(err);
    callback(null, header + '\n' + source);
  });
}

this.getOptions

如果loader需要传入配置参数,可以用该api获取传入项

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

  source = source.replace(/[name]/g, options.name);

  return `export default ${JSON.stringify(source)}`;
}