动手实现一个loader和plugin

108 阅读2分钟

loader

\

loader API

\

loader本质是个函数,会接受一个参数,参数内容是需要处理的内容,并且需要返回处理好的内容

\

// 最简单的loader,拿到参数直接返回出去
module.exports = source => {
    return source
}

// webpack.config.js
const path = require('path')
const MyPlugin = require('./plugin/MyPlugin')

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    },
    module: {
        rules: [
            {
                test: /.js$/,
                loader: './loader/loader.js'
            }
        ]
    }
    plugins: [
        new MyPlugin()
    ]
}

\

实现一个js语法转换loader,需要几个库

\

  • loader-utils 获取loader的参数等 (如果有需要的话)
  • loader-runner 无需webpack 调试loader (如果有需要的话)
  • schema-utils 对loader的参数进行校验 (如果有需要的话)
  • generate 将es6的代码转换为es5

\

// loader-runner的基本用法
const { runLoaders } = require('loader-runner')
const path = require('path')
const fs = require('fs')
// console.log(runLoaders)
runLoaders({
  resource: './index.js',
	// String: Absolute path to the resource (optionally including query string)

	loaders: [
    {
      loader: path.resolve(__dirname, './test-loader'),
      options: {
        test: 'hello world'
      }
    }
  ],
  context: { minimize: true },
	// Additional loader context which is used as base context

	readResource: fs.readFile.bind(fs)
}, (err, result) => {
  console.log(result)
})

\

实现我们的loader

\

const loaderUtils = require('loader-utils')
const validate = require('schema-utils')
const parser = require('@babel/parser')
const { transform } = require('@babel/core')
const generate = require('@babel/generator').default

const ast = (code) => {
  return parser.parse(code, {
    sourceType: 'module'
  })
}

const generator = (code, callback) => {
  transform(code, {
    presets: [
      [
        '@babel/preset-env', {
          targets: {
            node: 'current',
            browsers: 'last 2 version'
          },
          corejs: 3,
          useBuiltIns: 'usage'
        }
      ]
    ]
  }, (err, result) => {
    callback(err, result)
  })
}

module.exports = function testLoader (source) {
  // loader异步执行,需要这个
  const callback = this.async()
  // 获取loader的参数
  const options = loaderUtils.getOptions(this)
  // console.log(this)
  // 如果有需要参数校验
  // const schema = {
  //   type: 'object',
  //   "properties": {
  //     "test": {
  //       "description": "This is description of option.",
  //       "type": "string"
  //     }
  //   },
  // }
  // validate(schema, options, {
  //   name: 'test-loader',
  //   baseDataPath: 'options',
  // })
  // 把源码传进@babel/generator 生成转换后的源码
  generator(source, (err, result) => {
    if (err) throw err
    return callback(null, result.code)
  })
}

\

plugin

\

plugin API

\

webpack的plugin是个类,必须提供一个apply方法,apply会接受一个compiler对象,这个是webpack编译时生成的。plugin会参与到webpack的各种生命周期,可以通过compiler.hooks进行监听,在该周期内执行需要处理的内容。

\

// 该插件实现了webpack, build结束的时候会输出console.log
class MyPlugin {
    constructor (options) {
        this.options = options
    }
    
    apply (compiler) {
        compiler.hook.done.tap('MyPlugin', () => {
            console.log('编译完成')
        })
    }
}

// webpack.config.js
const path = require('path')
const MyPlugin = require('./plugin/MyPlugin')

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    },
    plugins: [
        new MyPlugin()
    ]
}

\

以上就是loader和plugin的基本写法,通过以上例子,你已经学会了如何自己实现一个webpack的loader和plugin,现在你可以自己动手写了。