node针对文件以及文件夹的相关操作

基于一个需求引发的一篇node文件基础操作笔记

Posted by Jerry on March 7, 2019

需求来源

后端团队写了一套服务端的API文档,但由于技术选型上的原因,产生了如下结构:

- demo/
  + img/                    # 文档图片目录
  index.html                # 总文档
  app.html                  # 子文档1
  bean.html                 # 子文档2
  file.md                   # 子文档3
  queryData.html            # 子文档4
  share.html                # 子文档5
  ...                       # 子文档6,7,8,9,10,11.....

总文档和子文档中都有对应的目录和内容,需要做一个自动化的操作,具体如下:

  • 将总文档的目录树和相应子文档的内容拼接,从新生成子文档的页面
  • 处理目录树链接,使得文档页面可以互相跳转

最终生成如下目录:

- dist/
  + img/                    # 文档图片目录
  app.html                  # 子文档1
  bean.html                 # 子文档2
  file.md                   # 子文档3
  queryData.html            # 子文档4
  share.html                # 子文档5
  ...                       # 子文档6,7,8,9,10,11.....

最终代码

app.js

const path = require('path');
const fs  = require('fs-extra');
const cheerio = require('cheerio');

const resourcePath = __dirname + '/demo/';
const buildPath = __dirname + '/dist/';
const mapPath = path.resolve('./reference.json');
const doc = {};

if(!fs.existsSync(buildPath)){
	fs.mkdirSync(buildPath);
	fs.mkdirSync(buildPath + 'images');
}

doc.map = JSON.parse(fs.readFileSync(mapPath).toString());
$ = cheerio.load(fs.readFileSync(resourcePath + 'index.html').toString());

$('.sectlevel1').children().each((i,el)=>{
	let _val = $(el).children('a').text();
	for (var key in doc.map) {  
            if(doc.map[key] == _val) {
            	$(el).children('a').attr('href',`${key}.html`);
            	$(el).children('ul').find('a').each((j,ele)=>{
            		let _hash = $(ele).attr('href');
            		$(ele).attr('href',`${key}.html${_hash}`);
            	});
            }
        }
})

fs.readdir(resourcePath,(err,files)=>{
	if(err){
        console.warn(err)
    }else{
        files.forEach(file=>{
            if(fs.statSync(resourcePath + file).isFile()){
                if(file !== 'index.html'){
                    let html = fs.readFileSync(resourcePath + file).toString();
                    _ = cheerio.load(html);
                    let content = _('#content');
                    $('#content').replaceWith(content);
                    $('#footer').remove();
                    $('#content').prepend('<h2>' + matchTitle(path.basename(file,".html")) + '</h2>');
                    fs.writeFile(buildPath + file, $.html(), function(err){
                       if(err) console.log(err)
                       else console.log(file + ' ------------------success');
                    })
                }
            }
            
        })
	}
})

fs.copySync(resourcePath + 'images', buildPath + 'images');


const matchTitle = (filename)=> {
	return doc.map[filename];
}

代码分析

使用方法基本都是异步版本(Sync),避免回调地狱。

判断路径(文件/文件夹)是否存在

fs.existsSync(path)  //true:存在;false:不存在;

创建文件夹

fs.mkdirSync(path)

读取文件

fs.readFileSync(path)

读取文件夹目录

fs.readdir(path,callback)

写入文件

fs.writeFile(path,content,callback)

判断是否文件

stat方法的参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。我们往往通过该方法,判断正在处理的到底是一个文件,还是一个目录。

fs.statSync(path).isFile()

拷贝目录

copySync需要插件 fs-extra 支持

fs.copySync(copyPath,distPath)

cheerio

需求中需要针对dom解析代码来匹配相应的href,所以这里强烈推荐 cheerio 插件,和JQuery一样的API,可以大大方便在node环境下针对dom的解析。

两个不同html的代码拼接片段:

const cheerio = require('cheerio');

$ = cheerio.load(fs.readFileSync('index.html').toString());
_ = cheerio.load(fs.readFileSync('app.html').toString());

let content = _('#content');    //获取app.html下的#content内容
$('#content').replaceWith(content);     //index.html下的#content内容替换为app.html下的内容