nodeJS前端腳手架——公用代碼塊,只寫一邊就好


最近接手一個(gè)任務(wù),給一個(gè)集團(tuán)寫一個(gè)靜態(tài)站點(diǎn),看了設(shè)計(jì)稿發(fā)現(xiàn)除了首頁,其他頁面的頭部nav和底部footer都是一樣的。如果寫好粘貼賦值粘貼復(fù)制,多累??!————于是想到一個(gè)需求:




如何能讓頁面公用部分寫一邊,然后所有頁面都能同步?



作為前端出身,技術(shù)棧又比較少~ 第一個(gè)想到的就是nodeJS了!畢竟JS是前端特有唯一一門編程語言了!






首先,屢屢思路:



  1. 目的是:公用部分單獨(dú)寫一份,然后頁面只需要寫除了公用部分之外的即可。
  2. 前提是:如果出現(xiàn)異常,不能影響自己寫的代碼->所以要另外建立一個(gè)文件夾,用于存放node處理后的代碼




接下來,開始node編寫:

<strong>section 1</strong>


一、 首先在新建文件夾下創(chuàng)建兩個(gè)文件夾:

app(寫代碼的地方)
public(生成代碼的地方)
handile.js(nodeJS文件)

目錄結(jié)構(gòu)如下:




二、 做到將app里的文件復(fù)制到public中
  解析功能

//復(fù)制文件用到node的fs模塊,用到readFile,writeFile功能,然而如果用這兩個(gè)語法,它的原理是將文件內(nèi)容讀取保存在內(nèi)存中,然后再一次性寫入一個(gè)文件
//想想,這樣就會(huì)遇到一個(gè)問題:如果文件比較大,一次性讀取和寫入,會(huì)不會(huì)很吃內(nèi)存。
//就像是這里要從水龍頭取水,將旁邊一個(gè)無法移動(dòng)的大缸接滿,如果選擇一次性接一缸水,再倒到水缸里,這顯然是不明智的
//考慮這一點(diǎn),這里要用Stream流信息處理方式,就是讀著寫著,像有水管一樣,上面流著,下面接著,這樣可行性就更高了,代碼如下:
var fs = require('fs');
function copy(origin, aim) {
    fs.createReadStream(origin).pipe(fs.createWriteStream(aim));
}

//這里pipe方法就是這里舉例中的水管



  現(xiàn)在有了一個(gè)能連通數(shù)據(jù)流的水管了,這里要將水管入口接到水龍頭上,出口接到大缸里





  但現(xiàn)在這里還遇到一個(gè)問題,app文件夾下是可能會(huì)有img css js等文件夾的,而這里的方法createReadStream只能讀取指定的一個(gè)文件,



<strong>所以這里要有一個(gè)遍歷app和hbuild目錄的方法,把a(bǔ)pp目錄里所有文件的路徑遍歷出來,給copy方法的origin參數(shù),再將app目錄里文件的路徑名字中app替換為hbuild,傳給copy方法的aim參數(shù),這樣如果app文件夾下有多個(gè)文件,就相當(dāng)于這里創(chuàng)造了多條水管,每個(gè)水管只負(fù)責(zé)一個(gè)文件信息流的傳輸</strong>



這里要用到readdir方法,這里選擇同步讀取,保證開水龍頭,水管傳輸,水進(jìn)入水缸的正確順序,這樣就不用promise來單獨(dú)處理異步了。所以這里用readdirSync方法

var path = require('path');
function travel(dir, callback) {
    fs.readdirSync(dir).forEach(function(file) {
        var pathname = path.join(dir, file); //將遍歷到文件的路徑給變量pathname
        
        if(fs.statSync(pathname).isDirectory()) {//判斷是當(dāng)前遍歷到的文件是否是一個(gè)文件夾,如果是則進(jìn)入文件夾再進(jìn)行遍歷:
            travelSync(pathname, callback);
        } else {
            callback(pathname);
        }
    })
}




這樣一個(gè)tranvel函數(shù),需要兩個(gè)參數(shù),遍歷的目錄路徑,和回調(diào)函數(shù)
這樣這里就可以組合兩個(gè)函數(shù)進(jìn)行遍歷和寫入  

var fs = require('fs');
var path = require('path');

travelSync('./app', function(pathname) {
    if(pathname) {
        var publicPath = pathname.replace('app', 'public')
            copy(pathname, publicPath);
    }
})

function travelSync(dir, callback) {
    fs.readdirSync(dir).forEach(function(file) {
        var pathname = path.join(dir, file);
        console.log(pathname)
        if(fs.statSync(pathname).isDirectory()) {
            travelSync(pathname, callback);
        } else {
            callback(pathname);
        }
    console.log('文件復(fù)制成功!')
    });
}

function copy(src, dst) {
    fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}


這里在app文件夾里新建幾個(gè)目錄和文件來做測(cè)試


header index footer 三個(gè)html文件里分別寫:

<!-- header.html -->
<header>
this is header!
</header>
<!-- index.html -->
<div class="content">
this is index.html
</div>
<!-- footer.html -->
<footer>
this is footer!
</footer>



然后在終端 cmd, 進(jìn)入當(dāng)前文件夾路徑,執(zhí)行 node handle.js  




然后再打開public文件夾,看下,發(fā)現(xiàn):


哎!what? 為什么css img js文件夾和里面的文件沒有復(fù)制過來?


這是因?yàn)檫@里在遍歷目錄的時(shí)候,copy方法只實(shí)現(xiàn)了文件流信息的傳輸,但沒有實(shí)現(xiàn)文件夾創(chuàng)建功能,所以這樣運(yùn)行會(huì)出現(xiàn)報(bào)錯(cuò):



那么接下來就要寫cipyDir方法:

function copyDir(path) {
    fs.mkdir(path, function(err) { //mkdir方法創(chuàng)建文件夾只需要通過第一個(gè)參數(shù)告訴node路徑和文件夾名即可創(chuàng)建,簡(jiǎn)單暴力
        if(!err) console.log(path+' 目錄創(chuàng)建成功!')
    });
}

這里這里要修改一下travel方法,當(dāng)travel遍歷到文件夾的時(shí)候,在build文件夾下執(zhí)行創(chuàng)建文件夾命令:

function travelSync(dir, callback) {
    fs.readdirSync(dir).forEach(function(file) {
        var pathname = path.join(dir, file);

        if(fs.statSync(pathname).isDirectory()) {
            callback(pathname,'dir'); //新增:遇到文件夾也執(zhí)行回調(diào),并傳入一個(gè)字符串用于告知回調(diào)函數(shù)知道這是一個(gè)文件夾的pathname
            travelSync(pathname, callback);
        } else {
            callback(pathname,'file');//這里也添加告知回調(diào)函數(shù)文件類型的第二個(gè)參數(shù)'file'
        }
    });
}

接下來擼一遍現(xiàn)在的handle.js,是這樣的:


var fs = require('fs');
var path = require('path');


travelSync('./app', function(pathname, fileType) {
    if(pathname && fileType === 'file') {
        // app/header.html
        var publicPath = pathname.replace('app', 'public');
        copy(pathname, publicPath);
    } else if (pathname && fileType === 'dir') {
        // app/css
        var publicPath = pathname.replace('app', 'public');
        copyDir(publicPath)
    }
})

function travelSync(dir, callback) {
    fs.readdirSync(dir).forEach(function(file) {
        var pathname = path.join(dir, file);

        if(fs.statSync(pathname).isDirectory()) {
            callback(pathname,'dir');
            travelSync(pathname, callback);
        } else {
            callback(pathname,'file');
        }
    });
}

function copy(path, aimPath) {
    fs.createReadStream(path).pipe(fs.createWriteStream(aimPath));
}

function copyDir(path) {
    fs.mkdir(path, function(err) {
        if(!err) console.log(path+' 目錄創(chuàng)建成功!')
    });
}

ok,現(xiàn)在再測(cè)試下復(fù)制功能是否能解決剛才無法復(fù)制文件夾的問題:命令行運(yùn)行:node handle.js,打印出:

再看public文件夾目錄:

漂亮!現(xiàn)在復(fù)制功能已經(jīng)達(dá)到。敲代碼的,就是需要做好鋪墊,才能快速行動(dòng)~ 接下來進(jìn)入關(guān)鍵時(shí)刻,處理文件的公用部分


<strong>section 2</strong>


這里模擬測(cè)試將app里的header footer 整合到index.html的 頭 和 尾。 現(xiàn)在要用到readFile 和 writeFile語法,讀取header footer里指定的內(nèi)容,再寫入index指定位置。


這里先想一個(gè)問題:如果文件比較大,內(nèi)容比較復(fù)雜,如何才能準(zhǔn)確的找到header footer里面指定的代碼段, 然后準(zhǔn)確的寫入index指定的位置?先別看下面解決方案,兩分鐘,看自己能想出哪些方法











我暫想到相對(duì)好用的方法是:

  1. 在header footer文件需要截取的代碼段頭尾添加一個(gè)標(biāo)記行注釋標(biāo)簽,同樣,在index里面也放一個(gè)注釋標(biāo)簽,告訴handle.js準(zhǔn)確位置

也有一個(gè)不好用的:就是直接判斷代碼段開始標(biāo)簽,和結(jié)束標(biāo)簽,這樣每次都要嚴(yán)格查詢代碼并做更改,其實(shí)挺麻煩的。



這里修改header index footer,添加標(biāo)記注釋,分別更改為:

<!-- header begin -->
<header>
    this is header!
</header>
<!-- header end -->
<!-- index begin -->
<div class="content">
    this is index.html
</div>
<!-- index end -->
<!-- footer begin -->
<footer>
    this is footer!
</footer>
<!-- footer end -->

接下來在heandle.js里寫header footer代碼段截取功能,

function getHeader() {
    fs.readFile('./app/header.html', function(err, html){
        var headerData = html.toString();
        // sliceStart只需要找到字符串的索引位置即可
        var sliceStart = headerData.indexOf('<!-- header begin -->');
        // sliceEnd找到字符串的位置后,加上字符串本身的長(zhǎng)度再加2是一個(gè)換行符的
        var sliceEnd = headerData.indexOf('<!-- header end -->') + '<!-- header end -->'.length+2;
        var header = headerData.slice(sliceStart, sliceEnd);
        console.log(header)
    });
}
getHeader();

命令行運(yùn)行node handle.js后 看到:

這樣就準(zhǔn)確找到想要的代碼段了。
由于還需要用同樣的方法找到index和footer代碼段,所以這里將getHeader方法封裝成一個(gè)getAimCode方法:

function getAimCode(headerPath, flagBegin, flagEnd) {
  fs.readFile(headerPath, function(err, html){
    if(!err) {
      var headerData = html.toString();
      // sliceStart只需要找到字符串的索引位置即可
      var sliceStart = headerData.indexOf(flagBegin);
      // sliceEnd找到字符串的位置后,加上字符串本身的長(zhǎng)度再加2是一個(gè)換行符的
      var sliceEnd = headerData.indexOf(flagEnd) + flagEnd.length+2;
      var header = headerData.slice(sliceStart, sliceEnd);
      console.log(header)
    }
  });
}
//測(cè)試以下用getAimCode獲取footer代碼段
getAimCode('./app/footer.html', '<!-- footer begin -->', '<!-- footer end -->');

封裝函數(shù)最笨的辦法:把可能會(huì)變動(dòng)的內(nèi)容,替換成參數(shù)變量,再通過函數(shù)調(diào)用,以參數(shù)的形式傳進(jìn)去,可以運(yùn)行下上面代碼,即可得到footer代碼段,測(cè)試上面代碼即可得到:

現(xiàn)在能找到需要截取的代碼段,接下來要進(jìn)行代碼段的準(zhǔn)確插入:

其實(shí)插入代碼段的基礎(chǔ)原理,就是將目標(biāo)文件信息讀取出來,分為三段:

  1. 插入位置之前的代碼段 top
  2. 插入header和footer之間的代碼段 content
  3. 插入footer之后的代碼段 bottom

然后只需要將代碼段拼接成 top + header + content + footer + bottom 這么一個(gè)完整的代碼塊,再寫入目標(biāo)文件就完成了。

接下來對(duì)目標(biāo)文件index進(jìn)行分割:

為了明顯看出index的分割效果,這里將index.html更改內(nèi)容為:

<html>
   <head>
       <title>index</title>
   </head>

<!-- index begin -->
<div class="content">
   this is index.html
</div>
<!-- index end -->

</html>

回想下,從header.html中切出header代碼段的時(shí)候,是找到

<!-- header begin -->
和
<!-- header end -->

兩個(gè)標(biāo)記的索引位置,用String.slice(start,end)方法來切割的。那么index的三段一樣要找到top content bottom三者的開始位置和結(jié)束位置。這里先默認(rèn)index文件的內(nèi)容被讀取為:indexData,那么:

1. top:是從文檔的開始,開始位置就是索引值0,
2. 結(jié)束索引值就是index begin標(biāo)記的開始,即indexData.indexOf('<!-- index begin -->');

3. content:開始索引值是indexData.indexOf('<!-- index begin -->');
4. 結(jié)束索引值是:indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;

5. bottom:開始索引值是indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2,
6. bottom可以直接切到文檔結(jié)尾,slice方法如果不傳入第二個(gè)參數(shù)的話就默認(rèn)直接從指定索引位置切割到結(jié)尾

這樣就可以寫出函數(shù):

function cutIndex() {
  fs.readFile('./app/index.html', function(err, html) {
    if(!err) {
      var indexData = html.toString();
      var topStart = 0,
        topEnd = indexData.indexOf('<!-- index begin -->');
      var contentStart = indexData.indexOf('<!-- index begin -->'),
        contentEnd = indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;
      var bottomStart = indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;

      var top = indexData.slice(topStart, topEnd),
        content = indexData.slice(contentStart, contentEnd),
        bottom = indexData.slice(bottomStart);
      console.log(top+'top打印結(jié)束');
      console.log(content+'content打印結(jié)束');
      console.log(bottom+'\n bottom打印結(jié)束');
    }
  })
}
cutIndex();

命令行里執(zhí)行:node handle.js
打印出:

現(xiàn)在得到了header footer top content bottom,接下來要做拼接并的功能。

將我們的代碼組裝起來成為:

fs.readFile('./app/header.html', function(err, html){
    if(!err) {
        var headerData = html.toString();
        // sliceStart只需要找到字符串的索引位置即可
        var sliceStart = headerData.indexOf('<!-- header begin -->');
        // sliceEnd找到字符串的位置后,加上字符串本身的長(zhǎng)度再加2是一個(gè)換行符的
        var sliceEnd = headerData.indexOf('<!-- header end -->') + '<!-- header end -->'.length+2;
        var header = headerData.slice(sliceStart, sliceEnd);
    }

    fs.readFile('./app/footer.html', function(err, html){
        if(!err) {
            var footerData = html.toString();
            // sliceStart只需要找到字符串的索引位置即可
            var sliceStart = footerData.indexOf('<!-- footer begin -->');
            // sliceEnd找到字符串的位置后,加上字符串本身的長(zhǎng)度再加2是一個(gè)換行符的
            var sliceEnd = footerData.indexOf('<!-- footer end -->') + '<!-- footer end -->'.length+2;
            var footer = footerData.slice(sliceStart, sliceEnd);
        }
        
        fs.readFile('./app/index.html', function(err, html) {
            if(!err) {
                var indexData = html.toString();
                var topStart = 0,
                    topEnd = indexData.indexOf('<!-- index begin -->');
                var contentStart = indexData.indexOf('<!-- index begin -->'),
                    contentEnd = indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;
                var bottomStart = indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;

                var top = indexData.slice(topStart, topEnd),
                    content = indexData.slice(contentStart, contentEnd),
                    bottom = indexData.slice(bottomStart);
            }
            console.log(top+header+content+footer+bottom);
        })
    })
})

執(zhí)行代碼就可以得到:

最終,結(jié)合section1,handle.js要完成:

  1. 先將app文件夾的目錄結(jié)構(gòu)復(fù)制到public文佳夾下
  2. 對(duì)文件切割完畢,重組合,再寫入public文件夾下的index.html中

基礎(chǔ)版最終代碼如下:

travelSync('./app', function(pathname, fileType) {
    if(pathname && fileType === 'file') {
        // app/header.html
        var publicPath = pathname.replace('app', 'public');
        copy(pathname, publicPath);
    } else if (pathname && fileType === 'dir') {
        // app/css
        var publicPath = pathname.replace('app', 'public');
        copyDir(publicPath)
    }

    fs.readFile('./app/header.html', function(err, html){
        if(!err) {
            var headerData = html.toString();
            // sliceStart只需要找到字符串的索引位置即可
            var sliceStart = headerData.indexOf('<!-- header begin -->');
            // sliceEnd找到字符串的位置后,加上字符串本身的長(zhǎng)度再加2是一個(gè)換行符的
            var sliceEnd = headerData.indexOf('<!-- header end -->') + '<!-- header end -->'.length+2;
            var header = headerData.slice(sliceStart, sliceEnd);
        }

        fs.readFile('./app/footer.html', function(err, html){
            if(!err) {
                var footerData = html.toString();
                // sliceStart只需要找到字符串的索引位置即可
                var sliceStart = footerData.indexOf('<!-- footer begin -->');
                // sliceEnd找到字符串的位置后,加上字符串本身的長(zhǎng)度再加2是一個(gè)換行符的
                var sliceEnd = footerData.indexOf('<!-- footer end -->') + '<!-- footer end -->'.length+2;
                var footer = footerData.slice(sliceStart, sliceEnd);
            }
            
            fs.readFile('./app/index.html', function(err, html) {
                if(!err) {
                    var indexData = html.toString();
                    var topStart = 0,
                        topEnd = indexData.indexOf('<!-- index begin -->');
                    var contentStart = indexData.indexOf('<!-- index begin -->'),
                        contentEnd = indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;
                    var bottomStart = indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;

                    var top = indexData.slice(topStart, topEnd),
                        content = indexData.slice(contentStart, contentEnd),
                        bottom = indexData.slice(bottomStart);
                }
                var indexChunk = top+header+content+footer+bottom;
                fs.writeFile('./public/index.html', indexChunk, function(err) {
                    if(!err) console.log('文件處理成功!')
                })
            })
        })
    })
})

再次在命令行里執(zhí)行:node handle.js
終端輸出:

再打開public/index.html,就變成了:

這里node命令是異步處理,所以出現(xiàn)代碼嵌套非常多,稍微改動(dòng)就成了傳說中的回調(diào)地獄~

后續(xù)更新promise優(yōu)化版。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,781評(píng)論 25 709
  • 原文鏈接:http://www.itdecent.cn/p/2a9367afe9e7 1510997059(1)....
    懸筆e絕閱讀 5,673評(píng)論 0 0
  • 今天是正月初六,昨晚下定決心準(zhǔn)備去唐三彩上班,約好了初八面試,兩班倒制,剛好有時(shí)間可以去練車,順便學(xué)習(xí)化妝,這個(gè)經(jīng)...
    趙小仙Rose閱讀 432評(píng)論 0 0
  • 我是耳朵郭。愛好寫東西的文盲。 中午坐在工作的地方,我托著腮幫子,盯著放在桌子上黑屏的手機(jī)發(fā)呆。一整夜不睡帶來了強(qiáng)...
    耳朵郭閱讀 587評(píng)論 0 1

友情鏈接更多精彩內(nèi)容