前情提要
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()