编写loader和plugin

原文地址

编写loader和plugin

github

一、loader

1.loader 介绍

loader 是什么

loader 其实是一个函数,对匹配到的内容进行转换,将转换后的结果返回。

loader 作用

webpackloader 就像是一位翻译官。webpack 只认识 JavaScript 这们语言,对于其他的资源通过 loader 后可以转化做预处理

  • loader 的执行是有顺序的,支持链式的调用。loader的执行顺序是从下到上,从右到左。比如处理样式类的文件,use:['style-loader', 'css-loader']css-loader处理后的文件返回给 style-loader
  • 一个 Loader 的职责是单一的,只需要完成一种转换。
  • Webpack 会默认缓存所有 Loader 的处理结果,对没有修改的 loader 不会重新加载,关闭webpack 的默认缓存结果需要添加this.cacheable(false);

常见的loader

  • 样式类的 loader:css-loader, style-loader, less-loader, postcss-loader(添加-webkit)
  • 文件类的 loader:url-loader, file-loader, raw-loader等。
  • 编译类的 loader:babel-loader, ts-loader
  • 校验测试类 loader:eslint-loader, jslint-loader

4. loader 的三种使用方式

    1. webpack.config.js 中配置
module.exports = {
    module:{
        rules:[{
                test:/\.css$/,
                use:['css-loader'],
                // use:{loader:'css-loader',options:{}}
            }
        ]
    }
}
    1. 通过命令行的参数方式
webpack --module-bind 'css=css-loader'
    1. 通过内联使用
import txt from 'css-loader!./file.css';

2. 编写一个 loader

思路:前面我们说过 1.loader 是一个函数;2.对匹配到的内容进行转换;3.再将转换内容返回,按照这个思路我们可以编写一个最简单的loader

// 在 ./loader/replaceLoader.js 创建一个替换字符串的 loader
module.exports = function(source) {
    return source.replace('a', 'b')
}

// 在webpack.config.js 使用 自己写的loader
module.exports = {
    module:{
        rules:[{
            test:"/\.js$/",
            use:[{
                    loader: path.resolve(__dirname, './loader/replaceLoader.js')
                    options:{
                        name: '林一一'   
                    }
                }
            ]
        }]
    }
}

// 或者使用 replaceLoader
module.exports={
    resolveLoader:['node_modules', './loader']
    module:{
        rules:[{
            test:"/\.js$/",
            use:['resolveLoader']
        }]
    }
}

上面就是一个最简单的编写 loader 的案例

  • loader 还可以接收 options 传入的参数,详情查看 loader API,也可以使用官方提供的 loader-util 接收参数
const loaderUtil = require('loader-utils')
module.exports = function(source) {
    console.log(this.query.name) // 林一一
    const options = loaderUtil.getOptions(this)
    return source.replace('a', 'b')
}
  • 异步:loader 是一个函数自然有同步和异步的区分。使用异步的loader需要添加 this.async() 申明异步操作
const loaderUtils = require('loader-utils')
module.exports = function(source) {
    const options = loaderUtils.getOptions(this)
    const callback = this.async()
    setTimeout(()=>{
        console.log(options.name)
        let res = source.replace('a', options.name)
        callback(null, res, sourceMaps, ast)
    }, 4000)
}

上面的代码会在4秒后打包成功,如果没有 this.async() 异步操作就会失败,callback() 回调函数将结果放回。

  • 默认情况下 webpack 给 loader 传递的字符串编码是 utf-8,如果需要处理二进制的文件需要添加exports.raw = true
  • 上面提到过 webpack 会默认将loader的加载结果缓存如果需要关闭webpack的缓存结果需要添加this.cacheable(false);
  • Npm link 专门用于开发和调试本地 Npm 模块,在没有发布到 npm 上面也可以在调式本地的loader。具体需要在package.json 中配置 本地loader,在根目录下执行npm link loader-name 就可以在node_modules中使用本地的loader了。同时也可以采用上面的resolveLoader 实现导入 loader 的方式

总结编写 loader 的思路

  1. loader 是一个导出函数,有返回值,可以借助第三方模块和Node api 实现。
  2. loader 可以使用 loader-utils 接收到options 中传递过来的参数
  3. loader 的异步编写需要显示的申明 const callback = this.async() 表明异步。
  4. loader 如果需要处理二进制文件也需要声明exports.raw = true
  5. loader 的允许结果会被webpack缓存,如果需要关闭webpack的缓存结果需要声明this.cacheable(false)
  6. 编写后的本地loader 可以借助 Npm linkresolveLoader 导入。

二、webpack 的构建流程

再讲 plugins 之前需要先清楚 webpack 的构建流程是怎样的

  1. 初始化参数。从配置文件和 shell 语句中合并的参数
  2. 开始编译。将上一步得到的参数初始化成 complier对象,加载所有的导入插件,执行对象的 run 方法开始执行编译;
  3. 确定入口。从配置的 entry 入口找出所有的入口文件。
  4. 编译模块。根据入口文件的依赖,调用所有配置的loader进行转换。
  5. 完成模块编译并输出。根据入口文件之间的依赖关系,形成一个个代码块 chunk
  6. 输出完成。将形成的代码块 chunk 输出到文件系统。

上面初始化形成的 complier对象 会被注入到插件的 apply(complier)内。complier对象对象包含了 Webpack 环境所有的的配置信息比如 options, loaders, plugins等等属性,可以简单的认为complier是 webpack 的实例,通过compler.plugin()可以监听到 webpack 广播出来的事件。

三、plugin

1 plugin 介绍

plugin 是什么

plugin 是一个插件,这个插件也就是一个类,基于事件流框架 Tapable 实现。在 webpack 的构建流程中在初始化参数后,就会加载所有的 plugin 插件,创建插件的实例。

plugin 作用

plugin 通过钩子可以涉及到 webpack 的整一个事件流程。也就是说 plugin 可以通过监听这些生命周期的钩子在合适的时机使用 webpack 提供的API 做一些事情。

常见的 plugin

  • html-webpack-plugin 会在打包后自动生成一个 html 文件,并且会将打包后的 js 文件引入到html 文件内。
  • optimize-css-assets-webpack-plugin 对CSS 代码进行压缩。
  • mini-css-extract-plugin。将写入 style 标签内的 css 抽离成一个 用 link 导入 生成的 CSS 文件
  • webpack-parallel-uglify-plugin。开启多进程执行代码压缩,提高打包的速度。
  • clean-webpack-plugin。每次打包前都将旧生成的文件删除。
  • serviceworker-webpack-plugin。为网页应用增加离线缓存功能。

plugin 的使用方式

plugins中使用

const ServiceworkerWebpackPlugin = require('serviceworker-webpack-plugin')
module.exports = {
    plugins:[
        new ServiceworkerWebpackPlugin(),
    ]
}

2 编写一个 plugin

思路:plugins 是一个类,webpack 为 plugin 提供了很多内置的 api,需要在原型上定义 apply(compliers) 函数。同时指定要挂载的 webpack 钩子。

class MyPlugin {
    constructor(params){
        console.log(params)
    }
    // webpack 初始化参数后会调用这个引用函数,闯入初始化的 complier对象。
    apply(complier){
         // 绑定钩子事件
        // complier.hooks.emit.tapAsync()
        compiler.plugin('emit', compilation => {
            console.log('MyPlugin')
        ))
    }
}
module.export = MyPlugin

compilation对象 包含当前的模块资源、编译生成资源、和能监听变化的文件。每一个文件发生变化后,都会生成一个 compilation 对象,通过 compilation 也能读取到 compiler 对象。
调式plugin 可以使用 node 的调式工具在 package.json 中添加 "debug":"node --inspect --inspect brk node_modules/webpack/bin/webpack.js"

总结编写 plugin 的思路

  1. 编写一个 class 类 。
  2. 在类中定义一个 apply 方法。
  3. 在应用方法apply()中指定挂载的 webpack 事件钩子complier.hooks.
  4. 处理 webpack 内部实例的特定数据。
  5. 功能完成后调用 webpack 提供的回调。

四、面试题

1. loader 和 plugin 的区别

  1. loader 是一个函数,用来匹配处理某一个特定的模块,将接收到的内容进行转换后返回。在webpack 中操作文件,充当文件转换器的角色。在 modules.rules 中配置。
  2. plugin 是一个插件,不直接操作文件,基于事件流框架 Tapable 实现,plugin 通过钩子可以涉及到 webpack 的整一个事件流程。也就是说 plugin 可以通过监听这些生命周期的钩子在合适的时机使用webpack 提供的API 做一些事情。在plugins中配置插件

2. loader 的编写思路

参上

3. plugin 的编写思路

参上

4. complier 和 compilation 区别

  1. complier 对象暴露了 webpack 整一个生命周期相关的钩子,是 webpack 初始化的参数的产物,包含options, entry, plugins等属性可以简单的理解为webpack的一个实例。
  2. compilation 对象是 complier 的实例,是每一次 webpack 构建过程中的生命周期对象。每一个文件发生变化后都能生成一个complition对象。
    总结:两个对象都有自己的生命周期钩子,compilation 对象 负责的是粒度更小的生命周期钩子。compiler对象是webpack整一个整个生命周期钩子的对象。

参考

webpack之loader和plugin简介

webpack 构建流程

webpack loader和plugin编写

深入Webpack-编写Loader

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容