Webpack 通过 Plugin 机制让其更加灵活,以适应各种应用场景。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

一个最基础的 Plugin 的代码是这样的:

class WebpackMockServicePlugin{
  // 在构造函数中获取用户给该插件传入的配置
  constructor(options){
  }

  // Webpack 会调用 WebpackMockServicePlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply(compiler){
  }
}

// 导出 Plugin
module.exports = WebpackMockServicePlugin;

在使用这个 Plugin 时,相关配置代码如下:

const WebpackMockServicePlugin= require('./WebpackMockServicePlugin.js');
module.export = {
  plugins:[
    new WebpackMockServicePlugin(options),
  ]
}

接下来开始我们的WebpackMockServicePlugin插件之旅

一、启动服务

我们知道mockjs可直接引入到项目,mockjs实质是通过ajax拦截,并不是真实的接口访问,要做到真实的接口访问,我们必须先启动一个服务。

安装 express

npm install express --save
or
yarn add express

创建一个serveice:

const express = require('express')
const app = express()

app.route()
    .get('/', (req, res) => {
        res.send('hello WebpackMockServicePlugin')
    })

class WebpackMockServicePlugin {
    constructor(options = {}) {}

    apply(compile) {
        app.listen(3000, () => {})
    }
}

module.exports = WebpackMockServicePlugin

设置默认端口及默认接口配置目录

class WebpackMockServicePlugin{
    constructor(options = {}) {
        this.port = options.port || 3000
        this.mockDir = path.join(process.cwd(), options.mockDir || 'mock')
    }
    
    async apply(compile) {
      this.listen()
    }
    
    listen() {
      app.listen(this.port, () => {
        console.log(`mock服务启动成功: http://localhost:${this.port}`)
      })
    }
}

二、根据目录结构生成对应的路由

目录结构及要生成的路由如下:

├─ mock
│  ├─ user
│  │  ├─ index.json                        // [get]     /user
│  │  ├─ del.delete.json                   // [delete]  /user/del
│  │  ├─ update.put.json                   // [put]     /user/update
│  │  └─ add.post.json                     // [post]    /user/add
│  ├─ news
│  │  └─ index.json                        // [get]     /news

生成路由代码并读取json文件生成返回数据:

class WebpackMockServicePlugin{
    ...
    
    async apply(compile) {
      this.createRoute()
      this.listen()
    }
    
    ...

    createRoute() {
      // 如果文件目录不存在,就创建该目录
      !fs.existsSync(this.mockDir) && fs.mkdirSync(this.mockDir)
      // 读取目录生成路由
      fs.readdir(this.mockDir, (err, dirs) => {
        if (err) throw err
        dirs.forEach(dir => {
          const files = fs.readdirSync(`${this.mockDir}/${dir}`)
          files.forEach(file => {
            const textArr = file.match(/(\w+).?(\w+)?.json/)
            if (textArr) {
              // type -> 请求类型
              const type = textArr[2] || 'get'
              /**
               * 根据文件格式生成路由
               * userinfo/index.json -> /userinfo [get]
               * userinfo/update.post.json -> /userinfo/update [post]
               */
              app.route(textArr[1] === 'index' ? `/${dir}` : `/${dir}/${textArr[1]}`)
                [type]((req, res) => {
                let mkdir = `${this.mockDir}/${dir}/${file}`
                const content = fs.readFileSync(mkdir)
                const data = JSON.parse(content.toString().replace(/\n/g, ''))
                res.json(Mock.mock(data))
              })
            }
          })
        })
      })
    }
}

三、判断端口是否被占用

写来写一个检测端口是否可用的方法:
注:监听带上 0.0.0.0

/**
 * 检测端口是否可以使用
 * @param {Number} port
 * @return {Promise<Boolean>}
 */
function portInUse(port) {
  return new Promise((resolve) => {
    const server = net.createServer().listen(port, '0.0.0.0');
    server.on('listening', function () {
      server.close()
      resolve(true)
    })

    server.on('error', function (err) {
      if (err.code === 'EADDRINUSE') {
        resolve(false)
      }
    })
  })
}

设置端口:

class WebpackMockServicePlugin {
    ...

    async apply(compile) {
      while (!(await portInUse(this.port))) {
        this.port++
      }
      this.createRoute()
      this.listen()
    }

    ...

}

四、监听文件变化更新路由

创建一个监听类,实现文件变化的监听:

const event = require('events')
const fs = require('fs')

class Watcher extends event.EventEmitter {
  constructor(watchDir) {
    super();
    this.watchDir = watchDir
    this.watchList = new Set()
  }

  watch() {
    this.watchList.forEach(value => {
      fs.watchFile(value, () => {
        this.emit('process', value)
      })
    })
  }

  start() {
    this.watchList.add(this.watchDir)
    fs.readdir(this.watchDir, ((err, files) => {
      if (files) {
        files.forEach(value => {
          this.watchList.add(`${this.watchDir}/${value}`)
        })
      }
      this.watch()
    }))
  }

}

module.exports = Watcher

因为watchFile只能监听到当前目录文件的变化,所有我们要创建一个watchList 存放所有需要监听的目录集合,
start():循环遍历所有的目录存放到watchFile集合
watch(): 监听所有目录,目录有变化时会触发process事件

使用Watcher类监听目录:

class WebpackMockServicePlugin {
    ...

    listen() {
      app.listen(this.port, () => {
        console.log(`mock服务启动成功: http://localhost:${this.port}`)
        /**
         * 开启监听目录,当目录或文件有更新时重新生成路由
         * @type {Watcher}
         */
        this.watcher = new Watcher(this.mockDir)
        this.watcher.on('process', dir => {
          // 之前没有创建目录没有绑定监听
          // 所以创建了新的目录时要重新执行监听
          this.watcher.start()
          // 文件发生改变时重新生成路由
          this.createRoute()
        })
        this.watcher.start()
      })
    }

    ...

}

这样我们就实现了一个真实的接口,并通过mockjs生成模拟数据。

完整代码及实例:https://github.com/yinMrsir/w...


前端老鹰
128 声望4 粉丝

欢迎大家访问我的github:[链接]