自動 Import 工具,前端打字員的自我救贖

自動 import 工具

先推薦兩個乾貨,關於正則的
regexrregexper,前者驗證正則是否和預期同樣,後者以圖的形式表達正則,有助於理解天書般的別人寫的正則前端

做爲一個前端打字員,一個常常遇到的場景就是在路由文件中引入模塊,好比這樣vue

Screenshot_2018-01-11 01.02.52_b1KSEv

router/index.js 中寫入node

import Vue from 'vue'
import Router from 'vue-router'

const About = () => import('../pages/About.vue')
const Home = () => import('../pages/Home.vue')

Vue.use(Router)
...

若是修改了模塊的名字,增長了模塊或者刪除了模塊,就須要從新修改這個路由文件webpack

老是作這麼機械的事情無異於消耗我這個前端打字員的壽命git

不能忍,遂寫個工具github

整理思路以下

Screenshot_2018-01-11 01.01.36_00Cube

其中,監視目錄下文件的變更依靠的是 node API 中fs.watch(filename[, options][, listener])web

替換目標文件中引入模塊的部分,則是經過正則來實現vue-router

在這裏五星推薦一個驗證正則是否正確的網站,regexrjson

代碼實現

監視包含模塊的目錄工具

fs.watch(dir, {
  recursive: true // 目錄下子目錄也被監視
}, (event, filename) => { 
// event 是文件變更的類型,添加文件、刪除文件和修改文件名都是'rename' 事件
// filename 是變化的文件或目錄
  if(event === 'rename'){ // 判斷文件變更類型
    
  }
})

當發生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)
                })
            }
        }
    })
}

生成好新的待引入的模塊後,接下來就是在路由文件中,將對應的內容替換掉

因此須要讀寫文件以及正則替換

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)
                    })
                })

其中/\/\*\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啊

相關文章
相關標籤/搜索