TeaWeb/webpack/EJSGenerator.ts

107 lines
3.6 KiB
TypeScript

import * as webpack from "webpack";
import * as ejs from "ejs";
import * as fs from "fs";
import * as util from "util";
import * as minifier from "html-minifier";
import * as path from "path";
import Compilation = webpack.compilation.Compilation;
interface Options {
input: string,
output: string,
minify?: boolean,
initialJSEntryChunk: string,
embedInitialJSEntryChunk?: boolean,
initialCSSFile: {
localFile: string,
publicFile: string
},
embedInitialCSSFile?: boolean,
variables?: {[name: string]: any};
}
class EJSGenerator {
readonly options: Options;
constructor(options: Options) {
this.options = options;
}
private async generateEntryJsTag(compilation: Compilation) {
const entry_group = compilation.chunkGroups.find(e => e.options.name === this.options.initialJSEntryChunk);
if(!entry_group) return; /* not the correct compilation */
const tags = entry_group.chunks.map(chunk => {
if(chunk.files.length !== 1)
throw "invalid chunk file count";
const file = chunk.files[0];
if(path.extname(file) !== ".js")
throw "Entry chunk file has unknown extension";
if(!this.options.embedInitialJSEntryChunk) {
return '<script type="application/javascript" src=' + compilation.compiler.options.output.publicPath + file + ' async defer></script>';
} else {
const script = fs.readFileSync(path.join(compilation.compiler.outputPath, file));
return `<script type="application/javascript">${script}</script>`;
}
});
return tags.join("\n");
}
private async generateEntryCssTag() {
if(this.options.embedInitialCSSFile) {
const style = await util.promisify(fs.readFile)(this.options.initialCSSFile.localFile);
return `<style>${style}</style>`
} else {
return `<link rel="stylesheet" href="${this.options.initialCSSFile.publicFile}">`
}
}
apply(compiler: webpack.Compiler) {
compiler.hooks.afterEmit.tapPromise(this.constructor.name, async compilation => {
const input = await util.promisify(fs.readFile)(this.options.input);
const variables = Object.assign({}, this.options.variables);
variables["initial_script"] = await this.generateEntryJsTag(compilation);
variables["initial_css"] = await this.generateEntryCssTag();
let generated = await ejs.render(input.toString(), variables, {
beautify: false, /* uglify is a bit dump and does not understands ES6 */
context: this
});
if(this.options.minify) {
generated = minifier.minify(generated, {
html5: true,
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeTagWhitespace: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: true,
});
}
await util.promisify(fs.writeFile)(this.options.output, generated);
});
compiler.hooks.afterCompile.tapPromise(this.constructor.name, async compilation => {
const file = path.resolve(this.options.input);
if(compilation.fileDependencies.has(file))
return;
console.log("Adding additional watch to %s", file);
compilation.fileDependencies.add(file);
});
}
}
export = EJSGenerator;