自動 Import 工具,前端打字員的自我救贖
自動 import 工具
作為一個前端打字員,一個經常遇到的場景就是在 路由文件中引入模塊 ,比如這樣

在 router/index.js 中寫入
import Vue from 'vue'
import Router from 'vue-router'
const About = () => import('../pages/About.vue')
const Home = () => import('../pages/Home.vue')
Vue.use(Router)
...</code></pre>
如果修改了模塊的名字,增加了模塊或者刪除了模塊,就需要重新修改這個路由文件
總是做這么機械的事情無異于消耗我這個 前端打字員 的壽命
不能忍,遂寫個工具
整理思路如下

其中,監視目錄下文件的變動依靠的是 node API 中 fs.watch(filename[, options][, listener])
替換目標文件中引入模塊的部分,則是通過正則來實現
在這里五星推薦一個驗證正則是否正確的網站, regexr
代碼實現
監視包含模塊的目錄
fs.watch(dir, {
recursive: true // 目錄下子目錄也被監視
}, (event, filename) => {
// event 是文件變動的類型,添加文件、刪除文件和修改文件名都是'rename' 事件
// filename 是變化的文件或目錄
if(event === 'rename'){ // 判斷文件變動類型
}
})</code></pre>
當發生 rename 事件后,需要重新獲得目錄下( from )所有的模塊,包括模塊名 moduleName ,模塊文件相對于引用模塊文件( to )的相對路徑 modulePath ,將它們存入變量 modules 中
實際項目中,模塊通常都是 .vue 文件,或者 .jsx 文件,因此只將這些作為模塊,在路由文件中引用
另外有些模塊文件因為各種原因,希望人工引入,而不被 watch ,這樣的文件存入 excludeArr 中
const _ = require('lodash')
let excludeArr = [...]
let modules = []
let extname = '.vue'
let from = './src/pages'
let to = './src/router/index.js"'
const mapDir = d => {
// 獲得當前文件夾下的所有的文件夾和文件
const [dirs, files] = _(fs.readdirSync(d)).partition(p =>
fs.statSync(path.join(d, p)).isDirectory()
)
// 映射文件夾
dirs.forEach(dir => {
modules.concat(mapDir(path.join(d, dir)))
})
// 映射文件
files.forEach(file => {
// 文件后綴名
let filename = path.join(d, file)
if (path.extname(file) === extname) {
if (!excludeArr.includes(path.resolve(__dirname, filename))) {
let moduleName = path.basename(file, extname)
// 若存在 -
if (moduleName.match('-')) {
moduleName = moduleName.replace(
/(-)(.{1})/,
(match, p1, p2, offset, string) => p2.toUpperCase()
)
}
modules.push({
moduleName,
modulePath: path.relative(path.dirname(to), filename)
})
}
}
})
}</code></pre>
生成好新的待引入的模塊后,接下來就是在路由文件中,將對應的內容替換掉
所以需要讀寫文件以及正則替換
const regex = /\/*\sautoImport(.\n)\/*\sautoImport\s*\//g
let importStr = ''
modules.forEach((m, index) => {
importStr =
importStr +
fillTemplate(template, m.moduleName, m.modulePath) +
(cache.length - 1 === index ? '' : '\n')
})
fs.readFile(to, 'utf8', (err, data) => {
if (err) return console.log(err)
let result = ''
if (data.match(regex)) {
result = data.replace(
regex,
/* autoImport */
${importStr}
/* autoImport */
)
} else {
/ 首次插入在文件最后的import插入 /
result = data.replace(
/(.import.)(\n)([^(import)])/,
(match, p1, p2, p3, offset, string) => {
return ${p1}
/* autoImport */
${importStr}
/* autoImport */
${p3}
}
)
}
fs.writeFile(to, result, 'utf8', err => {
if (err) return console.log(err)
})
})</code></pre>
其中 /\/\*\sautoImport(.*\n)*\/\*\sautoImport\s\*\//g 是用于匹配兩段注釋 /* autoImport */ 及其中間的內容
import Vue from 'vue'
import Router from 'vue-router'
/* autoImport */
const About = () => import('../pages/About.vue')
const Home = () => import('../pages/Home.vue')
/* autoImport */
Vue.use(Router)
當第一次使用,沒有 /* autoImport */ 時,就需要在最后一個 import 后面,插入引入的模塊
data.replace(
/(.*import.*)(\n)([^(import)])/,
(match, p1, p2, p3, offset, string) => {
return `${p1}
/* autoImport */
${importStr}
/* autoImport */
${p3}`
在這里還可以自定義了引入模塊的方式,例如懶加載, "const moduleName = () => import(modulePath)"
const template = "const moduleName = () => import(modulePath)"
const fillTemplate = (template, moduleName, modulePath) =>
template
.replace('moduleName', moduleName)
.replace('modulePath', `'${modulePath}'`)
為了工具的靈活性,把可配置項寫成json文件的形式
{
"extname": ".vue",
"from": "src/pages",
"to": "src/router/index.js",
"template": "const moduleName = () => import(modulePath)",
"exclude": [
"./src/pages/login.vue",
"./src/pages/404.vue",
"./src/pages/overall/**",
"./src/pages/account-result/**"
]
}
然后通過以下的方式來獲得
const config = fs.readFileSync('./autoImport.json')
const { extname, from, to, template, exclude } = JSON.parse(config)
后記
下一步準備把這個工具寫成webpack的插件,名字我都起好了, AutoImportPlugin ,先在github上占了個坑, 順手給顆星,不用改Bug
同時準備用更加成熟的模塊 chokidar 來代替原生的 watch
工具有問題提issue啊
來自:https://segmentfault.com/a/1190000012792016