乐趣区

记一次mpa多页面应用处理

起因

由于国内的搜索引擎对单页面应用支持不友好,所以一般网站的网站做的是多页面应用

选择

做网站当然是世界最好的语言 PHP 啦,开始也是想这样做的,但是在写这篇文章的时候,自己是一枚前端开发,
考虑到可维护性,其他的前端未必能看懂 PHP 代码,所以还是在 nodejs 方面选型,
nodejs 有名的 express 我觉得挺合适,但是最终部署的生产环境是虚拟主机,不支持 node 环境,-_-||
所以只能想办法生成多个静态 html 文件,也就是网站静态化
基于以上种种考虑,最终选择用 express 开发,最终生成静态页面

准备

1. 新建项目文件夹mpa,运行npm init,该填的填写,然后一路回车,得到 package.json

2. 安装 express,npm i express --save

3. 安装 ejs,npm i ejs --save
ejs是一个模板引擎,因为 express 默认的模板引擎是 jade,jade 与 html 语法相差较大,
所以我们要安装 ejs,ejs 可以认为就是 html 语言 +js 混编

4. 安装 supervisor,npm i supervisor --save-dev
nodejs 的 supervisor 是一个热部署工具,直接运行 express 项目只会监听模板文件的修改,而 js 文件的修改需要停止
再启动才能生效,使用 supervisor 启动它会监听所有文件的修改,一旦有文件修改,立马重启,从而实现热部署

配置目录

项目需要包含路由,国际化,模板,静态文件,布局文件,所以目录设置如下:

|-langs     // 国际化文件夹
    |-zh_CN.js
    |-en_US.js
|-layouts   // 布局模板文件夹
    |-header.html
    |-footer.html
|-public    // 静态资源文件夹
    |-static
        |-css
        |-js
        |-img
        |-vendor
|-views     // 内容模板文件夹
    |-*.html
|-index.js  // 主启动程序
|-build.js  // 打包成静态文件程序
|-tools.js  // 自定义函数工具文件

主启动程序

在 index.js 编写代码

const express = require('express')
const fs = require('fs')
const path = require('path')
const app = express()
var ejs = require('ejs');
const tools = require('./tools')

app.engine('html', ejs.__express);  //  配置模板引擎
app.set('view engine', 'html')
app.use(express.static(path.join(__dirname, 'public')));

// 配置
var CONFIG = {
    port: 100,
    lang: 'zh_CN'
}
var langs =  require('./langs/'+CONFIG.lang);

// 中间件
var setLang = (req, res, next) => {  // 根据 get 参数加载对应国际化文件
  if (req.query.lang) {
      CONFIG.lang = req.query.lang
      langs =  require('./langs/'+CONFIG.lang);
  } else {langs =  require('./langs/zh_CN');
  }
  console.log(req.url +' '+ (new Date()))
  next()}
app.use(setLang)

fs.readdirSync(path.join(__dirname, 'views')).map(file=>{  // 遍历 views 文件夹下模板文件,根据模板文件名称生成对应路由
    // 路由
    let route = file.substring(0,file.lastIndexOf('.'))
    if (route==='index') {app.get('/', (req, res) => {                        // 处理 / 和 index 首页路由,代码几乎一样,这块可以优化
          res.render(file, {...langs[route],header:langs['header'],footer:langs['footer'],url:tools.url(langs.lang)})   // 传递必要参数
        })
    }
    app.get('/'+route, (req, res) => {res.render(file, {...langs[route],header:langs['header'],footer:langs['footer'],url:tools.url(langs.lang)})
    })
    console.log(file.substring(0,file.lastIndexOf('.')))
})


// 服务启动
app.listen(CONFIG.port, () => console.log(`app listening on port ${CONFIG.port}!`))

打包程序

打包的步骤如下

1. 遍历 langs 文件,有多少个国际化文件就生成几个国际化文件夹

2. 遍历 views 文件,根据国际化文件渲染模板,输出 html 文件到对应国际化文件夹

3.copy 静态文件到打包目录

var ejs = require('ejs');
var fs = require('fs');
var path = require('path');// 解析需要遍历的文件夹
const tools = require('./tools')

var distType = 1
if (global.process.env.npm_config_argv) {let npmConfig = JSON.parse(global.process.env.npm_config_argv)
    if (npmConfig['original'][2] && npmConfig['original'][2]==='t2') {distType = 2;}
}

function delDir(path){let files = [];
    if(fs.existsSync(path)){files = fs.readdirSync(path);
        files.forEach((file, index) => {
            let curPath = path + "/" + file;
            if(fs.statSync(curPath).isDirectory()){delDir(curPath); // 递归删除文件夹
            } else {fs.unlinkSync(curPath); // 删除文件
            }
        });
        fs.rmdirSync(path);
    }
}

var viewPath = path.join(__dirname , 'views');
var outputPath = path.join(__dirname,'dist');

delDir(outputPath);
//process.exit();

if (!fs.existsSync(outputPath)) {fs.mkdirSync(outputPath)
}

const view = (filename)=>{return path.join(viewPath,filename + '.html');
}

var langFiles = fs.readdirSync(path.join(__dirname,'langs'));

if (distType===1) {langFiles.forEach((file)=>{var langPath = path.join(outputPath,file.substring(0,file.lastIndexOf('.')))
        if (!fs.existsSync(langPath)) {fs.mkdirSync(langPath)
        }
    }) 
    fs.readdir(viewPath,(err,files)=>{files.forEach((file) => {let stats = fs.statSync(path.join(viewPath,file));
            if (stats.isFile()) {langFiles.forEach((langFile)=>{var local = langFile.substring(0,langFile.lastIndexOf('.'))
                    var langs =  require('./langs/'+local);
                    let name = file.substring(0,file.lastIndexOf('.'))
                    ejs.renderFile(view(name),{...langs[name],header:langs['header'],footer:langs['footer'],url:tools.url(langs.lang)},(err,str)=>{fs.writeFile(path.join(outputPath,local,file), str, (err)=>{if (err) {console.log(` 创建 ${path.join(outputPath,local,file)}失败 `)
                            } else {console.log(` 创建 ${path.join(outputPath,local,file)}成功 `)
                            }
                        })
                    });
                })
            }
        })
    })
} else if (distType===2) {fs.readdir(viewPath,(err,files)=>{files.forEach((file) => {let stats = fs.statSync(path.join(viewPath,file));
            if (stats.isFile()) {langFiles.forEach((langFile)=>{var local = langFile.substring(0,langFile.lastIndexOf('.'))
                    var langs =  require('./langs/'+local);
                    let name = file.substring(0,file.lastIndexOf('.'))
                    let tplPtah = path.join(outputPath,name)
                    if (!fs.existsSync(tplPtah)) {fs.mkdirSync(tplPtah)
                    }
                    let tplLangPath = path.join(tplPtah,local)
                    if (!fs.existsSync(tplLangPath)) {fs.mkdirSync(tplLangPath)
                    }
                    let tplLangPathFile = path.join(tplLangPath,'index.html')
                    ejs.renderFile(view(name),{...langs[name],header:langs['header'],footer:langs['footer'],url:tools.url(langs.lang)},(err,str)=>{fs.writeFile(tplLangPathFile, str, (err)=>{if (err) {console.log(` 创建 ${tplLangPathFile}失败 `)
                            } else {console.log(` 创建 ${tplLangPathFile}成功 `)
                            }
                        })
                    });
                })
            }
        })
    })
}

const movePath = (fromPath,toPath)=>{if (!fs.existsSync(toPath)) {fs.mkdirSync(toPath)
    }
    fs.readdir(fromPath,(err,files)=>{files.forEach((file)=>{let filePath = path.join(fromPath,file)
            if (fs.statSync(filePath).isDirectory()) {movePath(path.join(fromPath,file),path.join(toPath,file));
            } else {fs.readFile(filePath,(err,str)=>{if (err) {console.log(` 拷贝 ${filePath}失败 `)
                    } else {fs.writeFile(path.join(toPath,file),str, (err)=>{if (err) {console.log(` 创建 ${path.join(toPath,file)}失败 `)
                            } else {console.log(` 创建 ${path.join(toPath,file)}成功 `)
                            }
                        })
                    }
                })
            }
        })
    })
}

movePath(path.join(__dirname,'public','static'),path.join(outputPath,'static'))

配置命令

主要配置 package.json 文件的启动命令和打包命令

"scripts": {
    "start": "supervisor index.js",
    "build": "node build.js"
}

脚手架完毕,可以愉快的开发了 ^_^

退出移动版