/**
* build html for webpack plugin
* @author vfasky
*
**/
'use strict'
import * as fs from 'fs-plus'
import * as path from 'path'
import * as glob from 'glob'
import * as nunjucks from 'nunjucks'
import * as chokidar from 'chokidar'
interface pathInfo {
soure: string; // html file path
relative: string; // soure path
out: string; // out path
}
/**
* 取所有模板列表
* @param tplPath 模板目录
* @param outPath 输出目录
*/
function getDep (tplPath:string, outPath:string):pathInfo[] {
// 读模板文件
let tplFiles = glob.sync(path.join(tplPath, '**/*.html'))
let data:pathInfo[] = []
tplFiles.forEach((htmlFile: string) => {
let relative = path.relative(tplPath, htmlFile)
let baseName = path.basename(htmlFile)
// 忽略以 _ 开头的文件名
if (baseName.indexOf('_') !== 0) {
data.push({
soure: htmlFile,
relative: relative,
out: path.join(outPath, relative)
})
}
})
return data
}
interface buildHtmlOptions {
tplPath: string;
outPath: string;
varMap?: any;
callback?: any;
filters?: any;
}
/**
* BuildHtml
*/
export default class BuildHtml {
tplPath: string;
outPath: string;
varMap: any;
buildCallback: any;
filters: any;
/**
* webpack compiler status
*/
stats: any;
/**
* webpack compiler
*/
compiler: any;
template: nunjucks.Environment
constructor (options:buildHtmlOptions) {
this.tplPath = options.tplPath
this.outPath = options.outPath
this.varMap = options.varMap || {}
this.filters = options.filters || {}
this.stats = null
this.buildCallback = options.callback || function(){}
this.template = nunjucks.configure(this.tplPath, {
autoescape: false,
noCache: true
})
Object.keys(this.filters).forEach((name) => {
this.template.addFilter(name, this.filters[name])
})
if (process.env.WATCH) {
this.watch()
}
}
apply (compiler:any):void {
this.compiler = compiler
compiler.plugin('done', (stats:any) => {
this.stats = stats
this.build()
})
}
watch ():void {
let watchPath = path.join(this.tplPath, '**/*.html')
let watcher = chokidar.watch(watchPath, {
persistent: true
})
watcher.on('all', (event:any, path:string) => {
if(this.stats === null) return
this.build()
// 通过 sockets 发送刷新信号
if (this.compiler && this.compiler._server &&
this.compiler._server.socketSendData) {
this.compiler._server.socketSendData(
this.compiler._server.sockets,
this.stats.toJson(),
null,
'changeTpl',
[path]
)
}
})
}
build ():void {
if (this.stats === null) return
let packMap:any = this.stats.toJson().assetsByChunkName
let packs = Object.keys(packMap)
interface renderDataConfig {
module: any;
staticPath: string;
env: string;
[key:string]: any;
}
let renderData:renderDataConfig = {
module: {},
staticPath: '',
env: 'sit'
}
Object.keys(this.varMap).forEach((key) => {
if (typeof this.varMap[key] === 'function'){
renderData[key] = this.varMap[key]()
}
})
// 注册 module:name
packs.forEach((pack) => {
if (Array.isArray(packMap[pack])) {
renderData.module[pack] = packMap[pack][0]
packMap[pack].forEach((fileName:string) => {
let name = `${pack}${path.extname(fileName)}`
renderData.module[name] = fileName
})
} else {
renderData.module[pack] = packMap[pack]
renderData.module[`${pack}.js`] = packMap[pack]
renderData.module[`${pack}.css`] = ''
}
})
this.template.addFilter('url', (moduleName:string, staticPath?:string, env?:string) => {
if (staticPath == undefined) {
staticPath = renderData.staticPath
}
if (env == undefined) {
env = renderData.env
}
return `${staticPath}dist/${env}/${renderData.module[moduleName]}`
})
this.buildCallback(renderData)
getDep(this.tplPath, this.outPath).forEach((v) => {
let tpl = fs.readFileSync(v.soure, 'utf8')
try {
tpl = this.template.renderString(tpl, renderData)
fs.writeFileSync(v.out, tpl, 'utf8')
} catch (err) {
console.error(err)
}
})
}
}