写代码也能防沉迷?「反内卷 & 防沉迷插件 wlb-webpack-plugin 开发纪录」

846 阅读5分钟

/ 一、这个插件是干什么的? /

wlb-webpack-plugin 插件会在非工作日及下班时间自动将「反内卷和代码防沉迷逻辑」注入到 webpack 打包产物中,是追求 work-life-balance 的前端工程师们的最佳选择。(手动滑稽)

项目地址(欢迎各位 star):github.com/shadowings-…

防沉迷前:

防沉迷后:

使用 webpack 打包时会有如下提示:

/ 二、这个插件是怎么做的? /

要实现 wlb-webpack-plugin,我们首先得知道这个插件有什么功能,我在构思做这个插件的时候,列出了如下四个功能点:

1、它得是一个 webpack 插件 —— 它需要遵守 webpack 的插件规范。

2、它得能判断工作时间和非工作时间,并支持用户配置。

3、它得能在非工作时间,注入「特定逻辑」到打包产物里。

4、「特定逻辑」包含:如果是 node 端,直接打印出「防沉迷标语」;如果是 web 端,需要在页面上展示「防沉迷标语」。

那么针对上述四个功能点,我们就可以开始愉快的写代码了~

2-1、先写一个 webpack 插件吧

webpack 官网中的例子已经写得很明白了,可以直接看这个:www.webpackjs.com/contribute/…

简单概括一下,一个 webpack 插件由以下组成:

1、一个 JavaScript 命名函数。(在我们的插件中,我们使用了 class,而非 function,当然这两者实际上没差)

2、在插件函数的 prototype 上定义一个 apply 方法。

3、指定一个绑定到 webpack 自身的事件钩子。

4、处理 webpack 内部实例的特定数据。

5、功能完成后调用 webpack 提供的回调。

那我们的 wlb-webpack-plugin 的大体结构就是这个样子:

class WLBPlugin {

  constructor(options) {

    // 处理初始化参数的逻辑

  }

  apply(compiler) {

    // 处理判断是否为工作时间的逻辑

    compiler.hooks.emit.tap('WLBPlugin', (compilation) => {

      // 处理注入「反内卷 & 防沉迷代码」到打包产物中的逻辑

    })

  }

}



module.exports = WLBPlugin;

2-2、处理用户配置

在处理用户配置之前,我们需要定义一下用户可以传哪些配置,我在构思插件的时候,定义了如下配置项:

  • startWorkingTime 开始工作的时间

  • endWorkingTime 结束工作的时间

  • ignoreWeekend 是否忽略周末

  • warningMessage 非工作时间提示信息

  • replaceOriginBundle 是否替换原来生成的 bundle

这里特别提一下 replaceOriginBundle 这个配置项吧,这个配置项是用于决定「增量添加防沉迷代码」或「直接将 bundle 内容替换为防沉迷代码」用的。虽然大部分场景下简单粗暴的直接替换代码就够用了。但如果你用了一些自定义的脚手架,直接替换代码会导致整个项目都跑不起来,所以这种情况下增量添加才能达到效果。

定义好配置之后,我们需要把这些配置设定初始值,并将它们存到 WLBPlugin 类中,这里用 Object.assign 函数就非常合适了,注意看 constructor 中的逻辑:

const DEFAULT_WARNING_MESSAGE =

  '别卷了!现在不是工作时间!为了营造良好的工作环境,WLB插件已经将「反内卷 & 防沉迷逻辑」注入到打包产物中。';



const DEFAULT_OPTIONS = {

  startWorkingTime: 10,

  endWorkingTime: 20,

  ignoreWeekend: false,

  warningMessage: DEFAULT_WARNING_MESSAGE,

  replaceOriginBundle: true,

};



class WLBPlugin {

  constructor(options) {

    this.options = Object.assign(DEFAULT_OPTIONS, options || {});

  }

  apply(compiler) {

    // 处理判断是否为工作时间的逻辑

    compiler.hooks.emit.tap('WLBPlugin', (compilation) => {

      // 处理注入「反内卷 & 防沉迷代码」到打包产物中的逻辑

    })

  }

}

2-3、判断是否为工作时间

当我们能够使用 this.options 读取到配置项后,直接使用内置的 Date 对象来获取当前时间和星期,并与配置项比较即可,也就是如下的代码:

const date = new Date();

const day = date.getDay();

const hour = date.getHours();

const isWorkdays = day <= 4 || ignoreWeekend;

const isWorkOvertime =

  !isWorkdays || hour < startWorkingTime || hour >= endWorkingTime;



if (isWorkOvertime) {

  // 处理非工作时间的逻辑

}

2-4、生成并注入「反内卷 & 防沉迷代码」

最后一步也是最关键的一步 —— 生成并注入代码,这里先从「生成代码」开始。

2-4-1、生成代码

根据最开始的构思,生成的代码需要满足:

1、在 node 端,直接打印出「防沉迷标语」

2、在 web 端,需要在页面上展示「防沉迷标语」。

node 端比较简单,直接 console.log 就搞定了。但在 web 端,我们需要通过操作 DOM 来完成。

具体可以看下述代码,在 web 端会起一个定时器,每一秒钟把 标签中的内容替换为「防沉迷标语」,并通过判断 window.showWLBPluginInfo 来保证只会起一个定时器。

const htmlTemplate = (slogan) => {

  return `<div><h1>${slogan}</h1><a href=\"https://github.com/shadowings-zy/wlb-webpack-plugin\">由「wlb-webpack-plugin 反内卷 & 代码防沉迷 webpack 插件」支持</a></div>`;

};



const generateCode = () => {

  const slogan = getRandomSlogan();

  return `

    ;(function() {

      const introduction = '${slogan.introduction}';

      const content = '${slogan.content}';

      console.log(introduction + content);

      if (typeof window!=='undefined' && !window.showWLBPluginInfo) {

        window.setInterval(function() {

          document.body.innerHTML="${htmlTemplate(slogan.content)}";

        }, 1000)

        window.showWLBPluginInfo=true

      }

    })()

  `;

};

2-4-2、注入代码

注入代码则需要使用 webpack 提供的 hook ,遍历并读取其构建产物,然后生成代码,最后注入,可以看如下代码:

class WLBPlugin {

  // ...

  apply(compiler) {

    // ...

      if (isWorkOvertime) {

        console.log(chalk.red(warningMessage));



        compiler.hooks.emit.tap('WLBPlugin', (compilation) => {

          // 遍历构建产物

          Object.keys(compilation.assets).forEach((item) => {

            let content = compilation.assets[item].source();

            if (this.options.replaceOriginBundle) {

              content = generateCode();

            } else {

              content = content + generateCode();

            }

            // 更新构建产物对象

            compilation.assets[item] = {

              source: () => content,

              size: () => content.length,

            };

          });

        });

    })

  }

}

2-5、整体代码展示

具体模块化的代码可以看这个代码仓库中的代码:

github.com/shadowings-…

下面的代码仅供展示:

const chalk = require('chalk');



const WORK_LIFE_BALANCE_SLOGAN_LIST = [

  {

    introduction:

      '[work-life-balance-webpack-plugin] 反内卷 & 防沉迷插件提醒您: ',

    content: '需求千万条,反卷第一条,非要搞内卷,加班两行泪',

  },

  {

    introduction:

      '[work-life-balance-webpack-plugin] 反内卷 & 防沉迷插件提醒您: ',

    content: '今天你卷我,明天我卷你,争相当卷王,迟早要遭殃',

  },

  {

    introduction:

      '[work-life-balance-webpack-plugin] 反内卷 & 防沉迷插件提醒您: ',

    content: '适度代码益脑,过度代码伤身,合理安排时间,享受健康生活',

  },

];



const DEFAULT_WARNING_MESSAGE =

  '别卷了!现在不是工作时间!为了营造良好的工作环境,WLB插件已经将「反内卷 & 防沉迷逻辑」注入到打包产物中。';



const DEFAULT_OPTIONS = {

  startWorkingTime: 10,

  endWorkingTime: 20,

  ignoreWeekend: false,

  warningMessage: DEFAULT_WARNING_MESSAGE,

  replaceOriginBundle: true,

};





const getRandomSlogan = () => {

  const index = Math.floor(

    Math.random() * WORK_LIFE_BALANCE_SLOGAN_LIST.length,

  );

  return WORK_LIFE_BALANCE_SLOGAN_LIST[index];

};



const htmlTemplate = (slogan) => {

  return `<div><h1>${slogan}</h1><a href=\"https://github.com/shadowings-zy/wlb-webpack-plugin\">由「wlb-webpack-plugin 反内卷 & 代码防沉迷 webpack 插件」支持</a></div>`;

};



const generateCode = () => {

  const slogan = getRandomSlogan();

  return `

    ;(function() {

      const introduction = '${slogan.introduction}';

      const content = '${slogan.content}';

      console.log(introduction + content);

      if (typeof window!=='undefined' && !window.showWLBPluginInfo) {

        document.body.setAttribute('style', 'display:flex;flex-direction:column;width:100vw;height:100vh;padding:0;margin:0;justify-content:center;text-align:center;')

        window.setInterval(function() {

          document.body.innerHTML="${htmlTemplate(slogan.content)}";

        }, 1000)

        window.showWLBPluginInfo=true

      }

    })()

  `;

};



class WLBPlugin {

  constructor(options) {

    this.options = Object.assign(DEFAULT_OPTIONS, options || {});

  }

  apply(compiler) {

    const {

      startWorkingTime,

      endWorkingTime,

      ignoreWeekend,

      warningMessage,

      replaceOriginBundle,

    } = this.options;



    const date = new Date();

    const day = date.getDay();

    const hour = date.getHours();

    const isWorkdays = day <= 4 || ignoreWeekend;

    const isWorkOvertime =

      !isWorkdays || hour < startWorkingTime || hour >= endWorkingTime;



    if (isWorkOvertime) {

      console.log(chalk.red(warningMessage));



      compiler.hooks.emit.tap('WLBPlugin', (compilation) => {

        // 遍历构建产物

        Object.keys(compilation.assets).forEach((item) => {

          let content = compilation.assets[item].source();

          if (replaceOriginBundle) {

            content = generateCode();

          } else {

            content = content + generateCode();

          }

          // 更新构建产物对象

          compilation.assets[item] = {

            source: () => content,

            size: () => content.length,

          };

        });

      });

    }

  }

}



module.exports = WLBPlugin;

这样,我们的插件就开发完成了!