前言

已经发表过一篇性能优化的文章《前端性能优化指南》,笔者总结了一些在我的项目开发过程中应用过的性能优化教训。说句真话,性能优化可能在面试过程中会有用,理论在我的项目开发过程中可能没几个同学会留神这些性能优化的细节。

若常常关注性能优化的话题,可能会发现无论怎样对代码做最好的优化也不迭对一张图片做一次压缩好。所以压缩图片成了性能优化里最常见的操作,不论是手动压缩图片还是主动压缩图片,在我的项目开发过程中必须得有。

主动压缩图片通常在webpack构建我的项目时接入一些第三方Loader&Plugin来解决。关上Github,搜素webpack image等关键字,Star最多还是image-webpack-loaderimagemin-webpack-plugin这两个Loader&Plugin。很多同学可能都会抉择它们,方便快捷,简略易用,无脑接入。

可是,这两个Loader&Plugin存在一些特地问题,它们都是基于imagemin开发的。imagemin的某些依赖托管在国外服务器,在npm i xxx装置它们时会默认走GitHub Releases的托管地址,若不是标准上网,你们是不可能装置得上的,即便标准上网也不肯定装置得上。所以笔者又刨根到底发表了一篇对于NPM镜像解决的文章《聊聊NPM镜像那些险象环生的坑》,专门解决这些因为网络环境而导致装置失败的问题。除了这个装置问题,imagemin还存在另一个大问题,就是压缩质感损失得比较严重,图片体积越大越显著,压缩进去的图片总有几张是失真的,而且总体压缩率不是很高。这样在交付我的项目时有可能被仔细的QA小姐姐抓个正着,怎么和设计图比照起来不清晰啊!

工具

图片压缩工具

此时可能有些同学已转战到手动压缩图片了。比拟好用的图片压缩工具无非就是以下几个,若有更好用的工具麻烦在评论里补充喔!同时笔者也整顿出它们的区别,供各位同学参考。

工具开源免费API收费体验
QuickPicture✖️✔️✖️可压缩类型较多,压缩质感较好,有体积限度,有数量限度
ShrinkMe✖️✖️✖️可压缩类型较多,压缩质感个别,无数量限度,有体积限度
Squoosh✔️✖️✔️可压缩类型较少,压缩质感个别,无数量限度,有体积限度
TinyJpg✖️✔️✔️可压缩类型较少,压缩质感很好,有数量限度,有体积限度
TinyPng✖️✔️✔️可压缩类型较少,压缩质感很好,有数量限度,有体积限度
Zhitu✖️✖️✖️可压缩类型个别,压缩质感个别,有数量限度,有体积限度

从上述表格比照可看出,收费体验都会存在体积限度,这个可了解,即便免费也一样,毕竟每个人都上传单张10多M的图片,哪个服务器能受得了。再来就是数量限度,一次只能上传20张,如同有个法则,压缩质感好就限度数量,否则就不限度数量,当然免费后就没有限度了。再来就是可压缩类型,图片类型个别是jpgpnggifsvgwebpgif压缩后个别都会失真,svg通常用在矢量图标上很少用在场景图片上,webp因为兼容性问题很少被应用,故能压缩jpgpng就足够了。当然压缩质感是最优思考,综上所述,大部分同学都会抉择TinyJpgTinyPng,其实它俩就是兄弟,出自同一厂商。

在笔者公众号的微信探讨群里发动了一个简略的投票,最终还是TinyJpgTinyPng胜出。

TinyJpg/TinyPng存在问题
  • 上传下载全靠手动
  • 只能压缩jpgpng
  • 每次只能压缩20
  • 每张体积最大不能超过5M
  • 可视化解决信息不是特地齐全
TinyJpg/TinyPng压缩原理

TinyJpg/TinyPng应用智能有损压缩技术将图片体积升高,选择性地缩小图片中类似色彩,只需很少字节就能保留数据。对视觉影响简直不可见,然而在文件体积上就有很大的差异。而应用到智能有损压缩技术被称为量化

TinyJpg/TinyPng在压缩png文件时成果更显著。扫描图片中类似色彩并将其合并,通过缩小色彩数量将24位png文件转换成体积更小的8位png文件,抛弃所有不必要的元数据。

大部分png文件都有50%~70%的压缩率,即便视力再好也很难辨别进去。应用优化过的图片可缩小带宽流量和加载工夫,整个网站应用到的图片经TinyJpg/TinyPng压缩一遍,其功效是再多的代码优化也无奈追赶得上的。

TinyJpg/TinyPng开发API

查阅相干材料,发现TinyJpg/TinyPng临时还未开源其压缩算法,不过提供了适宜开发者应用的API。有趣味的同学可到其开发API文档瞧瞧。

Node方面,TinyJpg/TinyPng官网提供了tinify作为压缩图片的外围JS库,应用很简略,看文档吧。可是换成开发API还是逃不过免费,你是想包月呢还是收费呢,想收费的话就持续往下看,土豪随便!

实现

笔者也是常常应用TinyJpg/TinyPng的程序猿,免费,那是不可能的????。寻找突破口,解决问题,是作为一位程序猿最根本的素养。咱们需明确什么问题,需解决什么问题

剖析

从上述得悉,只需对TinyJpg/TinyPng原有性能革新成以下性能。

  • 上传下载全自动
  • 可压缩jpgpng
  • 没有数量限度
  • 存在体积限度,最大体积不能超过5M
  • 压缩胜利与否输入详细信息
主动解决

对于前端开发者来说,这种无脑的上传下载操作必须得自动化,省事省心省力。然而这个操作得联合webpack来解决,到底是开发成Loader还是Plugin,前面再剖析。不过仔细的同学看题目就晓得用什么形式解决了。

压缩类型

gif压缩后个别都会失真,svg通常用在矢量图标上很少用在场景图片上,webp因为兼容性问题很少被应用,故能压缩jpgpng就足够了。在过滤图片时,应用path模块判断文件类型是否为jpgpng,是则持续解决,否则不解决。

数量限度

数量限度当然是不能存在的,万一我的项目里超过20张图片,那不是得分批处理,这个不能有。对于这种无需登录状态就能解决一些用户文件的网站,通常都会通过IP来限度用户的操作次数。有些同学可能会说,刷新页面不就行了吗,每次压缩20张图片,再刷新再压缩,万一有500张图片呢,你就刷新25次吗,这样很好玩是吧!

因为大多数Web架构很少会将应用服务器间接对外提供服务,个别都会设置一层Nginx作为代理和负载平衡,有的甚至可能有多层代理。鉴于大多数Web架构都是应用Nginx作为反向代理,用户申请不是间接申请应用服务器的,而是通过Nginx设置的对立接入层将用户申请转发到服务器的,所以可通过设置HTTP申请头字段X-Forwarded-For来伪造IP。

X-Forwarded-For指用来辨认通过代理负载平衡的形式连贯到Web服务器的客户端最原始的IP地址的HTTP申请头字段。当然,这个IP也不是变化无穷的,每次申请都需随机更换IP,骗过应用服务器。若应用服务器减少了伪造IP辨认,那可能就无奈持续应用随机IP了。

体积限度

体积限度这个能了解,也没必要搞一张那么大的图片,多节约带宽流量和加载工夫啊。在上传图片时,应用fs模块判断文件体积是否超过5M,是则不上传,否则持续上传。当然,交给TinyJpg/TinyPng接口判断也行。

输入信息

压缩胜利与否得让他人晓得,输入原始大小、压缩大小、压缩率和谬误提醒等,让他人分明这些解决信息。

编码

通过上述抽丝剥茧的剖析,那么就开始着手编码了。

随机生成HTTP申请头

既然可通过X-Forwarded-For来伪造IP,那么得有一个随机生成HTTP申请头字段的函数,每次申请接口时都随机生成相干的申请头字段。关上tinyjpg.com或tinypng.com上传一张图片,通过Chrome DevTools剖析Network发现其申请接口是web/shrink。另外每次申请也不要集中在繁多的hostname上,随机派发到tinyjpg.comtinypng.com上会更好。通过封装RandomHeader函数随机生成申请头信息,后续应用https模块RandomHeader()生成的配置作为入参进行申请。

trample是笔者开发的一个Web/Node通用函数工具库,蕴含惯例的工具函数,助你少写更多通用代码。详情请查看文档,顺便给一个Star以作激励。

const { RandomNum } = require("trample/node");const TINYIMG_URL = [    "tinyjpg.com",    "tinypng.com"];function RandomHeader() {    const ip = new Array(4).fill(0).map(() => parseInt(Math.random() * 255)).join(".");    const index = RandomNum(0, 1);    return {        headers: {            "Cache-Control": "no-cache",            "Content-Type": "application/x-www-form-urlencoded",            "Postman-Token": Date.now(),            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",            "X-Forwarded-For": ip        },        hostname: TINYIMG_URL[index],        method: "POST",        path: "/web/shrink",        rejectUnauthorized: false    };}
上传图片与下载图片

应用Promise封装上传图片下载图片的函数,不便后续应用Async/Await同步化异步代码。以下函数的具体断点调试就不说了,有趣味的同学自行调试函数的入参和出参哈!

const Https = require("https");const Url = require("url");function UploadImg(file) {    const opts = RandomHeader();    return new Promise((resolve, reject) => {        const req = Https.request(opts, res => res.on("data", data => {            const obj = JSON.parse(data.toString());            obj.error ? reject(obj.message) : resolve(obj);        }));        req.write(file, "binary");        req.on("error", e => reject(e));        req.end();    });}function DownloadImg(url) {    const opts = new Url.URL(url);    return new Promise((resolve, reject) => {        const req = Https.request(opts, res => {            let file = "";            res.setEncoding("binary");            res.on("data", chunk => file += chunk);            res.on("end", () => resolve(file));        });        req.on("error", e => reject(e));        req.end();    });}
压缩图片

通过上传图片函数获取压缩后的图片信息,再根据图片信息通过下载图片函数生成本地文件。

const Fs = require("fs");const Path = require("path");const Chalk = require("chalk");const Figures = require("figures");const { ByteSize, RoundNum } = require("trample/node");async function CompressImg(path) {    try {        const file = Fs.readFileSync(path, "binary");        const obj = await UploadImg(file);        const data = await DownloadImg(obj.output.url);        const oldSize = Chalk.redBright(ByteSize(obj.input.size));        const newSize = Chalk.greenBright(ByteSize(obj.output.size));        const ratio = Chalk.blueBright(RoundNum(1 - obj.output.ratio, 2, true));        const dpath = Path.join("img", Path.basename(path));        const msg = `${Figures.tick} Compressed [${Chalk.yellowBright(path)}] completed: Old Size ${oldSize}, New Size ${newSize}, Optimization Ratio ${ratio}`;        Fs.writeFileSync(dpath, data, "binary");        return Promise.resolve(msg);    } catch (err) {        const msg = `${Figures.cross} Compressed [${Chalk.yellowBright(path)}] failed: ${Chalk.redBright(err)}`;        return Promise.resolve(msg);    }}
压缩指标图片

实现上述步骤对应的函数后,就能自在压缩图片了,以下应用一张图片作为演示。

const Ora = require("ora");(async() => {    const spinner = Ora("Image is compressing......").start();    const res = await CompressImg("src/pig.png");    spinner.stop();    console.log(res);})();

你看,压缩完后笨猪都变帅猪了,能电眼的猪都是好猪。源码请查看compress-img。

若压缩指定文件夹里符合条件的所有图片,可通过fs模块获取图片并应用map()将单个图片门路映射为CompressImg(path),再通过Promise.all()操作即可。在这里就不贴代码了,当作思考题,自行实现。

将上述压缩图片的性能封装成Loader还是Plugin呢?接下来会一步一步剖析。

Loader&Plugin

webpack是一个前端资源打包工具,它依据模块依赖关系进行动态剖析,而后将这些模块依照指定规定生成对应的动态资源。

网上一大堆webpack教程,笔者就不再花大篇幅啰嗦了,置信各位同学都是一位规范的Webpack配置工程师。以下简略回顾一次webpack的组成、构建机制和构建流程,置信也能从这些知识点中定位出LoaderPluginWebpack构建流程中是处于一个什么样的角色位置。

本文所说的webpack都是基于webpack v4
组成
  • Entry:入口
  • Output:输入
  • Loader:转换器
  • Plugin:扩展器
  • Mode:模式
  • Module:模块
  • Target:指标
构建机制
  • 通过Babel转换代码并生成单个文件依赖
  • 从入口文件开始递归剖析并生成依赖图谱
  • 将各个援用模块打包成一个立刻执行函数
  • 将最终bundle文件写入bundle.js
构建流程
  • 初始

    • 初始参数:合并命令行和配置文件的参数
  • 编译

    • 执行编译:根据参数初始Compiler对象,加载所有Plugin,执行run()
    • 确定入口:根据配置文件找出所有入口文件
    • 编译模块:根据入口文件找出所有依赖模块关系,调用所有Loader进行转换
    • 生成图谱:失去每个模块转换后的内容及其之间的依赖关系
  • 输入

    • 输入资源:根据依赖关系将模块组装成块再组装成包(module → chunk → bundle)
    • 生成文件:根据配置文件将确认输入的内容写入文件系统
Loader

Loader用于转换模块源码,笔者将其翻译为转换器Loader可将所有类型文件转换为webpack可能解决的无效模块,而后利用webpack的打包能力对它们进行二次解决。

Loader具备以下特点:

  • 繁多职责准则(只实现一种转换)
  • 转换接管内容
  • 返回转换后果
  • 反对链式调用

Loader将所有类型文件转换为应用程序的依赖图谱可间接援用的模块,所以Loader可用于编译一些文件,例如pug → htmlsass → cssless → csses5 → es6ts → js等。

解决一个文件可应用多个LoaderLoader的执行程序和配置程序是相同的,即开端Loader最先执行,结尾Loader最初执行。最先执行的Loader接管源文件内容作为参数,其它Loader接管前一个执行的Loader的返回值作为参数,最初执行的Loader会返回该文件的转换后果。一句话概括:富土康流水线厂工

Loader开发思路总结如下:

  • 通过module.exports导出一个函数
  • 函数第一默认参数为source(源文件内容)
  • 在函数体中解决资源(可引入第三方模块扩大性能)
  • 通过return返回最终转换后果(字符串模式)
编写Loader时要遵循繁多职责准则,每个Loader只做一种转换工作
Plugin

Plugin用于扩大执行范畴更广的工作,笔者将其翻译为扩展器Plugin的范畴很广,在Webpack构建流程里从开始到完结都能找到机会作为插入点,只有你想不到没有你做不到。所以笔者认为Plugin的性能比Loader更加弱小。

Plugin具备以下特点:

  • 监听webpack运行生命周期中播送的事件
  • 在适合机会通过webpack提供的API扭转输入后果
  • webpack的Tapable事件流机制保障Plugin的有序性

webpack运行生命周期中会播送出许多事件,Plugin可监听这些事件并在适合机会通过webpack提供的API扭转输入后果。在webpack启动后,在读取配置过程中执行new MyPlugin(opts)初始化自定义Plugin获取其实例,在初始化Compiler对象后,通过compiler.hooks.event.tap(PLUGIN_NAME, callback)监听webpack播送事件,当捕抓到指定事件后,会通过Compilation对象操作相干业务逻辑。一句话概括:本人看着办

Plugin开发思路总结如下:

  • 通过module.exports导出一个函数或类
  • 函数原型或类上绑定apply()拜访Compiler对象
  • apply()中指定一个绑定到webpack本身的事件钩子
  • 在事件钩子中通过webpack提供的API解决资源(可引入第三方模块扩大性能)
  • 通过webpack提供的办法返回该资源
传给每个Plugin的Compiler和Compilation都是同一个援用,若批改它们身上的属性会影响前面的Plugin,所以需谨慎操作
Loader/Plugin区别
  • 实质

    • Loader实质是一个函数,转换接管内容,返回转换后果
    • Plugin实质是一个类,监听webpack运行生命周期中播送的事件,在适合机会通过webpack提供的API扭转输入后果
  • 配置

    • Loadermodule.rule中配置,类型是数组,每一项对应一个模块解析规定
    • Pluginplugin中配置,类型是数组,每一项对应一个扩展器实例,参数通过构造函数传入

封装

剖析

从上述可知LoaderPlugin在角色定位和执行机制上有很多不一样,到底如何抉择呢?各有各好,当然还是需剖析后进行抉择。

Loaderwebpack中扮演着转换器的角色,用于转换模块源码,简略了解就是将文件转换成另外模式的文件,而本文主题是压缩图片jpg压缩后还是jpgpng压缩后还是png,在文件类型上来说还是没有变动。Loader的转换过程是从属在整个Webpack构建流程中的,意味着打包工夫蕴含了压缩图片的工夫老本,对于谋求webpack性能优化来说实属有点违反准则。而Plugin恰好是监听webpack运行生命周期中播送的事件,在适合机会通过webpack提供的API扭转输入后果,所以可在整个Webpack构建流程实现后(全副打包文件输入实现后)插入压缩图片的操作。换句话说,打包工夫不再蕴含压缩图片的工夫老本,打包实现后该干嘛就干嘛,还能干嘛,压缩图片啊。

所以根据需要状况,Plugin作为首选。

编码

根据上述Plugin开发思路,那么就开始着手编码了。

笔者把这个压缩图片的Plugin命名为tinyimg-webpack-plugintinyimg意味着TinyJpgTinyPng合体。

新建我的项目,目录构造如下。

tinyimg-webpack-plugin├─ src│  ├─ index.js│  ├─ schema.json├─ util│  ├─ getting.js│  ├─ setting.js├─ .gitignore├─ .npmignore├─ license├─ package.json├─ readme.md

次要文件如下。

  • src

    • index.js:入口函数
    • schema.json:参数校验
  • util

    • getting.js:常量汇合
    • setting.js:函数汇合

装置我的项目所需模块,和上述compress-img的依赖统一,额定装置schema-utils用于校验Plugin参数是否符合规定。

npm i chalk figures ora schema-utils trample
封装常量汇合和函数汇合

将上述compress-imgTINYIMG_URLRandomHeader()封装到工具汇合中,其中常量汇合减少IMG_REGEXPPLUGIN_NAME两个常量。

// getting.jsconst IMG_REGEXP = /\.(jpe?g|png)$/;const PLUGIN_NAME = "tinyimg-webpack-plugin";const TINYIMG_URL = [    "tinyjpg.com",    "tinypng.com"];module.exports = {    IMG_REGEXP,    PLUGIN_NAME,    TINYIMG_URL};
// setting.jsconst { RandomNum } = require("trample/node");const { TINYIMG_URL } = require("./getting");function RandomHeader() {    const ip = new Array(4).fill(0).map(() => parseInt(Math.random() * 255)).join(".");    const index = RandomNum(0, 1);    return {        headers: {            "Cache-Control": "no-cache",            "Content-Type": "application/x-www-form-urlencoded",            "Postman-Token": Date.now(),            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",            "X-Forwarded-For": ip        },        hostname: TINYIMG_URL[index],        method: "POST",        path: "/web/shrink",        rejectUnauthorized: false    };}module.exports = {    RandomHeader};
通过module.exports导出一个函数或类
// index.jsmodule.exports = class TinyimgWebpackPlugin {};
函数原型或类上绑定apply()拜访Compiler对象
// index.jsmodule.exports = class TinyimgWebpackPlugin {    apply(compiler) {        // Do Something    }};
apply()中指定一个绑定到webpack本身的事件钩子

从上述剖析中可知,在全副打包文件输入实现后插入压缩图片的操作,所以应该抉择该机会对应的事件钩子。从Webpack Compiler Hooks API文档中可发现,emit正是这个Plugin所需的事件钩子。emit生成资源到输入目录前执行,此刻可获取所有图片文件的数据和输入门路。

为了不便在特定条件下启用性能打印日志,所以设置相干配置。

  • enabled:是否启用性能
  • logged:是否打印日志

apply()中解决相干业务逻辑,可能应用到Plugin的入参,那么就得对参数进行校验。定义一个PluginSchema,通过schema-utils来校验Plugin的入参。

// schema.json{    "type": "object",    "properties": {        "enabled": {            "description": "start plugin",            "type": "boolean"        },        "logged": {            "description": "print log",            "type": "boolean"        }    },    "additionalProperties": false}
// index.jsconst SchemaUtils = require("schema-utils");const { PLUGIN_NAME } = require("../util/getting");const Schema = require("./schema");module.exports = class TinyimgWebpackPlugin {    constructor(opts) {        this.opts = opts;    }    apply(compiler) {        const { enabled } = this.opts;        SchemaUtils(Schema, this.opts, { name: PLUGIN_NAME });        enabled && compiler.hooks.emit.tap(PLUGIN_NAME, compilation => {            // Do Something        });    }};
整合compress-imgPlugin

在整合过程中会有一些小批改,各位同学可比照看看哪些细节产生了变动。

// index.jsconst Fs = require("fs");const Https = require("https");const Url = require("url");const Chalk = require("chalk");const Figures = require("figures");const { ByteSize, RoundNum } = require("trample/node");const { RandomHeader } = require("../util/setting");module.exports = class TinyimgWebpackPlugin {    constructor(opts) { ... }    apply(compiler) { ... }    async compressImg(assets, path) {        try {            const file = assets[path].source();            const obj = await this.uploadImg(file);            const data = await this.downloadImg(obj.output.url);            const oldSize = Chalk.redBright(ByteSize(obj.input.size));            const newSize = Chalk.greenBright(ByteSize(obj.output.size));            const ratio = Chalk.blueBright(RoundNum(1 - obj.output.ratio, 2, true));            const dpath = assets[path].existsAt;            const msg = `${Figures.tick} Compressed [${Chalk.yellowBright(path)}] completed: Old Size ${oldSize}, New Size ${newSize}, Optimization Ratio ${ratio}`;            Fs.writeFileSync(dpath, data, "binary");            return Promise.resolve(msg);        } catch (err) {            const msg = `${Figures.cross} Compressed [${Chalk.yellowBright(path)}] failed: ${Chalk.redBright(err)}`;            return Promise.resolve(msg);        }    }    downloadImg(url) {        const opts = new Url.URL(url);        return new Promise((resolve, reject) => {            const req = Https.request(opts, res => {                let file = "";                res.setEncoding("binary");                res.on("data", chunk => file += chunk);                res.on("end", () => resolve(file));            });            req.on("error", e => reject(e));            req.end();        });    }    uploadImg(file) {        const opts = RandomHeader();        return new Promise((resolve, reject) => {            const req = Https.request(opts, res => res.on("data", data => {                const obj = JSON.parse(data.toString());                obj.error ? reject(obj.message) : resolve(obj);            }));            req.write(file, "binary");            req.on("error", e => reject(e));            req.end();        });    }};
在事件钩子中通过webpack提供的API解决资源

通过compilation.assets获取全副打包文件的对象,筛选出jpgpng,应用map()将单个图片数据映射为this.compressImg(file),再通过Promise.all()操作即可。

整个业务逻辑联合了PromiseAsync/Await两个ES6罕用个性,它俩组合起来玩异步编程极其乏味,对于它俩更多细节可查看笔者这篇4000点赞量14万浏览量的文章《1.5万字概括ES6全副个性》。

// index.jsconst Ora = require("ora");const SchemaUtils = require("schema-utils");const { IMG_REGEXP, PLUGIN_NAME } = require("../util/getting");const Schema = require("./schema");module.exports = class TinyimgWebpackPlugin {    constructor(opts) { ... }    apply(compiler) {        const { enabled, logged } = this.opts;        SchemaUtils(Schema, this.opts, { name: PLUGIN_NAME });        enabled && compiler.hooks.emit.tap(PLUGIN_NAME, compilation => {            const imgs = Object.keys(compilation.assets).filter(v => IMG_REGEXP.test(v));            if (!imgs.length) return Promise.resolve();            const promises = imgs.map(v => this.compressImg(compilation.assets, v));            const spinner = Ora("Image is compressing......").start();            return Promise.all(promises).then(res => {                spinner.stop();                logged && res.forEach(v => console.log(v));            });        });    }    async compressImg(assets, path) { ... }    downloadImg(url) { ... }    uploadImg(file) { ... }};
通过webpack提供的办法返回该资源

因为压缩图片的操作是在整个Webpack构建流程实现后,所以没有什么可返回了,故不作解决。

管制webpack依赖版本

因为tinyimg-webpack-plugin基于webpack v4,所以需在package.json中增加peerDependencies,用来告知装置该Plugin的模块必须存在peerDependencies里的依赖。

{    "peerDependencies": {        "webpack": ">= 4.0.0",        "webpack-cli": ">= 3.0.0"    }}
总结

依照上述总结的开发思路一步一步来实现编码,其实是挺简略的。若需开发一些跟本人我的项目相干的Plugin,还是需多多相熟Webpack Compiler Hooks API文档,置信各位同学都能手戳一个完满的Plugin进去。

tinyimg-webpack-plugin源码请戳这里查看,Star一个如何,嘻嘻。

测试

整个Plugin开发实现,接下来需走一遍测试流程,看能不能把这个压缩图片的扩展器跑通。置信各位同学都是一位规范的Webpack配置工程师,可自行编写测试Demo验证你们的Plugin

在根目录下创立test文件夹,并依照以下目录构造退出文件。

tinyimg-webpack-plugin├─ test│  ├─ src│  │  ├─ img│  │  │  ├─ favicon.ico│  │  │  ├─ gz.jpg│  │  │  ├─ pig-1.jpg│  │  │  ├─ pig-2.jpg│  │  │  ├─ pig-3.jpg│  │  ├─ index.html│  │  ├─ index.js│  │  ├─ index.scss│  │  ├─ reset.css│  └─ webpack.config.js

装置测试Demo所需的webpack相干配置模块。

npm i -D @babel/core @babel/preset-env babel-loader clean-webpack-plugin css-loader file-loader html-webpack-plugin mini-css-extract-plugin node-sass sass sass-loader style-loader url-loader webpack webpack-cli webpackbar

装置实现后,着手欠缺webpack.config.js代码,代码量有点多,间接贴链接好了,请戳这里。

最初在package.json中的scripts插入以下npm scripts,而后执行npm run test调试测试Demo。

{    "scripts": {        "test": "webpack --config test/webpack.config.js"    }}

公布

公布到NPM仓库上非常简单,仅需几行命令。若还没注册,连忙去NPM上注册一个账号。若以后镜像为淘宝镜像,需执行npm config set registry https://registry.npmjs.org/切换回源镜像。

接下来一波操作就可实现公布了。

  • 进入目录:cd my-plugin
  • 登录账号:npm login
  • 校验状态:npm whoami
  • 公布模块:npm publish
  • 退出账号:npm logout

若不想牢记这么多命令,可用笔者开发的pkg-master一键公布,若存在某些谬误会立马中断公布并提醒错误信息,是一个十分好用的集成创立和公布的NPM模块管理工具。详情请查看文档,顺便给一个Star以作激励。

装置

npm i -g pkg-master

应用
命令缩写性能形容
pkg-master createpkg-master c创立模块生成模块的根底文件
pkg-master publishpkg-master p公布模块检测NPM的运行环境账号状态,通过则主动公布模块

接入

装置

npm i tinyimg-webpack-plugin

应用
配置性能格局形容
enabled是否启用性能true/false倡议只在生产环境下开启
logged是否打印日志true/false打印解决信息

webpack.config.jswebpack配置插入以下代码。

在CommonJS中应用
const TinyimgPlugin = require("tinyimg-webpack-plugin");module.exports = {    plugins: [        new TinyimgPlugin({            enabled: process.env.NODE_ENV === "production",            logged: true        })    ]};
在ESM中应用

必须在babel加持下的Node环境中应用

import TinyimgPlugin from "tinyimg-webpack-plugin";export default {    plugins: [        new TinyimgPlugin({            enabled: process.env.NODE_ENV === "production",            logged: true        })    ]};
举荐一个零配置开箱即用的React/Vue利用自动化构建脚手架

bruce-cli是一个React/Vue利用自动化构建脚手架,其零配置开箱即用的长处非常适合入门级、初中级、疾速开发我的项目的前端同学应用,还可通过创立brucerc.js文件来笼罩其默认配置,只需专一业务代码的编写无需关注构建代码的编写,让我的项目构造更简洁。应用时记得查看文档哟,喜爱的话给个Star。

当然,笔者已将tinyimg-webpack-plugin集成到bruce-cli中,零配置开箱即用走起。

  • Github地址:请戳这里
  • 官网文档:请戳这里
  • 掘金文档:请戳这里

总结

总体来说开发一个Webpack Plugin不难,只需好好剖析需要,理解webpack运行生命周期中播送的事件,编写自定义Plugin在适合机会通过webpack提供的API扭转输入后果。

若感觉tinyimg-webpack-plugin对你有帮忙,可在Issue上提出你的贵重倡议,笔者会认真浏览并整合你的倡议。喜爱tinyimg-webpack-plugin的请给一个Star,或Fork本我的项目到本人的Github上,依据本身需要定制性能。