最近接手一個(gè)任務(wù)析蝴,給一個(gè)集團(tuán)寫(xiě)一個(gè)靜態(tài)站點(diǎn),看了設(shè)計(jì)稿發(fā)現(xiàn)除了首頁(yè)绿淋,其他頁(yè)面的頭部nav和底部footer都是一樣的闷畸。如果寫(xiě)好粘貼賦值粘貼復(fù)制,多累巴讨汀佑菩!————于是想到一個(gè)需求:
如何能讓頁(yè)面公用部分寫(xiě)一邊,然后所有頁(yè)面都能同步裁赠?
作為前端出身殿漠,技術(shù)棧又比較少~ 第一個(gè)想到的就是nodeJS了!畢竟JS是前端特有唯一一門(mén)編程語(yǔ)言了组贺!
首先凸舵,屢屢思路:
- 目的是:公用部分單獨(dú)寫(xiě)一份,然后頁(yè)面只需要寫(xiě)除了公用部分之外的即可失尖。
- 前提是:如果出現(xiàn)異常啊奄,不能影響自己寫(xiě)的代碼->所以要另外建立一個(gè)文件夾,用于存放node處理后的代碼
接下來(lái)掀潮,開(kāi)始node編寫(xiě):
<strong>section 1</strong>
一菇夸、 首先在新建文件夾下創(chuàng)建兩個(gè)文件夾:
app(寫(xiě)代碼的地方)
public(生成代碼的地方)
handile.js(nodeJS文件)
目錄結(jié)構(gòu)如下:
二、 做到將app里的文件復(fù)制到public中
解析功能
//復(fù)制文件用到node的fs模塊仪吧,用到readFile,writeFile功能庄新,然而如果用這兩個(gè)語(yǔ)法,它的原理是將文件內(nèi)容讀取保存在內(nèi)存中薯鼠,然后再一次性寫(xiě)入一個(gè)文件
//想想择诈,這樣就會(huì)遇到一個(gè)問(wèn)題:如果文件比較大,一次性讀取和寫(xiě)入出皇,會(huì)不會(huì)很吃?xún)?nèi)存羞芍。
//就像是這里要從水龍頭取水,將旁邊一個(gè)無(wú)法移動(dòng)的大缸接滿(mǎn)郊艘,如果選擇一次性接一缸水荷科,再倒到水缸里唯咬,這顯然是不明智的
//考慮這一點(diǎn),這里要用Stream流信息處理方式畏浆,就是讀著寫(xiě)著胆胰,像有水管一樣,上面流著刻获,下面接著蜀涨,這樣可行性就更高了,代碼如下:
var fs = require('fs');
function copy(origin, aim) {
fs.createReadStream(origin).pipe(fs.createWriteStream(aim));
}
//這里pipe方法就是這里舉例中的水管
現(xiàn)在有了一個(gè)能連通數(shù)據(jù)流的水管了,這里要將水管入口接到水龍頭上将鸵,出口接到大缸里
但現(xiàn)在這里還遇到一個(gè)問(wèn)題勉盅,app文件夾下是可能會(huì)有img css js等文件夾的,而這里的方法createReadStream只能讀取指定的一個(gè)文件顶掉,
<strong>所以這里要有一個(gè)遍歷app和hbuild目錄的方法草娜,把a(bǔ)pp目錄里所有文件的路徑遍歷出來(lái),給copy方法的origin參數(shù)痒筒,再將app目錄里文件的路徑名字中app替換為hbuild宰闰,傳給copy方法的aim參數(shù),這樣如果app文件夾下有多個(gè)文件簿透,就相當(dāng)于這里創(chuàng)造了多條水管移袍,每個(gè)水管只負(fù)責(zé)一個(gè)文件信息流的傳輸</strong>
這里要用到readdir方法,這里選擇同步讀取老充,保證開(kāi)水龍頭葡盗,水管傳輸,水進(jìn)入水缸的正確順序啡浊,這樣就不用promise來(lái)單獨(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)行遍歷和寫(xiě)入
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è)目錄和文件來(lái)做測(cè)試
header index footer 三個(gè)html文件里分別寫(xiě):
<!-- 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
然后再打開(kāi)public文件夾坝茎,看下涤姊,發(fā)現(xiàn):
哎!what嗤放? 為什么css img js文件夾和里面的文件沒(méi)有復(fù)制過(guò)來(lái)砂轻?
這是因?yàn)檫@里在遍歷目錄的時(shí)候,copy方法只實(shí)現(xiàn)了文件流信息的傳輸斤吐,但沒(méi)有實(shí)現(xiàn)文件夾創(chuàng)建功能搔涝,所以這樣運(yùn)行會(huì)出現(xiàn)報(bào)錯(cuò):
那么接下來(lái)就要寫(xiě)cipyDir方法:
function copyDir(path) {
fs.mkdir(path, function(err) { //mkdir方法創(chuàng)建文件夾只需要通過(guò)第一個(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ù)文件類(lèi)型的第二個(gè)參數(shù)'file'
}
});
}
接下來(lái)擼一遍現(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ù)制功能是否能解決剛才無(wú)法復(fù)制文件夾的問(wèn)題:命令行運(yùn)行:node handle.js诬留,打印出:
再看public文件夾目錄:
漂亮!現(xiàn)在復(fù)制功能已經(jīng)達(dá)到贫母。敲代碼的文兑,就是需要做好鋪墊,才能快速行動(dòng)~ 接下來(lái)進(jìn)入關(guān)鍵時(shí)刻腺劣,處理文件的公用部分
<strong>section 2</strong>
這里模擬測(cè)試將app里的header footer 整合到index.html的 頭 和 尾绿贞。 現(xiàn)在要用到readFile 和 writeFile語(yǔ)法,讀取header footer里指定的內(nèi)容橘原,再寫(xiě)入index指定位置籍铁。
這里先想一個(gè)問(wèn)題:如果文件比較大,內(nèi)容比較復(fù)雜趾断,如何才能準(zhǔn)確的找到header footer里面指定的代碼段拒名, 然后準(zhǔn)確的寫(xiě)入index指定的位置?先別看下面解決方案芋酌,兩分鐘增显,看自己能想出哪些方法
我暫想到相對(duì)好用的方法是:
- 在header footer文件需要截取的代碼段頭尾添加一個(gè)標(biāo)記行注釋標(biāo)簽,同樣脐帝,在index里面也放一個(gè)注釋標(biāo)簽同云,告訴handle.js準(zhǔn)確位置
也有一個(gè)不好用的:就是直接判斷代碼段開(kāi)始標(biāo)簽,和結(jié)束標(biāo)簽腮恩,這樣每次都要嚴(yán)格查詢(xún)代碼并做更改梢杭,其實(shí)挺麻煩的。
這里修改header index footer秸滴,添加標(biāo)記注釋?zhuān)謩e更改為:
<!-- 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 -->
接下來(lái)在heandle.js里寫(xiě)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ù)變量全释,再通過(guò)函數(shù)調(diào)用,以參數(shù)的形式傳進(jìn)去误债,可以運(yùn)行下上面代碼浸船,即可得到footer代碼段妄迁,測(cè)試上面代碼即可得到:
現(xiàn)在能找到需要截取的代碼段,接下來(lái)要進(jìn)行代碼段的準(zhǔn)確插入:
其實(shí)插入代碼段的基礎(chǔ)原理李命,就是將目標(biāo)文件信息讀取出來(lái)登淘,分為三段:
- 插入位置之前的代碼段 top
- 插入header和footer之間的代碼段 content
- 插入footer之后的代碼段 bottom
然后只需要將代碼段拼接成 top + header + content + footer + bottom 這么一個(gè)完整的代碼塊,再寫(xiě)入目標(biāo)文件就完成了封字。
接下來(lái)對(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)方法來(lái)切割的。那么index的三段一樣要找到top content bottom三者的開(kāi)始位置和結(jié)束位置笆制。這里先默認(rèn)index文件的內(nèi)容被讀取為:indexData绅这,那么:
1. top:是從文檔的開(kāi)始,開(kāi)始位置就是索引值0项贺,
2. 結(jié)束索引值就是index begin標(biāo)記的開(kāi)始君躺,即indexData.indexOf('<!-- index begin -->');
3. content:開(kāi)始索引值是indexData.indexOf('<!-- index begin -->');
4. 結(jié)束索引值是:indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2;
5. bottom:開(kāi)始索引值是indexData.indexOf('<!-- index end -->')+'<!-- index end -->'.length+2,
6. bottom可以直接切到文檔結(jié)尾,slice方法如果不傳入第二個(gè)參數(shù)的話(huà)就默認(rèn)直接從指定索引位置切割到結(jié)尾
這樣就可以寫(xiě)出函數(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开缎,接下來(lái)要做拼接并的功能棕叫。
將我們的代碼組裝起來(lái)成為:
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要完成:
- 先將app文件夾的目錄結(jié)構(gòu)復(fù)制到public文佳夾下
- 對(duì)文件切割完畢完残,重組合伏钠,再寫(xiě)入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
終端輸出:
再打開(kāi)public/index.html,就變成了:
這里node命令是異步處理扎拣,所以出現(xiàn)代碼嵌套非常多赴肚,稍微改動(dòng)就成了傳說(shuō)中的回調(diào)地獄~
后續(xù)更新promise優(yōu)化版。