htmlgaga
Version:
Manage non-SPA pages with webpack and React.js
938 lines (880 loc) • 34.8 kB
JavaScript
;
function _interopDefault(ex) {
return ex && "object" == typeof ex && "default" in ex ? ex.default : ex;
}
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = {};
return e && Object.keys(e).forEach((function(k) {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: !0,
get: function() {
return e[k];
}
});
})), n.default = e, n;
}
var yargs = _interopDefault(require("yargs")), rimraf = _interopDefault(require("rimraf")), fs = require("fs"), path = require("path"), path__default = _interopDefault(path), pino = _interopDefault(require("pino")), MiniCssExtractPlugin = _interopDefault(require("mini-css-extract-plugin")), rehypePrism = _interopDefault(require("@mapbox/rehype-prism")), webpack = _interopDefault(require("webpack")), express = _interopDefault(require("express")), devMiddleware = _interopDefault(require("webpack-dev-middleware")), http = _interopDefault(require("http")), fs$1 = _interopDefault(require("fs-extra")), HtmlWebpackPlugin = _interopDefault(require("html-webpack-plugin")), CssoWebpackPlugin = _interopDefault(require("csso-webpack-plugin")), WebpackAssetsManifest = _interopDefault(require("webpack-manifest-plugin")), TerserJSPlugin = _interopDefault(require("terser-webpack-plugin")), prettier = _interopDefault(require("prettier")), react = require("react"), server = require("react-dom/server"), HtmlTags = _interopDefault(require("html-webpack-plugin/lib/html-tags")), tapable = require("tapable"), merge = _interopDefault(require("lodash.merge")), MessageType = require("./MessageType-08992331.cjs.prod.js"), WebSocket = _interopDefault(require("ws"));
const cwd = process.cwd(), logger = pino({
level: process.env.LOG_LEVEL || "info",
prettyPrint: {
translateTime: "HH:MM:ss",
ignore: "pid,hostname"
},
serializers: {
err: pino.stdSerializers.err,
error: pino.stdSerializers.err
}
}), assetsRoot = "static", alias = {
img: path.resolve(cwd, "static/img"),
css: path.resolve(cwd, "static/css"),
js: path.resolve(cwd, "static/js")
}, publicFolder = "public", extensions = [ ".js", ".jsx", ".ts", ".tsx", ".mjs", ".json" ], performance = require("perf_hooks").performance, PerformanceObserver = require("perf_hooks").PerformanceObserver, cacheRoot = path.join(cwd, ".htmlgaga", "cache"), babelPresets = [ "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript" ], rules = [ {
test: /\.(js|jsx|ts|tsx|mjs)$/i,
exclude: /node_modules/,
use: [ {
loader: "babel-loader",
options: {
presets: [ ...babelPresets ],
plugins: [ "react-require" ],
cacheDirectory: !0,
cacheCompression: !1
}
} ]
}, {
test: /\.(md|mdx)$/i,
use: [ {
loader: "babel-loader",
options: {
presets: [ ...babelPresets ],
plugins: [ "react-require" ],
cacheDirectory: !0,
cacheCompression: !1
}
}, {
loader: "@mdx-js/loader",
options: {
rehypePlugins: [ rehypePrism ]
}
} ]
}, {
test: /\.(png|svg|jpg|jpeg|gif)$/i,
loader: "file-loader",
options: {
name: "[name].[contenthash].[ext]"
}
}, {
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [ {
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "fonts/"
}
} ]
}, {
test: /\.(sa|sc|c)ss$/i,
use: [ {
loader: MiniCssExtractPlugin.loader
}, "css-loader", {
loader: "postcss-loader",
options: {
ident: "postcss",
plugins: [ require("tailwindcss"), require("autoprefixer") ]
}
}, "sass-loader" ]
} ];
function isHtmlRequest(url) {
return !!url.endsWith("/") || !!/\.html$/.test(url);
}
function deriveFilenameFromRelativePath(from, to) {
const relativePath = path.relative(from, to), {ext: ext} = path.parse(relativePath);
return relativePath.replace(ext, ".html");
}
const doNotFilter = () => !0;
async function collect(root = cwd, filter = doNotFilter, acc = []) {
const files = await fs.promises.readdir(root);
for (const file of files) {
const filePath = path.resolve(root, file), stats = await fs.promises.stat(filePath);
if (stats.isFile()) {
const f = filePath;
filter(f) && acc.push(f);
} else stats.isDirectory() && await collect(filePath, filter, acc);
}
return acc;
}
function _classPrivateFieldGet(receiver, privateMap) {
var descriptor = privateMap.get(receiver);
if (!descriptor) throw new TypeError("attempted to get private field on non-instance");
return descriptor.get ? descriptor.get.call(receiver) : descriptor.value;
}
function _classPrivateFieldSet(receiver, privateMap, value) {
var descriptor = privateMap.get(receiver);
if (!descriptor) throw new TypeError("attempted to set private field on non-instance");
if (descriptor.set) descriptor.set.call(receiver, value); else {
if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
descriptor.value = value;
}
return value;
}
var _options = new WeakMap;
class PrettyPlugin {
constructor(options) {
_options.set(this, {
writable: !0,
value: void 0
}), _classPrivateFieldSet(this, _options, options);
}
apply(compiler) {
compiler.hooks.emit.tap("PrettyPlugin", compilation => {
var _classPrivateFieldGet2;
(null === (_classPrivateFieldGet2 = _classPrivateFieldGet(this, _options).html) || void 0 === _classPrivateFieldGet2 ? void 0 : _classPrivateFieldGet2.pretty) && Object.keys(compilation.assets).forEach(asset => {
if (asset.endsWith(".html")) {
const source = compilation.assets[asset].source(), prettyHtml = prettier.format(Buffer.isBuffer(source) ? source.toString() : source, {
parser: "html"
});
compilation.assets[asset] = {
source: () => prettyHtml,
size: () => prettyHtml.length
};
}
});
});
}
}
var _pagesDir = new WeakMap, _outputPath = new WeakMap, _clients = new WeakMap, _config = new WeakMap;
class ClientsCompiler {
constructor(pagesDir, outputPath, config) {
_pagesDir.set(this, {
writable: !0,
value: void 0
}), _outputPath.set(this, {
writable: !0,
value: void 0
}), _clients.set(this, {
writable: !0,
value: void 0
}), _config.set(this, {
writable: !0,
value: void 0
}), _classPrivateFieldSet(this, _pagesDir, pagesDir), _classPrivateFieldSet(this, _outputPath, outputPath),
_classPrivateFieldSet(this, _config, config);
}
createWebpackConfig(entry) {
var _ref, _classPrivateFieldGet3;
const relative = path__default.relative(_classPrivateFieldGet(this, _pagesDir), entry), outputHtml = relative.replace(/\.client\.(js|ts)$/, ".html");
return {
experiments: {
asset: !0
},
mode: "production",
optimization: {
minimize: !0,
minimizer: [ new TerserJSPlugin({
terserOptions: {},
extractComments: !1
}) ],
splitChunks: {
cacheGroups: {
vendors: path__default.resolve(cwd, "node_modules")
}
}
},
entry: {
[relative.replace(/\.client.*/, "").split(path__default.sep).join("-")]: entry
},
output: {
ecmaVersion: 5,
path: path__default.resolve(_classPrivateFieldGet(this, _outputPath)),
filename: "[name].[contenthash].js",
chunkFilename: "[name]-[id].[contenthash].js",
publicPath: null != ASSET_PATH ? ASSET_PATH : _classPrivateFieldGet(this, _config).assetPath
},
module: {
rules: rules
},
resolve: {
extensions: extensions,
alias: alias
},
externals: _classPrivateFieldGet(this, _config).globalScripts ? _classPrivateFieldGet(this, _config).globalScripts.map(script => ({
[script[0]]: script[1].global
})) : [],
plugins: [ new HtmlWebpackPlugin({
template: path__default.resolve(_classPrivateFieldGet(this, _outputPath), outputHtml),
filename: outputHtml,
minify: null === (_ref = null === (_classPrivateFieldGet3 = _classPrivateFieldGet(this, _config).html) || void 0 === _classPrivateFieldGet3 ? void 0 : _classPrivateFieldGet3.pretty) || void 0 === _ref || _ref
}), new webpack.DefinePlugin({
'"production"': '"production"'
}), new CssoWebpackPlugin({
restructure: !1
}), new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
}), new WebpackAssetsManifest({
fileName: "client-assets.json",
generate: generateManifest
}), new PrettyPlugin(_classPrivateFieldGet(this, _config)) ]
};
}
async run(callback) {
_classPrivateFieldSet(this, _clients, await collect(_classPrivateFieldGet(this, _pagesDir), filename => filename.endsWith(".client.js") || filename.endsWith(".client.ts")));
const configs = _classPrivateFieldGet(this, _clients).map(client => this.createWebpackConfig(client));
webpack(configs).run(callback);
}
}
function Render(App) {
return server.renderToStaticMarkup(react.createElement(App));
}
function hasClientEntry(pageEntry, exts = "js,ts".split(",")) {
const {name: name, dir: dir} = path__default.parse(pageEntry);
for (let i = 0; i < exts.length; i++) {
const ext = exts[i], clientEntry = path__default.join(dir, `${name}.client.${ext}`);
if (fs$1.existsSync(clientEntry)) return {
exists: !0,
clientEntry: clientEntry
};
}
return {
exists: !1
};
}
function _defineProperty(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
const {htmlTagObjectToString: htmlTagObjectToString} = HtmlTags;
async function loadHtmlTags(root, filename) {
try {
const {headTags: headTags, bodyTags: bodyTags} = await new Promise((function(resolve) {
resolve(_interopNamespace(require(path__default.resolve(root, filename))));
}));
return {
headTags: headTags,
bodyTags: bodyTags
};
} catch (err) {
return {
headTags: [],
bodyTags: []
};
}
}
async function loadAllHtmlTags(root, files) {
return Promise.all(files.map(async file => await loadHtmlTags(root, file))).then(values => values.reduce((acc, cur) => (acc.headTags = acc.headTags.concat(cur.headTags),
acc.bodyTags = acc.bodyTags.concat(cur.bodyTags), acc), {
headTags: [],
bodyTags: []
}));
}
class Ssr {
constructor() {
_defineProperty(this, "hooks", void 0), _defineProperty(this, "helmet", void 0),
this.hooks = {
helmet: new tapable.SyncHook
};
}
async run(pagesDir, templateName, cacheRoot, outputPath, htmlgagaConfig) {
const htmlTags = await loadAllHtmlTags(cacheRoot, [ `${templateName}.json` ]);
let {headTags: headTags} = htmlTags, {bodyTags: bodyTags} = htmlTags;
htmlgagaConfig.globalScripts && (headTags = htmlgagaConfig.globalScripts.map(script => {
const {global: global, ...others} = script[1];
return {
tagName: "script",
voidTag: !1,
attributes: {
...others
}
};
}).concat(headTags)), bodyTags = [];
let preloadStyles = "";
htmlgagaConfig.html.preload.style && (preloadStyles = headTags.filter(tag => "link" === tag.tagName).map(tag => `<link rel="preload" href="${tag.attributes.href}" as="${"stylesheet" === tag.attributes.rel ? "style" : ""}" />`).join(""));
let preloadScripts = "";
htmlgagaConfig.html.preload.script && (preloadScripts = bodyTags.filter(tag => "script" === tag.tagName).concat(headTags.filter(tag => "script" === tag.tagName)).map(tag => `<link rel="preload" href="${tag.attributes.src}" as="script" />`).join(""));
const hd = headTags.map(tag => htmlTagObjectToString(tag, !0)).join(""), bd = bodyTags.map(tag => htmlTagObjectToString(tag, !0)).join(""), appPath = `${path__default.resolve(outputPath, templateName + ".js")}`, {default: App} = await new Promise((function(resolve) {
resolve(_interopNamespace(require(appPath)));
})), html = Render(App);
let body;
var _htmlgagaConfig$html;
(this.hooks.helmet.call(), body = this.helmet ? `<!DOCTYPE html><html ${this.helmet.htmlAttributes.toString()}><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /><meta name="generator" content="htmlgaga" />${this.helmet.title.toString()}${this.helmet.meta.toString()}${this.helmet.link.toString()}${preloadStyles}${preloadScripts}${hd}</head><body>${html}${bd}</body></html>` : `<!DOCTYPE html><html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /><meta name="generator" content="htmlgaga" />${preloadStyles}${preloadScripts}${hd}</head><body>${html}${bd}</body></html>`,
!1 === hasClientEntry(path__default.join(pagesDir, templateName)).exists) && (!0 === (null == htmlgagaConfig || null === (_htmlgagaConfig$html = htmlgagaConfig.html) || void 0 === _htmlgagaConfig$html ? void 0 : _htmlgagaConfig$html.pretty) && (body = prettier.format(body, {
parser: "html"
})));
fs$1.outputFileSync(path__default.join(outputPath, templateName + ".html"), body),
fs$1.removeSync(appPath);
}
}
function normalizeAssetPath() {
const ASSET_PATH = process.env.ASSET_PATH;
if (void 0 !== ASSET_PATH) return ASSET_PATH.endsWith("/") ? ASSET_PATH : ASSET_PATH + "/";
}
function _defineProperty$1(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
const defaultConfiguration = {
html: {
pretty: !0,
preload: {
style: !0,
script: !0
}
},
plugins: [],
assetPath: ""
}, configuration = "htmlgaga.config.js";
class Builder {
constructor(pagesDir) {
_defineProperty$1(this, "pagesDir", void 0), _defineProperty$1(this, "config", void 0),
this.pagesDir = pagesDir;
}
applyOptionsDefaults() {
var _this$config$html, _this$config$plugins;
this.config = {
...defaultConfiguration,
...this.config,
html: merge({}, defaultConfiguration.html, null !== (_this$config$html = this.config.html) && void 0 !== _this$config$html ? _this$config$html : {}),
plugins: merge([], defaultConfiguration.plugins, null !== (_this$config$plugins = this.config.plugins) && void 0 !== _this$config$plugins ? _this$config$plugins : [])
};
}
async resolveConfig() {
const configName = path__default.resolve(this.pagesDir, "..", configuration);
let config;
try {
config = await new Promise((function(resolve) {
resolve(_interopNamespace(require(configName)));
}));
} catch (err) {
config = {};
}
this.config = config, this.applyOptionsDefaults(), logger.debug(configuration, this.config);
}
}
function _defineProperty$2(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
class PersistDataPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(PersistDataPlugin.PluginName, compilation => {
HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync(PersistDataPlugin.PluginName, (htmlPluginData, callback) => {
fs$1.outputJSON(path__default.join(cacheRoot, `${htmlPluginData.outputName.replace(/\.html$/, "")}.json`), {
headTags: htmlPluginData.headTags,
bodyTags: htmlPluginData.bodyTags
}, () => {
callback(null, htmlPluginData);
});
});
});
}
}
function _defineProperty$3(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
_defineProperty$2(PersistDataPlugin, "PluginName", "PersistDataPlugin");
const NAME = "RemoveAssetsPlugin";
class RemoveAssetsPlugin {
constructor(filter, callback) {
_defineProperty$3(this, "filter", void 0), _defineProperty$3(this, "callback", void 0),
this.filter = filter, this.callback = callback;
}
apply(compiler) {
compiler.hooks.compilation.tap(NAME, compilation => {
compilation.hooks.processAssets.tap(NAME, assets => {
Object.keys(assets).forEach(filename => {
this.filter(filename) && (delete assets[filename], this.callback && this.callback(filename));
});
});
});
}
}
function _defineProperty$4(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
function _classPrivateFieldGet$1(receiver, privateMap) {
var descriptor = privateMap.get(receiver);
if (!descriptor) throw new TypeError("attempted to get private field on non-instance");
return descriptor.get ? descriptor.get.call(receiver) : descriptor.value;
}
function _classPrivateFieldSet$1(receiver, privateMap, value) {
var descriptor = privateMap.get(receiver);
if (!descriptor) throw new TypeError("attempted to set private field on non-instance");
if (descriptor.set) descriptor.set.call(receiver, value); else {
if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
descriptor.value = value;
}
return value;
}
function generateManifest(seed, files, entrypoints) {
return {
files: files.reduce((manifest, {name: name, path: path}) => ({
...manifest,
[name]: path
}), seed),
entrypoints: entrypoints
};
}
const BEGIN = "begin", END = "end", ASSET_PATH = normalizeAssetPath();
var _pages = new WeakMap, _outputPath$1 = new WeakMap, _pageEntries = new WeakMap;
class ProdBuilder extends Builder {
constructor(pagesDir, outputPath) {
super(pagesDir), _pages.set(this, {
writable: !0,
value: void 0
}), _outputPath$1.set(this, {
writable: !0,
value: void 0
}), _defineProperty$4(this, "config", void 0), _pageEntries.set(this, {
writable: !0,
value: void 0
}), _classPrivateFieldSet$1(this, _outputPath$1, outputPath), _classPrivateFieldSet$1(this, _pageEntries, []);
}
normalizedPageEntry(pagePath) {
return path.relative(this.pagesDir, pagePath).replace(new RegExp(`\\${path.extname(pagePath)}$`), "");
}
createWebpackConfig(pages) {
const entries = pages.reduce((acc, page) => {
const pageEntryKey = this.normalizedPageEntry(page);
return _classPrivateFieldGet$1(this, _pageEntries).push(pageEntryKey), acc[pageEntryKey] = page,
acc;
}, {}), htmlPlugins = pages.map(page => {
const filename = deriveFilenameFromRelativePath(this.pagesDir, page);
return new HtmlWebpackPlugin({
chunks: [ this.normalizedPageEntry(page) ],
filename: filename,
minify: !1,
inject: !1,
cache: !1,
showErrors: !1,
meta: !1
});
});
return {
experiments: {
asset: !0
},
externals: [ "react-helmet", "react", "react-dom" ],
mode: "production",
entry: {
...entries
},
optimization: {
minimize: !1
},
output: {
ecmaVersion: 5,
path: path.resolve(_classPrivateFieldGet$1(this, _outputPath$1)),
libraryTarget: "commonjs2",
filename: pathData => {
var _pathData$chunk, _pathData$chunk2;
if ((null == pathData || null === (_pathData$chunk = pathData.chunk) || void 0 === _pathData$chunk ? void 0 : _pathData$chunk.name) && entries[null == pathData || null === (_pathData$chunk2 = pathData.chunk) || void 0 === _pathData$chunk2 ? void 0 : _pathData$chunk2.name]) return "[name].js";
return "[name].[contenthash].js";
},
chunkFilename: "[name]-[id].[contenthash].js",
publicPath: null != ASSET_PATH ? ASSET_PATH : this.config.assetPath
},
module: {
rules: rules
},
resolve: {
extensions: extensions,
alias: alias
},
plugins: [ new PersistDataPlugin, new WebpackAssetsManifest({
fileName: "assets.json",
generate: generateManifest
}), new webpack.DefinePlugin({
'"production"': '"production"'
}), new CssoWebpackPlugin({
restructure: !1
}), ...htmlPlugins, new RemoveAssetsPlugin(filename => -1 !== _classPrivateFieldGet$1(this, _pageEntries).indexOf(filename.replace(".html", "")), filename => logger.debug(`${filename} removed by RemoveAssetsPlugin`)), new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
}) ]
};
}
runCallback(err, stats) {
if (err) return err.stack ? logger.error(err.stack) : logger.error(err), void (err.details && logger.error(err.details));
if (!stats) return;
const info = stats.toJson();
stats.hasErrors() && info.errors.forEach(err => logger.error(err.message)), stats.hasWarnings() && info.warnings.forEach(warning => logger.warn(warning.message));
}
markEnd() {
performance.mark(END), performance.measure("begin to end", BEGIN, END);
new PerformanceObserver((list, observer) => {
logger.info(`All ${this.pageOrPages(_classPrivateFieldGet$1(this, _pages).length)} built in ${(list.getEntries()[0].duration / 1e3).toFixed(2)}s!`),
observer.disconnect();
}).observe({
entryTypes: [ "measure" ]
}), performance.measure("Build time", BEGIN, END);
}
markBegin() {
performance.mark(BEGIN);
}
pageOrPages(len) {
return len < 2 ? len + " page" : len + " pages";
}
async ssr() {
for (const templateName of _classPrivateFieldGet$1(this, _pageEntries)) {
const ssr = new Ssr;
if (Array.isArray(this.config.plugins)) for (const plugin of this.config.plugins) plugin.apply(ssr);
ssr.run(this.pagesDir, templateName, cacheRoot, _classPrivateFieldGet$1(this, _outputPath$1), this.config);
}
}
async run() {
this.markBegin(), logger.info("Collecting pages..."), _classPrivateFieldSet$1(this, _pages, await collect(this.pagesDir, searchPageEntry)),
logger.info(`${this.pageOrPages(_classPrivateFieldGet$1(this, _pages).length)} collected`),
await this.resolveConfig(), webpack(this.createWebpackConfig(_classPrivateFieldGet$1(this, _pages))).run(async (err, stats) => {
this.runCallback(err, stats), await this.ssr();
const clientJsCompiler = new ClientsCompiler(this.pagesDir, _classPrivateFieldGet$1(this, _outputPath$1), this.config);
await clientJsCompiler.run((err, stats) => {
this.runCallback(err, stats), this.cleanCache(), fs$1.copySync(path.join(this.pagesDir, "..", "public"), _classPrivateFieldGet$1(this, _outputPath$1)),
this.markEnd();
});
});
}
cleanCache() {
fs$1.removeSync(cacheRoot);
}
}
const exts = "mjs,js,jsx,ts,tsx,md,mdx";
function searchPageEntry(pagePath, extList = exts) {
return new RegExp(`.(${extList.split(",").join("|")})$`).test(pagePath) && extList.split(",").every(ext => !1 === pagePath.includes(`.client.${ext}`));
}
function findRawFile(sourceDir, url, extList = exts.split(",")) {
url.endsWith("/") && (url += "/index.html");
for (let i = 0; i < extList.length; i++) {
const searchExt = extList[i], rawFilePath = path.join(sourceDir, url.replace(/\.html$/, `.${searchExt}`));
if (fs$1.existsSync(rawFilePath)) return {
src: rawFilePath,
exists: !0
};
}
return {
exists: !1
};
}
function deriveEntryKeyFromRelativePath(from, to) {
const relativePath = path.relative(from, to), {base: base, name: name} = path.parse(relativePath);
return path.join(relativePath.replace(base, "") + name + "/index");
}
const vendors = {
"react-vendors": [ "react", "react-dom" ]
}, socketClient = `${require.resolve("../Client")}`;
function createEntries(pagesDir, pages, vendors$1 = vendors) {
return {
...pages.reduce((acc, page) => {
const entryKey = deriveEntryKeyFromRelativePath(pagesDir, page), hasClientJs = hasClientEntry(page), entry = {
[entryKey]: {
import: [ socketClient, page ],
dependOn: Object.keys(vendors$1)
}
};
return !0 === hasClientJs.exists && (entry[deriveEntryKeyFromRelativePath(pagesDir, hasClientJs.clientEntry)] = {
import: [ hasClientJs.clientEntry ],
dependOn: [ entryKey ]
}), acc = {
...acc,
...entry
};
}, {}),
...vendors$1
};
}
const createDOMRenderRule = pagesDir => ({
include: filename => filename.startsWith(pagesDir) && !1 === filename.includes(".client."),
plugins: [ [ "react-dom-render", {
hydrate: !1,
root: "htmlgaga-app"
} ] ]
});
function createWebpackConfig(pages, pagesDir, socketUrl, options) {
return {
experiments: {
asset: !0
},
mode: "development",
entry: () => createEntries(pagesDir, pages),
output: {
ecmaVersion: 5,
publicPath: "/"
},
stats: "minimal",
module: {
rules: [ {
test: /\.(mjs|js|jsx|ts|tsx)$/i,
exclude: [ /node_modules/ ],
use: [ {
loader: "babel-loader",
options: {
presets: [ "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript" ],
plugins: [ "react-require" ],
overrides: [ createDOMRenderRule(pagesDir) ]
}
} ]
}, {
test: /\.(mdx|md)$/,
use: [ {
loader: "babel-loader",
options: {
presets: [ "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript" ],
plugins: [ "react-require" ],
overrides: [ createDOMRenderRule(pagesDir) ]
}
}, {
loader: "@mdx-js/loader",
options: {
rehypePlugins: [ rehypePrism ]
}
} ]
}, {
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset"
}, {
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [ {
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "fonts/"
}
} ]
}, {
test: /\.(sa|sc|c)ss$/i,
use: [ "style-loader", "css-loader", {
loader: "postcss-loader",
options: {
ident: "postcss",
plugins: [ require("tailwindcss"), require("autoprefixer") ]
}
}, "sass-loader" ]
} ]
},
resolve: {
extensions: extensions,
alias: alias
},
plugins: [ new webpack.DefinePlugin({
'"production"': '"development"',
__WEBSOCKET__: JSON.stringify(socketUrl)
}), new webpack.NoEmitOnErrorsPlugin ],
...options
};
}
function newHtmlWebpackPlugin(pagesDir, page, vendors$1 = vendors) {
const htmlFilename = deriveFilenameFromRelativePath(pagesDir, page), entryKey = deriveEntryKeyFromRelativePath(pagesDir, page), hasClientJs = hasClientEntry(page);
return new HtmlWebpackPlugin({
template: require.resolve("../devTemplate"),
chunks: !0 === hasClientJs.exists ? [ ...Object.keys(vendors$1), entryKey, deriveEntryKeyFromRelativePath(pagesDir, hasClientJs.clientEntry) ] : [ ...Object.keys(vendors$1), entryKey ],
chunksSortMode: "manual",
filename: htmlFilename
});
}
function watchCompilation(compiler, wsServer) {
if (!wsServer) return;
compiler.hooks.done.tap("htmlgaga-reload", stats => {
const statsJson = stats.toJson({
all: !1,
hash: !0,
assets: !0,
warnings: !0,
errors: !0,
errorDetails: !1
}), hasErrors = stats.hasErrors(), hasWarnings = stats.hasWarnings();
wsServer.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) return client.send(JSON.stringify({
type: MessageType.MessageType.HASH,
data: {
hash: statsJson.hash,
startTime: stats.startTime,
endTime: stats.endTime
}
})), hasErrors ? (console.log(statsJson.errors), client.send(JSON.stringify({
type: MessageType.MessageType.ERRORS,
data: statsJson.errors
}))) : hasWarnings ? client.send(JSON.stringify({
type: MessageType.MessageType.WARNINGS,
data: statsJson.warnings
})) : void client.send(JSON.stringify({
type: MessageType.MessageType.RELOAD
}));
});
}), compiler.hooks.invalid.tap("htmlgaga-reload", () => {
wsServer.clients.forEach(client => {
client.readyState === WebSocket.OPEN && client.send(JSON.stringify({
type: MessageType.MessageType.INVALID
}));
});
});
}
function createWebSocketServer(httpServer, socketPath) {
const wsServer = new WebSocket.Server({
server: httpServer,
path: socketPath
});
function cleanup() {
wsServer.close(() => {
process.exit(1);
});
}
return wsServer.on("connection", socket => {
socket.on("message", data => {
console.log(`${data} from client`);
});
}), wsServer.on("close", () => {
console.log("closed");
}), process.on("SIGINT", cleanup), process.on("SIGTERM", cleanup), wsServer;
}
function _defineProperty$5(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
const PLUGIN_NAME = "InjectGlobalScripts";
class InjectGlobalScriptsPlugin {
constructor(scripts) {
_defineProperty$5(this, "scripts", void 0), this.scripts = scripts;
}
apply(compiler) {
compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(PLUGIN_NAME, (htmlPluginData, callback) => {
const globalScripts = this.scripts.map(script => ({
tagName: "script",
voidTag: !1,
attributes: {
src: script
}
}));
callback(null, {
...htmlPluginData,
headTags: htmlPluginData.headTags.concat(globalScripts)
});
});
});
}
}
function _classPrivateFieldGet$2(receiver, privateMap) {
var descriptor = privateMap.get(receiver);
if (!descriptor) throw new TypeError("attempted to get private field on non-instance");
return descriptor.get ? descriptor.get.call(receiver) : descriptor.value;
}
function _classPrivateFieldSet$2(receiver, privateMap, value) {
var descriptor = privateMap.get(receiver);
if (!descriptor) throw new TypeError("attempted to set private field on non-instance");
if (descriptor.set) descriptor.set.call(receiver, value); else {
if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
descriptor.value = value;
}
return value;
}
var _host = new WeakMap, _port = new WeakMap, _pages$1 = new WeakMap, _httpServer = new WeakMap;
class DevServer extends Builder {
constructor(pagesDir, {host: host, port: port}) {
super(pagesDir), _host.set(this, {
writable: !0,
value: void 0
}), _port.set(this, {
writable: !0,
value: void 0
}), _pages$1.set(this, {
writable: !0,
value: void 0
}), _httpServer.set(this, {
writable: !0,
value: void 0
}), _classPrivateFieldSet$2(this, _host, host), _classPrivateFieldSet$2(this, _port, port),
_classPrivateFieldSet$2(this, _pages$1, []);
}
listen() {
_classPrivateFieldGet$2(this, _httpServer).listen(_classPrivateFieldGet$2(this, _port), _classPrivateFieldGet$2(this, _host), () => {
const server = `http://${_classPrivateFieldGet$2(this, _host)}:${_classPrivateFieldGet$2(this, _port)}`;
console.log(`Listening on ${server}`);
}).on("error", err => {
throw logger.info("You might run server on another port with option like --port 9999"),
err;
});
}
async start() {
await this.resolveConfig();
const webpackConfig = createWebpackConfig(_classPrivateFieldGet$2(this, _pages$1), this.pagesDir, `${_classPrivateFieldGet$2(this, _host)}:${_classPrivateFieldGet$2(this, _port)}/__websocket`, {
externals: this.config.globalScripts ? this.config.globalScripts.reduce((acc, cur) => (acc[cur[0]] = cur[1].global,
acc), {}) : []
}), compiler = webpack(webpackConfig), devMiddlewareInstance = devMiddleware(compiler), app = express();
app.use((pagesDir => (req, res, next) => {
if (isHtmlRequest(req.url)) {
const page = findRawFile(pagesDir, req.url);
if (page.exists) {
const src = page.src;
_classPrivateFieldGet$2(this, _pages$1).includes(src) || (_classPrivateFieldGet$2(this, _pages$1).push(src),
newHtmlWebpackPlugin(pagesDir, src).apply(compiler), new InjectGlobalScriptsPlugin(this.config.globalScripts ? this.config.globalScripts.map(script => script[1].src) : []).apply(compiler),
devMiddlewareInstance.invalidate());
}
}
next();
})(this.pagesDir)), app.use(devMiddlewareInstance);
const cwd = path.resolve(this.pagesDir, "..");
app.use(express.static(path.join(cwd, "public"))), app.use((function(req, res, next) {
if (req.is("html")) return res.status(404).end("Page Not Found");
next();
})), _classPrivateFieldSet$2(this, _httpServer, http.createServer(app));
const wsServer = createWebSocketServer(_classPrivateFieldGet$2(this, _httpServer), "/__websocket");
return watchCompilation(compiler, wsServer), this.listen(), app;
}
}
yargs.scriptName("htmlgaga").usage("$0 <cmd> [args]").command("dev", "Run development server", {
host: {
default: "localhost",
description: "Host to run server"
},
port: {
default: 8080,
description: "Port to run server"
}
}, (async function(argv) {
const {host: host, port: port} = argv, pagesDir = path.resolve(cwd, "pages");
new DevServer(pagesDir, {
host: host,
port: port
}).start();
})).command("build", "Build static html & assets", {
dest: {
default: "out",
description: "The output directory"
}
}, (function(argv) {
const pagesDir = path.resolve(cwd, "pages");
if (!fs.existsSync(pagesDir)) throw new Error("Couldn't find a `pages` directory. Make sure you have it under the project root");
const {dest: dest} = argv, outDir = path.resolve(cwd, dest);
rimraf(outDir, async err => {
if (err) return logger.error(err);
rimraf(path.resolve(cwd, ".htmlgaga"), async err => {
if (err) return logger.error(err);
const builder = new ProdBuilder(pagesDir, outDir);
process.env.NODE_ENV = "production", await builder.run();
});
});
})).help().argv;