前情提要
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整个目录,然后在add和unlink的回调中添加判断文件后缀的代码,因为我们可能只在意.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()