nodejs 编译时实现html文件,引入公共文件处理

198 阅读4分钟

用nodejs的原生fs文件模块,去实现html文件,引入公共文件处理工具。assets资源文件夹存放公共文件,pages存放页面文件,页面文件里面会引用公共文件。common公共文件夹里面存放公共文件。实现了监听页面文件的监听,当页面文件发生变化时,会重新编译页面文件。资源文件改变更新时,重新拷贝资源包到dist目录。

源码下载:https://gitee.com/leolee18/html-template/

目录结构

src是源码目录,包含了pages目录,其下是所有的页面文件,里面可能会引用公共文件。common是公共文件目录,包含了header.html和footer.html等文件。 dist是资源目录,用来存放页面include标签被公共文件替换后的实际使用的页面文件资源。

 dist
 include.js
 src
    common
      footer.html
      header.html
    assets
      bg.jpg  
    pages
      index.html

index.html文件内容

<!DOCTYPE HTML>
<html>
    <head>
        <title>nodejs 制作html静态公共文件包含工具</title>
        <meta charset="utf8" />
        <style>
            html,body,div{padding:0;border:0;margin:0;box-sizing:border-box;}
            html,body{height:100%;}
            body{display:flex;flex-direction:column;}
            .center-middle{display:flex;justify-content:center;align-items:center;}
            .header{flex:0 0 50px;border-bottom:1px solid #ddd;background-color:#95cfd4;}
            .bodyer{flex:1 1 100%;background-color:#ececec;}
            .footer{flex:0 0 50px;border-top:1px solid #ddd;background-color:#7def40;}
        </style>
    </head>
    <body>
        <include src="./header.html"></include>
        <div class="bodyer center-middle">bodyer</div>
        <include src="./footer.html"></include>
    </body>
</html>

header.html文件内容

<div class="header center-middle">header</div>

footer.html文件内容

<div class="footer center-middle">footer</div>

include.js文件代码

  1. 用nodejs的fs模块的readdirSync同步文件读取函数读取pages目录下的所有文件。
  2. 遍历每个文件,正则匹配语句,读取包含于其中的src属性指定的文件路径,并用此文件内容替换掉当前所匹配的include标签。
  3. 将替换掉include标签后的新的文件内容,写入到dist资源目录,文件名跟当前pages下的文件名同名。
const $Fs = require('fs'); // 引入文件模块
let files = $Fs.readdirSync('./src/pages'); // 读取pages页面文件目录下的html文件
files.forEach((file,index) => { // 遍历获得的所有页面文件
    let old_data = $Fs.readFileSync('./src/pages/' + file,'utf8'); // 读取当前文件的内容
    let new_data = old_data.replace(/\<include.*src="(.*)">.*\<\/include\>/gi,(match,p1,offset,string) => {//查找html文件中include标签
        let inc_data = $Fs.readFileSync('./src/common/' + p1,'utf8'); // p1是第一个括号匹配到的值,也就是include包含的文件路径,读取此公共文件
        return inc_data; // 此回调函数返回的值,将替换正则匹配到的字符串
    });
    $Fs.writeFileSync('./dist/' + file,new_data); //将include标签被替换后新的html内容,写入到dist资源目录的同名file文件中(不存在会自动新建)。
});
console.log('执行成功');

node执行include.js文件

node include.js

监控文件系统变动

npm install chokidar
const chokidar = require('chokidar');

// 监听指定目录下的所有文件和子目录
const watcher = chokidar.watch('/path/to/directory', {
  ignored: /(^|[\/\\])\../, // 忽略以 . 开头的文件和目录
  persistent: true // 长期运行
});

watcher.on('add', path => console.log(`File ${path} has been added`));
watcher.on('change', path => console.log(`File ${path} has been changed`));
watcher.on('unlink', path => console.log(`File ${path} has been removed`));

node 移动静态文件夹

const fs = require('fs');
const path = require('path');
 
// 源文件夹路径
const sourceDir = path.join(__dirname, 'static');
// 目标文件夹路径
const targetDir = path.join(__dirname, 'public', 'static');
 
// 移动文件夹的函数
function moveFolder(source, destination) {
  fs.rename(source, destination, (err) => {
    if (err) throw err;
    console.log('文件夹移动成功!');
  });
}
 
// 调用函数移动文件夹
moveFolder(sourceDir, targetDir);

静态html页面服务器 http-server

npm i -g http-server
// http-server [path] [options]
//path默认指向工程路径下的./public,如果不存在那么使用./。
//options就是常见的配置,比如端口、代理地址等,常用配置

http-server ./ -o --port 8085 --proxy http://192.168.11.120:8888/
hs ./ -o -p 8085 -P http://192.168.11.120:8888/

"scripts": {
    "dev": "http-server  ./ -o --port 8089 --proxy http://192.168.11.120:8888/"
}

live-server

live-server服务器(与http-server一样,但多了代码热更新功能,只要修改了代码,不需要刷新浏览器,浏览器会自动热更新)

npm i live-server -g
// 使用方法与http-server一致
live-server --port=80

package.json

{
  "name": "html",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "http-server ./dist -o --port 8085 | node index.js",
    "build": "node index.js",
    "live": "live-server ./dist --port=8080 | node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chokidar": "^3.6.0",
    "http-server": "^14.1.1"
  }
}

index.js

const chokidar = require('chokidar');
const fs = require('fs'); // 引入文件模块
const path = require('path');

const mPath = './src/pages/';
const mCom = './src/common/';
const mDist = './dist/';
const mAsstes = './src/assets';
// 监听页面目录
const watcher = chokidar.watch(mPath, {
  ignored: /(^|[\/\\])\../, // 忽略以 . 开头的文件和目录
  persistent: true // 长期运行
});
// 监听 资源目录
const watcherAss = chokidar.watch(mAsstes, {
  ignored: /(^|[\/\\])\../, // 忽略以 . 开头的文件和目录
  persistent: true // 长期运行
});
// 更新页面文件
function updatePage(params) {
  const fileName = path.basename(params);
  const pagePath = path.join(mPath, fileName);

  let old_data = fs.readFileSync(pagePath, 'utf8');
  let new_data = old_data.replace(/\<include.*src="(.*)">.*\<\/include\>/gi, (match, p1, offset, string) => {
    const comPath = path.join(mCom, p1);
    let inc_data = fs.readFileSync(comPath, 'utf8');
    return inc_data;
  });
  const disPath = path.join(mDist, fileName);
  fs.writeFileSync(disPath, new_data);
}

//移动资源文件夹
function moveFolder() {
  const mAsName = path.basename(mAsstes);

  fs.cp(mAsstes, (mDist + mAsName), { recursive: true }, (err) => {
    if (err) {
      // console.error(err);
    }
  });
}

//清空dist文件夹
function deleteFolderRecursive(mPath) {
  const folderPath = path.join(mPath);
  if (fs.existsSync(folderPath)) {
    // 递归删除文件夹
    fs.rmdirSync(folderPath, { recursive: true }, (error) => {
      if (error) {
        // return console.error(`无法删除文件夹: ${error}`);
      }
    });
  }
}
// 删除文件
function deleteFile(params) {
  const fileName = path.basename(params);
  const pagePath = path.join(mDist, fileName);
  if (fs.existsSync(pagePath)) {
    fs.unlinkSync(pagePath);
  }
}
// 创建目录
function mkdir(filePath) {
  const arr = filePath.split('/');
  let dir = arr[0];
  for (let i = 1; i < arr.length; i++) {
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir);
    }
    dir = dir + '/' + arr[i];
  }
}
// 编译所有页面
function updatePageAll() {
  // 删除文件夹下的所有文件和文件夹
  deleteFolderRecursive(mDist);

  if (!fs.existsSync(mDist)) {
    mkdir(mDist);
  }

  let files = fs.readdirSync(mPath); // 读取pages页面文件目录下的html文件
  files.forEach((file, index) => { // 遍历获得的所有页面文件
    let old_data = fs.readFileSync(mPath + file, 'utf8'); // 读取当前文件的内容
    let new_data = old_data.replace(/\<include.*src="(.*)">.*\<\/include\>/gi, (match, p1, offset, string) => {//查找html文件中include标签
      let inc_data = fs.readFileSync(mCom + p1, 'utf8'); // p1是第一个括号匹配到的值,也就是include包含的文件路径,读取此公共文件
      return inc_data; // 此回调函数返回的值,将替换正则匹配到的字符串
    });
    fs.writeFileSync(mDist + file, new_data); //将include标签被替换后新的html内容,写入到dist资源目录的同名file文件中(不存在会自动新建)。
  });

  // 移动资源文件夹
  moveFolder();
}
// 防抖函数
function debounce(fn, t, path) {
  let timer;
  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(function () {
      fn(path);
    }, t);
  }
}
// 监听页面改变
const mCatch = {};
let mAllFile;
watcher.on('add', path => {
  if (!mAllFile) {
    mAllFile = debounce(updatePageAll, 1000, path);
  }
  mAllFile();
  // console.log(`File ${path} has been added`)
});
watcher.on('change', path => {
  if (!mCatch[path]) {
    mCatch[path] = debounce(updatePage, 1000, path);
  }
  mCatch[path]();
  // console.log(`File ${path} has been changed`)
});
watcher.on('unlink', path => {
  deleteFile(path);
  // console.log(`File ${path} has been removed`)
});

//监听资源改变
let assFile;
watcherAss.on('change', path => {
  if (!assFile) {
    assFile = debounce(moveFolder, 1000, path);
  }
  assFile();
});
watcherAss.on('unlink', path => {
  if (!assFile) {
    assFile = debounce(moveFolder, 1000, path);
  }
  assFile();
});

扩展

Node.js + gulp 合并静态页模版,文件更新自动热重载。浏览器可预览目录下的页面。使用的是ejs的语法.其实你用什么文件后缀都可以,都是按ejs来解析

// 项目地址:https://gitcode.com/Liaozhenting/template/overview