使用node.js開發(fā)前端打包程序
我們?cè)谧銮岸碎_發(fā)的時(shí)候經(jīng)常會(huì)在部署上線的時(shí)候做程序的打包和合并,我們接下來就會(huì)對(duì)如何使用 node.js 開發(fā)前端打包程序做非常深入的講解,希望能夠幫到有需要的同學(xué)。
我們現(xiàn)在做前端開發(fā)更多的是多人共同協(xié)作開發(fā),每個(gè)人負(fù)責(zé)不同的模塊,便于開發(fā)和調(diào)試。這樣就導(dǎo)致我們***部署上線的時(shí)候需要把所有人開發(fā)的模塊進(jìn)行合并,生成單個(gè)或多個(gè)文件上線。如果手動(dòng)合并的話肯定是費(fèi)時(shí)又費(fèi)力,而且非常容易出錯(cuò),所以我們一般都是通過一些工具來實(shí)現(xiàn)自動(dòng)合并的功能。
打包程序的原理非常簡(jiǎn)單,入口文件->尋找依賴關(guān)系->替換依賴關(guān)系->生成文件,其中中間的兩個(gè)步驟是遞歸執(zhí)行的。
我們先來看一下使用 node.js 如何完成一個(gè)簡(jiǎn)單的文件合并功能:
- // 打包文件內(nèi)容
 - var contentList = [];
 - // 排重列表
 - var loadedFileList = {};
 - // 打包主程序
 - function combine(filePath){
 - // 這里獲取入口文件的內(nèi)容
 - var fileContent = fs.readFileSync(filePath);
 - // 遍歷文件內(nèi)容
 - fileContent.forEach(function(value){
 - // 這里的findImport是需要你來實(shí)現(xiàn)的方法,用正則來匹配依賴關(guān)系
 - var matchFile = findImport(value);
 - if(matchFile){
 - //如果匹配到依賴關(guān)系
 - If(!loadedFileList[matchFile]){
 - //如果依賴關(guān)系不在排重列表中,遞歸調(diào)用combine
 - combine(matchFile);
 - contentList.push(‘\n’);
 - }
 - }else{
 - contentList.push(value);
 - }
 - });
 - }
 
***只要根據(jù) contentList 里面的內(nèi)容來生成文件就可以了,怎么樣,是不是很簡(jiǎn)單呢?下面我們就要介紹另外一種方式,使用流來完成我們的打包程序。
在 node.js 中,流(Stream)是一個(gè)由不同對(duì)象實(shí)現(xiàn)的抽象接口。流可以是可讀的、可寫的、或者既可讀又可寫的。所有的流都是 EventEmitter 的實(shí)例。我們可以通過繼承接口來構(gòu)造我們自己所需要的流。在我們的打包程序里面需要兩個(gè)流,一個(gè)負(fù)責(zé)按行輸出文件內(nèi)容,另外一個(gè)負(fù)責(zé)處理依賴關(guān)系。所有的文件內(nèi)容都在這兩個(gè)流里面循環(huán)流動(dòng),當(dāng)所有的依賴關(guān)系都處理完畢之后就結(jié)束流動(dòng)并生成對(duì)應(yīng)的文件,這樣就達(dá)到我們的目的了。
讓我們先來看一下負(fù)責(zé)按行輸出文件內(nèi)容的流是怎么樣的:
- var Stream = require('stream').Stream,
 - util = require('util'),
 - path = require('path'),
 - fs = require('fs');
 - // 構(gòu)造函數(shù)
 - function LineStream() {
 - this.writable = true;
 - this.readable = true;
 - this.buffer = '';
 - }
 - module.exports = LineStream;
 - // 繼承流接口
 - util.inherits(LineStream, Stream);
 - // 重寫write方法,所有pipe過來的數(shù)據(jù)都會(huì)調(diào)用此方法
 - LineStream.prototype.write = function(data, encoding) {
 - var that = this;
 - // 把buffer轉(zhuǎn)換為string類型
 - if (Buffer.isBuffer(data)) {
 - data = data.toString(encoding || 'utf8');
 - }
 - var parts = data.split(/\n/g);
 - // 如果有上一次的buffer存在就添加到最前面
 - if (this.buffer.length > 0) {
 - parts[0] = this.buffer + parts[0];
 - }
 - // 遍歷并發(fā)送數(shù)據(jù)
 - for (var i = 0; i < parts.length - 1; i++) {
 - this.emit('data', parts[i]);
 - }
 - // 把***一行數(shù)據(jù)保存到buffer,使傳遞過來的數(shù)據(jù)保持連續(xù)和完整。
 - this.buffer = parts[parts.length - 1];
 - };
 - // end方法,在流結(jié)束時(shí)調(diào)用
 - LineStream.prototype.end = function() {
 - // 如果還有buffer,發(fā)送出去
 - if(this.buffer.length > 0){
 - this.emit('data',this.buffer);
 - this.buffer = '';
 - }
 - this.emit('end');
 - };
 
這樣我們的 lineStream 就完成了,我們看到在 write 方法里面就做了一件事,分解傳遞過來的數(shù)據(jù)并按行發(fā)送出去,然后我們看下處理依賴關(guān)系的流 DepsStream。
- var stream = require('stream').Stream;
 - var util = require('util');
 - var fs = require('fs');
 - var path = require('path');
 - module.exports = DepsStream;
 - util.inherits(DepsStream,stream);
 - function DepsStream(){
 - this.writable = true;
 - this.readable = true;
 - this.buffer = '';
 - this.depsList = [];
 - };
 - // 這里的write方法只發(fā)送數(shù)據(jù),不對(duì)數(shù)據(jù)做任何的處理
 - DepsStream.prototype.write = function(data){
 - this.emit('data',data);
 - };
 - // 我們?cè)谶@里重新pipe方法,使其能夠處理依賴關(guān)系和生成最終文件
 - DepsStream.prototype.pipe = function(dest,opt){
 - var that = this;
 - function ondata(chunk){
 - var matches = findImport(chunk);
 - if(matches){
 - if(this.depsList.indexOf(matches) >= 0){
 - // 我們?cè)谶@里把處理過后的數(shù)據(jù)pipe回lineStream
 - dest.write('\n');
 - }else{
 - this.depsList.push(matches);
 - var code = getFileContent(matches);
 - // 我們?cè)谶@里把處理過后的數(shù)據(jù)pipe回lineStream
 - dest.write('\n' + code);
 - }
 - }else{
 - this.buffer += chunk + '\n';
 - }
 - }
 - function onend(){
 - // 生成最終文件
 - var code = this.buffer;
 - fs.writeFileSync(filePublishUrl,code);
 - console.log(filePublishUrl + ' combine done.');
 - }
 - // 監(jiān)聽end事件
 - that.on('end',onend);
 - // 監(jiān)聽data事件
 - that.on('data',ondata);
 - };
 - // end方法
 - DepsStream.prototype.end = function(){
 - this.emit('end');
 - };
 
我們看到上面的程序里面我們?cè)?pipe 方法里面監(jiān)聽了 end 事件和 data 事件,ondata 方法主要用來對(duì)數(shù)據(jù)進(jìn)行處理,發(fā)現(xiàn)有依賴關(guān)系的話就獲取對(duì)應(yīng)依賴關(guān)系的文件并重新發(fā)回給 LineStream 進(jìn)行處理。onend 方法用來生成最終的文件,我們來看一下最終的調(diào)用方法:
- var fileStream = fs.createReadStream(filepath);
 - var lineStream = new LineStream();
 - var depsStream = new DepsStream();
 - fileStream.pipe(lineStream);
 - lineStream.pipe(depsStream);
 - depsStream.pipe(lineStream);
 
怎么樣,是不是非常簡(jiǎn)單,感興趣的同學(xué)趕快嘗試一下吧。















 
 
 






 
 
 
 