我们在做前端开发的时候经常会在部署上线的时候做程序的打包和合并,我们接下来就会对如何使用 node.js 开发前端打包程序做非常深入的讲解,希望能够帮到有需要的同学。
我们现在做前端开发更多的是多人共同协作开发,每个人负责不同的模块,便于开发和调试。这样就导致我们***部署上线的时候需要把所有人开发的模块进行合并,生成单个或多个文件上线。如果手动合并的话肯定是费时又费力,而且非常容易出错,所以我们一般都是通过一些工具来实现自动合并的功能。
打包程序的原理非常简单,入口文件->寻找依赖关系->替换依赖关系->生成文件,其中中间的两个步骤是递归执行的。
我们先来看一下使用 node.js 如何完成一个简单的文件合并功能:
- // 打包文件内容
- var contentList = [];
- // 排重列表
- var loadedFileList = {};
- // 打包主程序
- function combine(filePath){
- // 这里获取入口文件的内容
- var fileContent = fs.readFileSync(filePath);
- // 遍历文件内容
- fileContent.forEach(function(value){
- // 这里的findImport是需要你来实现的方法,用正则来匹配依赖关系
- var matchFile = findImport(value);
- if(matchFile){
- //如果匹配到依赖关系
- If(!loadedFileList[matchFile]){
- //如果依赖关系不在排重列表中,递归调用combine
- combine(matchFile);
- contentList.push(‘\n’);
- }
- }else{
- contentList.push(value);
- }
- });
- }
***只要根据 contentList 里面的内容来生成文件就可以了,怎么样,是不是很简单呢?下面我们就要介绍另外一种方式,使用流来完成我们的打包程序。
在 node.js 中,流(Stream)是一个由不同对象实现的抽象接口。流可以是可读的、可写的、或者既可读又可写的。所有的流都是 EventEmitter 的实例。我们可以通过继承接口来构造我们自己所需要的流。在我们的打包程序里面需要两个流,一个负责按行输出文件内容,另外一个负责处理依赖关系。所有的文件内容都在这两个流里面循环流动,当所有的依赖关系都处理完毕之后就结束流动并生成对应的文件,这样就达到我们的目的了。
让我们先来看一下负责按行输出文件内容的流是怎么样的:
- var Stream = require('stream').Stream,
- util = require('util'),
- path = require('path'),
- fs = require('fs');
- // 构造函数
- function LineStream() {
- this.writable = true;
- this.readable = true;
- this.buffer = '';
- }
- module.exports = LineStream;
- // 继承流接口
- util.inherits(LineStream, Stream);
- // 重写write方法,所有pipe过来的数据都会调用此方法
- LineStream.prototype.write = function(data, encoding) {
- var that = this;
- // 把buffer转换为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];
- }
- // 遍历并发送数据
- for (var i = 0; i < parts.length - 1; i++) {
- this.emit('data', parts[i]);
- }
- // 把***一行数据保存到buffer,使传递过来的数据保持连续和完整。
- this.buffer = parts[parts.length - 1];
- };
- // end方法,在流结束时调用
- LineStream.prototype.end = function() {
- // 如果还有buffer,发送出去
- if(this.buffer.length > 0){
- this.emit('data',this.buffer);
- this.buffer = '';
- }
- this.emit('end');
- };
这样我们的 lineStream 就完成了,我们看到在 write 方法里面就做了一件事,分解传递过来的数据并按行发送出去,然后我们看下处理依赖关系的流 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方法只发送数据,不对数据做任何的处理
- DepsStream.prototype.write = function(data){
- this.emit('data',data);
- };
- // 我们在这里重新pipe方法,使其能够处理依赖关系和生成最终文件
- DepsStream.prototype.pipe = function(dest,opt){
- var that = this;
- function ondata(chunk){
- var matches = findImport(chunk);
- if(matches){
- if(this.depsList.indexOf(matches) >= 0){
- // 我们在这里把处理过后的数据pipe回lineStream
- dest.write('\n');
- }else{
- this.depsList.push(matches);
- var code = getFileContent(matches);
- // 我们在这里把处理过后的数据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.');
- }
- // 监听end事件
- that.on('end',onend);
- // 监听data事件
- that.on('data',ondata);
- };
- // end方法
- DepsStream.prototype.end = function(){
- this.emit('end');
- };
我们看到上面的程序里面我们在 pipe 方法里面监听了 end 事件和 data 事件,ondata 方法主要用来对数据进行处理,发现有依赖关系的话就获取对应依赖关系的文件并重新发回给 LineStream 进行处理。onend 方法用来生成最终的文件,我们来看一下最终的调用方法:
- var fileStream = fs.createReadStream(filepath);
- var lineStream = new LineStream();
- var depsStream = new DepsStream();
- fileStream.pipe(lineStream);
- lineStream.pipe(depsStream);
- depsStream.pipe(lineStream);
怎么样,是不是非常简单,感兴趣的同学赶快尝试一下吧。