webpack-loader是怎样炼成的

目录

啰嗦两句

学习这件事从学习动机上来看,能够分红两种状况:主动学习和被动学习。主动学习就是,某天你浏览网页的时候,看到一个酷到没朋友的效果,赶忙打开开发者工具,看看用了什么 css 属性,用了什么库或者框架实现的,这是主动学习。css

还有一种是被动学习。就拿我来讲,以前用 mpvue 写小程序的时候,页面的 json 配置都是写在 main.js 里面的,loader 会从 main.js 解析出对应的代码块,而后为我生成对应的配置文件。可是前两天,当我又初始化一个新项目的时候(使用的是 mpvue-loader1.1.4),这个好用的特性竟然消失了,我须要在目录下本身手动建一个 json 文件写页面配置。vue

人有这么一种本性,从很差的体验切换到好的体验很快,可是再切回去就很难受😢。因此,这回只有硬着头皮写个 webpack loader 来回归原来的体验了。实现的功能很简单,就是从新实现 mpvue 原有的功能,从 js 文件中解析出配置项的内容,并生成一个json文件到对应的文件夹中。
webpack

loader 是干什么的

无图言卵,先上个图:
1539876855690.jpges6

把 webpack 想像成一个工厂,loader 就是一个个身怀绝技的流水线工人,有的会处理 svg,有的会压缩 css 或者图片,有的会处理 less,有的会将 es6 转换为 es5。他们在 webpack 的调度下 (确切的说是 loader-runer),层次分明的完成本身工做后,把本身处理的结果交给下一个工人,直到最后由 webpack 将他们的劳动成果生成 dist 目录下的文件。web

因此一个 loader 用一个函数来表示,应该是这样的:正则表达式

module.exports = function(content, map, meta) {
  return content;
};

上面咱们就定义了一个什么都不干还拿工资的 loader,它就是拿到内容后原样交给下一个 loader 同窗。可是,它如今其实还只是一个函数 -- 由于它尚未混入 webpack loader 内部啊,如今咱们帮他打入 webpack 的 loader 内部:json

// webpack.config.js

...
module: {
  rules: [
    {
      test: /\.js$/,
      include: [resolve('src'), resolve('test')],
      use: [
        {
          loader: path.resolve('path/to/my-loader.js'), // 本 loader😊
          options: {a: 1}
        },
        ... 其余 loader
      ]
    }
  ]
}

做为一个什么都不作的 loader,它在 rules 下面使用 /\.js$/ 这个正则表达式,告 (hu) 诉 (you) webpack 它能够处理 js 文件, 还经过 includes 字段,说明了它的业务范围只负责 src 和 test 目录下的 js 文件。小程序

如今回到上面的图,大部分 loader 仍是实实在在办事的。有的能够处理文本文件,如 css 预处理,进去的是 less 语法的文件,出来的是 css 语法的文件;有的能够处理二进制文件,好比将较小的图片变成 base64 字符串。还有的 loader 买一送二,好比 mpvue-loader, 输入的是 vue 文件,可是会输出 wxss,wxml,js 三个文件。可是,这些工做仅靠 loader 本身是办不到的,它须要和 webpack 沟通。也就是说,干活是须要工具的,这个工具就是 loader 的上下文 (context)。
api

loader 的工具箱 --context

根据 官方文档 的解释,loader context 表示在 loader 内使用 this 能够访问的一些方法或属性。仍是在上面的那个啥都不干 loader 上说明:框架

const path = require('path')
module.exports = function(content){
  console.log('resource', this.resource) // 文件路径带 query
  console.log('query', this.query)// 对应配置中的 options {a: 1}
  console.log('resourcePath', this.resourcePath)// 文件路径
  this.emitFile('main.json', JSON.stringify({hello: 'world'}))// dist目录下生成一个 json 文件
  this.emitWarning('这个 loader 啥都不干')// 会触发一个警告⚠️
  // this.emitError('这个 loader 啥都不干')// 会致使本次编译过程失败
  return content
}

正如上面的例子那样,有了上下文提供的工具包,loader 就能够干更多的事情而不仅是对 content 进行处理,好比:

  • 经过 this.emitError 向 webpack 抛出一个错误,中断本次编译
  • 经过 this.emitFile 生成一个新的文件,emitFile接受的第一个参数是相对于dist目录的文件路径
  • 经过 this.resource 得到资源路径

此外还有不少工具,你们能够看文档了解。

loader 实战

把这些了解清楚以后,咱们就能够实现以前想要的功能了:从全部文件名为 main.js 的文件中提取 export default 的内容,并在同级目录下生成一个 json 文件,以下所示:

// 生成前 src 目录

page
|-main.js

// 生成后 dist 目录
page
|-main.js
|-main.json

所有代码以下,解释见注释:

module.exports = function(source){

  if(/main\.js$/.test(this.resource)){// 只处理 main.js 文件
    let jsonPath = this.resource.replace(/.+src\//, '') +'on'// 生成 json 文件相对于 dist 目录的路径
    let re = /export\s+?default\s*(\{[\s\S]+\})/m; // 解析出文件中的 export default 代码块
    if (re.test(source)) {
      let config = eval('a=' + re.exec(source)[1]); // 将配置转成对象
      console.log(config)
      this.emitFile(jsonPath, JSON.stringify(config)); // 写到文件中
    }
  }
  return source;
}

这就是本文的所有内容了,今天参加了同窗的婚礼,见了几个老同窗,包括还在船厂🚢 打拼的同窗。吃饭的时候聊着你们的现状,真的很开心,没有想象中的局促和无话可说。转眼毕业都三年了,时间过的真 tm 快。(完)

相关文章
相关标签/搜索