资讯专栏INFORMATION COLUMN

重构smart-import

Pocher / 2985人阅读

摘要:前情提要自动工具,前端打字员的自我救赎记第一次发布包经历,是重构中的代码是版本可以工作的代码配置文件待导入的模块引用模块的文件引用模块的方式忽略的模块实现监听文件的删除和添加以上代码主要使用了来监听文件的变化。

前情提要

自动 Import 工具,前端打字员的自我救赎

记第一次发布npm包经历,smart-import

GitHub:smart-import

develop是重构中的代码

master是1.0版本可以工作的代码

配置文件

from:待导入的模块

to:引用模块的文件

template:引用模块的方式

ignored:忽略的模块

{
    "from": "demo/pages/**/*.vue",
    "to": "demo/router/index.js",
    "template": "const moduleName = () => import(modulePath)",
    "ignored": [
        "demo/pages/pageA.vue"
    ]
}
实现监听文件的删除和添加
#!/usr/bin/env node
const path = require("path")
const chokidar = require("chokidar")
const config = JSON.parse(fs.readFileSync("smart-import.json"))

class SmartImport {
    constructor({ from }) {
        this.from = from
        this.extname = path.extname(from)
    }

    watch() {
        chokidar
            .watch(this.from, {
                ignoreInitial: true
            })
            .on("add", file => {
                console.log("add", file)
            })
            .on("unlink", file => {
                console.log("unlink", file)
            })
    }
}

let smartImport = new SmartImport(config)
smartImport.watch()

以上代码主要使用了chokidar来监听文件的变化。但存在一个问题,如果删除文件夹,而文件夹中包含匹配的模块,不会触发unlink事件。所以改成watch整个目录,然后在addunlink的回调中添加判断文件后缀的代码,因为我们可能只在意.vue,而不在意.js

...
watch() {
        chokidar
            .watch(path.dirname(this.from), {
                ignoreInitial: true
            })
            .on("add", file => {
                if (path.extname(file) === this.extname) {
                    console.log("add", file)
                }
            })
            .on("unlink", file => {
                if (path.extname(file) === this.extname) {
                    console.log("unlink", file)
                }
            })
    }
...

现在符合from的文件的变动(添加和删除)都被监视了,但是总觉得

if (path.extname(file) === this.extname) {
  
}

写了两遍,不开心

class SmartImport {
    constructor({ from }) {
        this.from = from
        this.extname = path.extname(from)
        this.checkExt = this.checkExt.bind(this)
    }

    watch() {
        const { from, checkExt } = this
        chokidar
            .watch(path.dirname(from), {
                ignoreInitial: true
            })
            .on(
                "add",
                checkExt(file => {
                    console.log("add", file)
                })
            )
            .on(
                "unlink",
                checkExt(file => {
                    console.log("unlink", file)
                })
            )
    }

    checkExt(cb) {
        return file => {
            if (path.extname(file) === this.extname) {
                cb(file)
            }
        }
    }
}

新添加了函数checkExt(),它的参数和返回值都是函数,只是添加了判断文件后缀名的逻辑。

高阶函数有木有!

函数式编程有木有!

另外就是注意通过this.checkExt = this.checkExt.bind(this),绑定this的指向。

文件的变动映射到数组中

定义一个数组保存匹配的文件,另外匹配文件的变动会触发doImport()事件

代码就变成了这样

class SmartImport {
    constructor({ from, ignored }) {
        this.from = from
        this.ignored = ignored
        this.extname = path.extname(from)
        this.modules = []
    }

    watch() {
        const { from, ignored, extname, modules } = this
        chokidar
            .watch(path.dirname(from), {
                ignoreInitial: true,
                ignored
            })
            .on(
                "add",
                this.checkExt(file => {
                    console.log("add", file)
                    modules.push(file)
                    this.doImport()
                })
            )
            .on(
                "unlink",
                this.checkExt(file => {
                    console.log("unlink", file)
                    _.remove(modules, p => p === file)
                    this.doImport()
                })
            )
    }

    checkExt(cb) {
        const { extname } = this
        return file => {
            if (path.extname(file) === extname) {
                cb(file)
            }
        }
    }

    doImport() {
        console.log("doImport...")
        console.log(this.modules)
    }
}

注意,我又把this.checkExt = this.checkExt.bind(this)给删了,还是直接通过this.checkExt()调用方便,虽然代码看起来凌乱了。

另外就是把this.doImport()又写了两遍。嗯,思考一下。其实modules变化,就应该触发doImport()

发布-订阅模式有木有

所以添加了个类ModuleEvent

class ModuleEvent {
    constructor() {
        this.modules = []
        this.events = []
    }

    on(event) {
        this.events.push(event)
    }

    emit(type, val) {
        if (type === "push") {
            this.modules[type](val)
        } else {
            _.remove(this.modules, p => p === val)
        }
        for (let i = 0; i < this.events.length; i++) {
            this.events[i].apply(this, [type, this.modules])
        }
    }
}

同时修改类SmartImport

class SmartImport {
    constructor({ from, ignored }) {
        this.from = from
        this.ignored = ignored
        this.extname = path.extname(from)
        this.moduleEvent = new ModuleEvent()
    }

    init() {
        this.moduleEvent.on((type, modules) => {
            this.doImport(type, modules)
        })
        this.watch()
    }

    watch() {
        const { from, ignored, extname, modules } = this
        chokidar
            .watch(path.dirname(from), {
                ignoreInitial: true,
                ignored
            })
            .on(
                "add",
                this.checkExt(file => {
                    console.log("add", file)
                    this.moduleEvent.emit("push", file)
                })
            )
            .on(
                "unlink",
                this.checkExt(file => {
                    console.log("unlink", file)
                    this.moduleEvent.emit("remove", file)
                })
            )
    }

    checkExt(cb) {
        const { extname } = this
        return file => {
            if (path.extname(file) === extname) {
                cb(file)
            }
        }
    }

    doImport(type, modules) {
        console.log(`type: ${type}`)
        console.log(modules)
    }
}

let smartImport = new SmartImport(config)
smartImport.init()

终于理解了很多库中on方法的原理有木有!对象中有个events,专门存这些回调函数有木有

另外我们观察chokidar.on(eventType, cb),对比自己的moduleEvent.on(cb)。想想也是,也许我只想监听特定的事件呢

修改ModuleEvent

class ModuleEvent {
    constructor({ from, ignored }) {
        this.modules = glob.sync(from, {
            ignore: ignored
        })
        this.events = {}
    }

    on(type, cb) {
        if (!this.events[type]) {
            this.events[type] = []
        }
        this.events[type].push(cb)
    }

    emit(type, val) {
        if (type === "push") {
            this.modules[type](val)
        } else {
            _.remove(this.modules, p => p === val)
        }
        for (let i = 0; i < this.events[type].length; i++) {
            this.events[type][i].apply(this, [this.modules])
        }
    }
}

后来觉得这个套路挺常见,将其抽象出来,最后形成代码如下

#!/usr/bin/env node
const fs = require("fs")
const path = require("path")
const glob = require("glob")
const chokidar = require("chokidar")
const _ = require("lodash")
const config = JSON.parse(fs.readFileSync("smart-import.json"))

const CustomEvent = (() => {
    let events = {}
    let on = (type, cb) => {
        if (!events[type]) {
            events[type] = []
        }
        events[type].push(cb)
    }
    let emit = (type, data) => {
        for (let i = 0; i < events[type].length; i++) {
           events[type][i].apply(this, [data])
        }
    }
    return {
        on,
        emit
    }
})()

class SmartImport {
    constructor({ from, ignored }) {
        this.from = from
        this.ignored = ignored
        this.extname = path.extname(from)
        this.modules = glob.sync(from, {
            ignore: ignored
        })
    }

    init() {
        CustomEvent.on("push", m => {
            console.log("Do pushing")
            this.modules.push(m)
        })
        CustomEvent.on("remove", m => {
            console.log("Do removing")
            _.remove(this.modules, p => p === m)
        })
        this.watch()
    }

    watch() {
        const { from, ignored, extname, modules } = this
        chokidar
            .watch(path.dirname(from), {
                ignoreInitial: true,
                ignored
            })
            .on(
                "add",
                this.checkExt(file => {
                    CustomEvent.emit("push", file)
                })
            )
            .on(
                "unlink",
                this.checkExt(file => {
                    CustomEvent.emit("remove", file)
                })
            )
    }

    checkExt(cb) {
        const { extname } = this
        return file => {
            if (path.extname(file) === extname) {
                cb(file)
            }
        }
    }
}

let smartImport = new SmartImport(config)
smartImport.init()
未完待续

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92502.html

相关文章

  • 记第一次发布npm包经历,smart-import

    摘要:故事背景前情提要自动工具,前端打字员的自我救赎的功能根据配置文件,在目标文件中自动导入规定目录下自定义模块,并监听规定目录下文件的变动,自动更新尚在测试中的使用安装工具编写配置文件需要自动导入的模块的后缀名自动导入的模块的来源目 故事背景 前情提要:自动 Import 工具,前端打字员的自我救赎 github: smart-import smart-import 的功能 根据配置文件...

    Raaabbit 评论0 收藏0
  • 重构:一项常常被忽略的基本功

    摘要:无论如何,单元测试一直是一中非常重要却常常被忽视的技能。在实践中,重构的要求是很高的它需要有足够详尽的单元测试,需要有持续集成的环境,需要随时随地在小步伐地永远让代码处于可工作状态下去进行改善。 showImg(https://segmentfault.com/img/bVbttWF?w=1000&h=528); 五月初的时候朋友和我说《重构》出第 2 版了,我才兴冲冲地下单,花了一个...

    idealcn 评论0 收藏0
  • 重构改善既有的代码设计(重构原则)

    摘要:难以通过重构手法完成设计的改动先想像重构的情况。何时不该重构现有代码根本不能正常运作。现在,我可以修改这个子类而不必承担午一中影响另一处的风险。 重构:对软件内部结构的一种调整,目的是再不改变软件的可观察行为的前提下,提高其可理解性,降低其修改成本。 两顶帽子 添加新功能 添加新功能时不应该修改既有代码,只管添加新功能,通过测试重构 重构时你就不能再添加功能,只管改进程序结构,此时...

    XUI 评论0 收藏0
  • 重构-改善既有代码的设计(二) --重构原则

    摘要:改进代码设计的一个重要原则就是消除重复代码使软件更容易被理解优秀的代码能够让接收你代码的付出更少的学习成本。重构更容易找到重构能加深对代码的理解。可以重构的情况添加功能时可以重构。说明你没有发现代码的错误。需要重构复审代码时可以重构。 为何重构 重构不是银弹,但是帮助你达到以下几个目的 改进软件设计 不良的程序需要更多的代码。而代码越多,正确的修改就越困难。改进代码设计的一个重要原则就...

    myshell 评论0 收藏0
  • 重构-改善既有代码设计》读书笔记-重构

    摘要:重构改善既有代码设计动词使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。修补错误时重构代码时重构怎么重构关于代码的重构技巧参考重构改善既有代码设计读书笔记代码篇个人博客 重构定义 名词 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。——《重构-改善既有代码设计》 动词 使用一系列重构手法,在不改变软件可观察行为的前提下,...

    ermaoL 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<