基于node實現(xiàn)文件路徑替換

寫在最前


本次分享一個簡易路徑替換工具沙咏。功能很簡單废膘,重點在于掌握:

  • 遞歸遍歷文件夾目錄
  • 正則替換目標(biāo)內(nèi)容
  • 解壓上傳文件厂庇,返回更新后的壓縮文件

源碼地址:https://github.com/Aaaaaaaty/Blog/tree/master/fsPathSys

效果預(yù)覽

在線預(yù)覽

結(jié)果對比圖:

wechatimg24

PS:后端支持匹配js、css、img口叙、background-image的url的對應(yīng)路徑并進(jìn)行分別替換,當(dāng)前只是展示方便嗅战,前端只傳遞一個路徑將所有匹配的源路徑替換為目標(biāo)路徑妄田。

整體流程

  1. 前端上傳壓縮包及需要替換的路徑字段
  2. 后端解壓縮
  3. 遞歸文件目錄俺亮,找到.js/.css/.html文件并匹配替換路徑
  4. 壓縮整體文件,返回到前端

整體來說可能會遇到的難點在于對正則的使用疟呐,以及完成替換后將壓縮的文件夾傳回本地脚曾。以前沒怎么寫過正則正好借此機(jī)會來學(xué)習(xí)一波,同時對于文件夾(注意不是文件傳輸F艟摺)傳輸踩了一下坑本讥。畢竟大部分時間做靜態(tài)服務(wù)器我們是只需要返回單個文件不需要以一個文件夾的形式來返回到前端。

解壓縮zip

在nodejs文檔中發(fā)現(xiàn)原生api貌似只支持gzip的解壓縮鲁冯,故引入了第三方插件unzip來解決拷沸。

let inp = fs.createReadStream(path)
let extract = unzip.Extract({ path: targetPath })
inp.pipe(extract)
extract.on('error', () => {
    cons('解壓出錯:' + err);
})
extract.on('close', () => {
    cons('解壓完成');
})

這個插件有一點坑的地方在于它沒有說明如何監(jiān)聽'close'、'error'等事件薯演。還是我去看源碼里面發(fā)現(xiàn)要通過上面的形式來調(diào)用才能成功:)

遞歸文件目錄

通過fs模塊的stat方法來判斷當(dāng)前路徑是文件還是文件夾來決定是否繼續(xù)遍歷撞芍。

function fsPathSys(path) { //遍歷路徑
    let stat = fs.statSync(path)
    if(stat.isDirectory()) {
        fs.readdir(path, isDirectory) //讀文件夾
        function isDirectory(err, files) {
            if(err) {
                return err
            } else {
                files.forEach((item, index) => {
                    let nowPath = `${path}/${item}`
                    let stat = fs.statSync(nowPath)
                    if(!stat.isDirectory()) {
                        ...somthing going on
                    } else {        
                        fsPathSys(nowPath)
                    }
                })
            }
        }
    }
    else {
        ...
    }
        
}

正則匹配

正則的重點則在于如何匹配到需要的地方,以及替換的順序也需要有所考量涣仿。
本次需要匹配的地方有四個:

  • script標(biāo)簽下的src
  • link標(biāo)簽下的href
  • img標(biāo)簽下的src
  • css中background-image下的url

由于目標(biāo)地址前的關(guān)鍵字src勤庐、href可能在不同的標(biāo)簽中,同時最初的想法就是有可能不同類型的文件的存放地址是不同的好港。故采用的匹配原則是先將script愉镰、link、img钧汹、background提取出來丈探,然后再分別匹配src、href拔莱、url關(guān)鍵字碗降。

//body:要替換的文本
let data = [
    {   
        'type': 'script',
        'point': targetUrl
    },
    {   
        'type': 'link',
        'point': targetUrl
    },
    {   
        'type': 'img',
        'point': targetUrl
    },
    {   
        'type': 'background',
        'point': targetUrl
    }
]
data.forEach((obj, i) => {
    if(obj.type === 'script' || obj.type === 'link' || obj.type === 'img') {
        let bodyMatch = body.match(new RegExp(`<${obj.type}.*?>`, 'g'))
        if(bodyMatch) {
            bodyMatch.forEach((item, index) => {
                let itemMatch = item.match(/(src|href)\s*=\s*["|'].*?["|']/g)
                if(itemMatch) {
                    itemMatch.forEach((data, i) => {
                        let matchItem = data.match(/(["|']).*\//g)[0].replace(/\s/g, '').slice(1)
                        if(!replaceBody[matchItem]) {
                            replaceBody[matchItem] = obj.point
                        }
                    })
                }
            })
        }
    } else if(obj.type === 'background') {
        let bodyMatch = body.match(/url\(.*?\)/g)
        if(bodyMatch) {
            bodyMatch.forEach((item, index) => {
                let itemMatch = item.match(/\(.*\//g)[0].replace(/\s/g, '').slice(1)
                if(!replaceBody[itemMatch]) {
                    replaceBody[itemMatch] = obj.point
                }
            })
        }

    }
})

其中關(guān)于正則的使用可以參考這篇文章JS正則表達(dá)式完整教程(略長) 真的是非常詳細(xì),我就不班門弄斧了塘秦∷显ǎ總的來說上面的代碼得到了一個對象,replaceBody尊剔。這個對象的key是要替換的路徑爪幻,value是替換后的路徑:

wechatimg25

細(xì)心的童鞋可能會發(fā)現(xiàn),如果現(xiàn)在直接遍歷這個對象進(jìn)行替換是不是就能大功告成了呢须误?肯定不是的:)因為替換要有先后順序挨稿,不然會有大麻煩。
例如我們將要替換'../css/'以及'./css/'京痢,如果我們先替換后者那么之前的'../css/中的'./css/'也會被換掉從而整體替換失敗這并不是我們想要的結(jié)果奶甘。

目前的做法是將對象中的key排序,長的在前祭椰,之后再進(jìn)行替換臭家。這樣至少不會出現(xiàn)上面所提到的情況疲陕。

Object.keys(replaceBody).sort((a,b) => b.length - a.length) //對對象排序

另外還需要注意一個小點即在替換'.'的時候,由于'.'在正則中表示通配符钉赁。那么此時需要先將所有的'.'替換為'.'再進(jìn)行下面的操作鸭轮。

壓縮整體文件,返回到前端

考慮到現(xiàn)在要傳回前端的是一個文件夾橄霉,故要對其進(jìn)行壓縮。采用開啟子進(jìn)程的方式來編寫shell命令來壓縮文件夾邑蒋。(node的zlib模塊我沒找到怎么來壓縮文件夾姓蜂。。有知道的同學(xué)歡迎分享)

let dirName = `${filePath}.tar.gz`
exec(`tar -zcvf ${dirName} ${filePath}`, (error, stdout, stderr) => {
    if (error) {
        cons(`exec error: ${error}`);
        return;
    }
    let out = fs.createReadStream(dirName)
    res.writeHead(200, {
        'Content-type':'application/octet-stream',
        'Content-Disposition': 'attachment; filename=' + dirName.match(/ip_.*/)[0] 
    })
    out.pipe(res) 
})

這里的重點是將壓縮包用流的形式讀取出來如果不在返回頭加入'Content-Disposition'字段医吊,返回的文件將是那種類似buffer流的形式钱慢,沒有了文件夾層級結(jié)構(gòu)等等。卿堂。查閱了資料才發(fā)現(xiàn)是因為這個頭的緣故束莫。

Content-disposition 是 MIME 協(xié)議的擴(kuò)展,MIME 協(xié)議指示 MIME 用戶代理如何顯示附加的文件草描。

小結(jié)

本次實現(xiàn)這個小工具览绿,使作者正則還有文件在后端的壓縮解壓以及http傳輸中的細(xì)節(jié)有了新的認(rèn)識。源代碼在git上歡迎clone~

參考文獻(xiàn)

最后

慣例po作者的博客穗慕,不定時更新中——
有問題歡迎在issues下交流饿敲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逛绵,隨后出現(xiàn)的幾起案子怀各,更是在濱河造成了極大的恐慌,老刑警劉巖术浪,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓢对,死亡現(xiàn)場離奇詭異,居然都是意外死亡胰苏,警方通過查閱死者的電腦和手機(jī)硕蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碟联,“玉大人妓美,你說我怎么就攤上這事±鸱酰” “怎么了壶栋?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長普监。 經(jīng)常有香客問我贵试,道長琉兜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任毙玻,我火速辦了婚禮豌蟋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桑滩。我一直安慰自己梧疲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布运准。 她就那樣靜靜地躺著幌氮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胁澳。 梳的紋絲不亂的頭發(fā)上该互,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音韭畸,去河邊找鬼宇智。 笑死,一個胖子當(dāng)著我的面吹牛胰丁,可吹牛的內(nèi)容都是我干的随橘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼锦庸,長吁一口氣:“原來是場噩夢啊……” “哼太防!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酸员,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蜒车,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幔嗦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酿愧,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年邀泉,在試婚紗的時候發(fā)現(xiàn)自己被綠了嬉挡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡汇恤,死狀恐怖庞钢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情因谎,我是刑警寧澤基括,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站财岔,受9級特大地震影響风皿,放射性物質(zhì)發(fā)生泄漏河爹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一桐款、第九天 我趴在偏房一處隱蔽的房頂上張望咸这。 院中可真熱鬧,春花似錦魔眨、人聲如沸媳维。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侨艾。三九已至,卻和暖如春拓挥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袋励。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工侥啤, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茬故。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓盖灸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親磺芭。 傳聞我的和親對象是個殘疾皇子赁炎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容