译文 编写一个loader

https://doc.webpack-china.org/contribute/writing-a-loader

  loader是一个导出了函数的node模块,当资源须要被这个loader所转换的时候,这个函数就会被执行,这个函数可以通过this访问loader api。有三种方式来本地开发和测试loader。

设置

  测试一个单独的loader,可以通过path.resolve一个本地文件来加载loader:

{
  test: /\.js$/
  use: [
    {
      loader: path.resolve('path/to/loader.js'),
      options: {/* ... */}
    }
  ]
}

  测试多个loader的话,可以通过以下方式来添加webpack对loader的搜索路径:

resolveLoader: {
  modules: [
    'node_modules',
    path.resolve(__dirname, 'loaders')
  ]
}

  第三种方式:通过 npm link 从仓库中引入loader包进我们的项目中

简单用法

  当一个loader被调用时,被导出的函数会被执行,它有一个字符串参数,代表了文件的内容。函数应该返回一个或两个值,分别代表字符串js代码和一个可选的js对象sourceMap,对于复杂的情况,可以调用 this.callback(err, values...) 来返回两个以上的值,里面的err可以直接抛出或者传递给callback都可以,返回一个值时直接return 即可

复杂用法

  对于loader的链式调用,他们的调用次序是从后往前或者从下往上,loader接收上一个loader的处理结果作为参数

loader的书写方针

简单

  loader做单一简单的工作,这有利于使每个loader变得简单,而且使他们更方便地链式调用

链式调用

  使用loader可以被链式调用的优势,把一个任务分成多个简单步骤,让每个简单的loader分别去完成。

模块化

  使输出变得模块化。

无状态

  保证loader每次运行仅仅依赖于上一个loader的输出结果

loader的依赖

  如果一个loader须要读取外部的资源(如读取文件系统),则必须进行依赖声明(使用addDependency),这是为了在观察模式下使缓存无效以及重新编译

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);
  });
};

模块内的依赖

  对于不同类型的模块,有不同的处理依赖的方式,如css,@import和url(...)会引入依赖,这些依赖须要被模块系统来处理,这有两种进行处理:

  1. 转换为require
  2. 使用this.resolve函数处理路径

  对于第一种方式,css-loader是一个好例子;而对less,不能仅仅对import或url()进行require替换,因为less须要被编译,所以less-loader须要使用第二种方式,通过webpack来处理依赖

抽取模块内的公共代码

  使用loader对模块进行处理时,避免生成公共代码,可以在loader进行处理的时候,生成一个运行时文件,然后通过require实现模块之间的代码共享,而不是在每个模块中都重复生成相同的代码

绝对路径

  不要在模块代码中使用绝对路径,因为当根目录发生变化(项目迁移),文件的hash会发生变化,在loader-utils中有一个函数stringifyRequest可以将绝对路径转为相对路径

peer 依赖

  可以把对其他包的依赖作为一个perrDependency,如sass-loader依赖于node-sass:

"peerDependencies": {
  "node-sass": "^4.0.0"
}

测试

  使用jest对loader进行测试

npm i --save-dev jest babel-jest babel-preset-env

 

 .babelrc

{
  "presets": [[
    "env",
    {
      "targets": {
        "node": "4"
      }
    }
  ]]
}

 创建一个src/loader.js实现功能:处理一个txt文件,将里面的[name]替换为options里的name值。返回将结果作为一个模块返回

import { getOptions } from 'loader-utils';

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

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

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

要处理的文件test/example.txt如下:

Hey [name]!

 下一步是使用nodeApimemory-fs来启动webpack,这可以让我们把生成的文件提交到内存中,还可以获取文件stats数据。下面进行安装:

npm i --save-dev webpack memory-fs

test/compiler.js

import path from 'path';
import webpack from 'webpack';
import memoryfs from 'memory-fs';

export default (fixture, options = {}) => {
  // 生成一个webpack实例
  const compiler = webpack({
    context: __dirname,
    // 这里的entry实际会被替换为要处理的文件,entry可以理解为是第一个要被处理的文件
    entry: `./${fixture}`, 
    output: {
      path: path.resolve(__dirname),
      filename: 'bundle.js',
    },
    module: {
      rules: [{
        test: /\.txt$/,
        use: {
          loader: path.resolve(__dirname, '../src/loader.js'),
          options: {
            name: 'Alice'
          }
        }
      }]
    }
  });
  
  // 将webpack的输出文件设置到内存中  
  compiler.outputFileSystem = new memoryfs();
 
  // webpack 的run接收的函数的第二个参数代表webpack输出的文件信息
  return new Promise((resolve, reject) => {
    compiler.run((err, stats) => {
      if (err) reject(err);

      resolve(stats);
    });
  });
}

 开始进行测试,执行 test/loader.test.js

import compiler from './compiler.js';

test('Inserts name and outputs JavaScript', async () => {
  // 这里指定的被测试文件会被替换为以上的entry文件
  const stats = await compiler('example.txt');
  const output = stats.toJson().modules[0].source;

  expect(output).toBe(`export default "Hey Alice!\\n"`);
});

 

posted @ 2017-12-28 14:15  HelloHello233  阅读(420)  评论(0编辑  收藏  举报