使用express和node.js将公用文件同步到不同项目中

一、需求背景

有多个独立项目需要使用相同的组件(上传组件、省市区选择组件等),我们可以创建一个父项目,并在父项目中创建多个子项目,将公用组件封装在父项目中,并监听公用组件是否发生变化,同步到子项目中,实现只封装一次组件,即可在多个项目中使用的需求。

监听文件变化

要实现将文件同步至多个项目中,我们可以使用chokidar监听父项目中组件文件夹中的文件与文件目录是否发生了改变,并通过使用expressnode.js将发生改变的文件同步到子项目中。

二、chokidar介绍

chokidar是一个NPM库,可以通过监听文件或者目录的变化,并在这些变化发生时触发回调函数。

使用方法

javascript 复制代码
const chokidar = require("chokidar");
const watcher = chokidar.watch(path); // 监听的路径(本项目为父项目的组件文件夹)

watcher
.on("change", (mpath) => {
    // 文件发生改变时触发
})
.on("add", (mpath) => {
    // 新增文件时触发
})
.on("addDir", (mpath) => {
    // 新增文件夹时触发
})
.on("unlink", (mpath) => {
    // 删除文件时触发
})
.on("unlinkDir", (mpath) => {
    // 删除文件夹时触发
})

三、使用commandar配置命令行参数

commandar是一个强大的命令行参数处理库,详细使用方法请参考文档: commander中文文档 |中文示例

vbnet 复制代码
const { program } = require("commandar");

program
.version(version)
.option("-e, --employee", "Start employee project.")
.option("-c, --client", "Start client project")
.option("-l, --landing", "Start landing project")
.parse(process.argv);

version方法

version方法添加了用于显示命令版本的处理。

option方法

option方法用于定义选项,同时可以附加选项的简介,每个选项可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(--后面接一个或者单个单词),使用逗号,空格或者" | "分隔。

parse方法

parse方法用于解析系统的命令行参数,一定不能省略,否则将会解析失败。

四、引入expressfs模块

express

基于node.js平台的 web 应用开发框架,项目中用于启动服务,以便监听文件变化和同步文件。

fs

node.js的文件系统模块,提供了丰富的函数和方法,可以进行文件的读取、写入、复制和删除等操作,同时也支持文件目录的创建、遍历和修改等操作。使用fs模块,可以在node.js环境中轻松的与文件系统进行交互。本项目中用到的函数与方法有:access/stat/writeFileSync/mkdirSync/readdir/readFileSync/existsSync/statSync/readdirSync/unlinkSync/rmdirSync

  • fs.access(path, callback):测试用户对path指定文件或目录的权限,本项目中用于判断是否存在path文件目录,不存在时创建目录。
  • fs.stat(path, callback):获取文件或目录的信息,获取到的信息存在一个fs.stats类的对象中。有以下方法:
scss 复制代码
  stats.isFile()
  stats.isDirectory()
  stats.isBlockDevice()
  stats.isCharacterDevice()
  stats.isSymbolicLink()
  stats.isFIFO()
  stats.isScoket()
  • fs.writeFileSync(path, data[, options]):同步写入内容。
makefile 复制代码
path: 被写入文件的路径
data: 要写入的内容,字符串格式,项目中通过 fs.readFileSync(src)同步读取文件内容
options: 写入文件的参数配置,默认是utf8编码
  • fs.mkdirSync(path):同步地创建目录。
  • fs.readdir(path):读取目录的内容。
  • fs.readFileSync(path):读取文件内容
  • fs.existsSync(path):判断是否存在路径,存在则返回true,否则返回false
  • fs.statSync(path):获取路径的fs.stats类,与fs.stat()作用类似。
  • fs.readdirSync(path):读取目录(文件夹)的内容。
  • fs.unlinkSync(path):同步删除目录中的文件或者符号链接,只适用于文件的删除。
  • fs.rmdirSync(path):同步删除目录中的文件夹,只适用于文件夹的删除。
  • 更多方法的详细使用方法请参考:fs | Node .js API 文档

五、流程设计

引入所需的模块

在父项目根目录创建http.js文件。

ini 复制代码
const express = require("express");
const chokidar = require('chokidar');

const {
    program
} = require('commander');

const path=require("path");
let fs = require('fs');

const app = express();
const port = 3002;

定义删除文件和复制文件的函数

scss 复制代码
// 删除文件
function delDir(path, reservePath) {
    if (fs.existsSync(path)) {
        if (fs.statSync(path).isDirectory()) { // 判断是不是文件夹
            let files = fs.readdirSync(path); // 拿到文件夹里面的内容
            
            files.forEach((file, index) => {
                let currentPath = path + "/" + file;
                
                if (fs.statSync(currentPath).isDirectory()) {
                    // 如果还是文件夹,则继续执行delDir函数
                    delDir(currentPath, reservePath);
                } else {
                    // 同步删除文件
                    fs.unlinkSync(currentPath);
                }
            });
            if (path !== reservePath) {
                fs.rmdirSync(path); // 删除原先的文件夹的目录
            }
        } else {
            fs.unlinkSync(path);
        }
    }
}


// 复制文件
function copyDir(src, dist, onError) {
    fs.access(dist, function(err){
        if(err){
            // 目录不存在时创建目录
            fs.stat(src, function(err, stat) {
                if(stat.isFile()) {
                    fs.writeFileSync(dist,'');
                }else{
                    fs.mkdirSync(dist);
                }
            })
        }
        
        _copyCall(null, src, dist, onError);
    });
}

function _copyCall(err, src, dist, onError) {
    if(err){
        onError(err);
        return;
    }

    fs.stat(src, function(err, stat) {
        if (stat.isFile()) {
            fs.writeFileSync(dist, fs.readFileSync(src));
        } else if (stat.isDirectory()){
            fs.readdir(src, function(err, paths) {
                if(err){
                    onError(err)
                } else {
                    paths.forEach(function(_path) {
                        let _src = src + '/' + _path;
                        let _dist = dist + '/' + _path;

                        fs.stat(_src, function(err, stat) {
                            if(err) {
                                onError(err);
                            } else {
                                // 判断是文件还是目录
                                if (stat.isFile()) {
                                    if (
                                        path.extname(_dist).includes('~') ||
                                        path.extname(_src).includes('~'))
                                    {
                                        return;
                                    }
                                    fs.writeFileSync(_dist, fs.readFileSync(_src));
                                } else if (stat.isDirectory()) {
                                    // 是目录时,递归复制
                                    copyDir(_src, _dist, onError)
                                }
                            }
                        })
                    })
                }
            })
        }
    })
}

设置命令行参数

arduino 复制代码
program
    .version("1.0.0")
    .option('--employee', 'Start employee project')
    .option('--client', 'Start client project')
    .option('--landing', 'Start landing project')
    .parse(process.argv);

启动服务

scss 复制代码
app.use(express.static(path.join(__dirname, 'resources'))); // 加载静态资源

app.listen(port, () => {
    const srcPath = path.resolve(__dirname, './components');
    
    let distPaths = [];

    // 判断是需要更新到哪一个项目
    if(program.employee){
        distPaths.push(path.resolve(__dirname,'./employee/_components'));
    }
    if(program.client){
        distPaths.push(path.resolve(__dirname,'./client/_components'));
    }
    if(program.landing){
        distPaths.push(path.resolve(__dirname,'./landing/_components'));
    }
    
    // 遍历文件目录,复制到对应的子项目的_components文件夹中
    distPaths.forEach(distPath => {
        copyDir(srcPath, distPath, (err) => {
            console.error(err)
        });
    })
    
    // 监听components文件夹是否发生了改变
    const watcher = chokidar.watch(srcPath)

    watcher
    .on('change', (mpath) => {
        _copy(mpath)
    })
    .on('add', (mpath) => {
        _copy(mpath)
    })
    .on('addDir', (mpath) => {
        _copy(mpath)
    })
    .on('unlink', (mpath) => {
        _del(mpath);
    })
    .on('unlinkDir', (mpath) => {
        _del(mpath);
    })

    function _copy (mpath) {
        distPaths.forEach(distPath => {
            copyDir(mpath,distPath + mpath.split('components')[1],(err) => {
                console.error(err)
            })
        })

    }

    function _del (mpath) {
        distPaths.forEach(distPath => {
            delDir(distPath+mpath.split('components')[1]);
        })
    }

});

package.json中配置指令

json 复制代码
"scripts": {
  "start:employee": "node http.js --employee",
  "start:client": "node http.js --client",
  "start:landing": "node http.js --landing",
  "start": "node http.js --client --employee --landing"
},
相关推荐
j喬乔36 分钟前
Node导入不了命名函数?记一次Bug的探索
typescript·node.js
z千鑫2 小时前
【前端】入门指南:Vue中使用Node.js进行数据库CRUD操作的详细步骤
前端·vue.js·node.js
小马哥编程7 小时前
原型链(Prototype Chain)入门
css·vue.js·chrome·node.js·原型模式·chrome devtools
蜜獾云14 小时前
npm淘宝镜像
前端·npm·node.js
dz88i814 小时前
修改npm镜像源
前端·npm·node.js
CodeChampion20 小时前
61.基于SpringBoot + Vue实现的前后端分离-在线动漫信息平台(项目+论文)
java·vue.js·spring boot·后端·node.js·maven·idea
小王码农记20 小时前
解决npm publish发布包后拉取时一直提示 Couldn‘t find any versions for “包名“ that matches “版本号“
前端·npm·node.js
求知若饥1 天前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
理想不理想v2 天前
webpack最基础的配置
前端·webpack·node.js