const fs = require('fs'); const swc = require('@swc/core'); const path = require('path'); const util = require('util'); const exec = util.promisify(require('child_process').exec); const postcss = require('postcss'); const htmlminify = require('html-minifier-terser').minify; const utils = require('./assets/utils.js'); const assproc = require('./assets/assproc.js'); const rootDir = __dirname; const assetsDir = path.join(rootDir, 'assets'); const assetsInfo = path.join(assetsDir, 'current.json'); const pubDir = path.join(rootDir, 'public'); const pubAssets = '/assets'; const pubAssetsFull = path.join(pubDir, pubAssets); const pubAssetCSSFormat = '%s-%s.css'; const pubAssetJSFormat = '%s-%s.js'; const isDebugBuild = fs.existsSync(path.join(rootDir, '.debug')); const renderTemplates = [ { in: 'errors/401', out: '/error-401.html' }, { in: 'errors/403', out: '/error-403.html' }, { in: 'errors/404', out: '/error-404.html' }, { in: 'errors/500', out: '/error-500.html' }, { in: 'errors/503', out: '/error-503.html' }, ]; const swcJscOptions = { target: 'es2016', loose: false, externalHelpers: false, keepClassNames: true, preserveAllComments: false, transform: {}, parser: { syntax: 'ecmascript', jsx: true, dynamicImport: false, privateMethod: false, functionBind: false, exportDefaultFrom: false, exportNamespaceFrom: false, decorators: false, decoratorsBeforeExport: false, topLevelAwait: true, importMeta: false, }, transform: { react: { runtime: 'classic', pragma: '$er', }, }, }; const htmlMinifyOptions = { collapseBooleanAttributes: true, collapseWhitespace: true, conservativeCollapse: false, decodeEntities: false, quoteCharacter: '"', removeAttributeQuotes: true, removeComments: true, removeEmptyAttributes: true, removeOptionalTags: true, removeScriptTypeAttributes: true, removeStyleLinkTypeAttributes: true, sortAttributes: true, sortClassName: true, }; const postcssPlugins = []; if(!isDebugBuild) postcssPlugins.push(require('cssnano')); postcssPlugins.push(require('autoprefixer')({ remove: false, })); fs.mkdirSync(pubAssetsFull, { recursive: true }); (async () => { console.log('Building assets...'); const dirs = fs.readdirSync(assetsDir); const assets = {}; const _postcss = postcss(postcssPlugins); for(const dir of dirs) { const dirPath = path.join(assetsDir, dir); if(!fs.statSync(dirPath).isDirectory()) continue; const parts = dir.split('.', 2); if(parts.length < 2) continue; console.log(); console.log(dir); let filePath = ''; let fileBody = ''; if(parts[1] === 'js') { fileBody = await assproc.process(dirPath, { 'prefix': '#', 'entry': 'main.js' }); fileBody = (await swc.transform(fileBody, { filename: dir, sourceMaps: false, isModule: false, minify: !isDebugBuild, jsc: swcJscOptions, })).code; filePath = path.join(pubAssets, util.format(pubAssetJSFormat, parts[0], utils.shortHash(fileBody))); } else if(parts[1] === 'css') { fileBody = await assproc.process(dirPath, { 'prefix': '@', 'entry': 'main.css' }); fileBody = (await _postcss.process(fileBody, { from: dir })).css; filePath = path.join(pubAssets, util.format(pubAssetCSSFormat, parts[0], utils.shortHash(fileBody))); } if(filePath !== '') { assets[dir] = filePath; fs.writeFileSync(path.join(pubDir, filePath), fileBody); } else console.error(`Unknown type: ${parts[1]}`); } console.log('Writing assets info...'); fs.writeFileSync(assetsInfo, JSON.stringify(assets)); console.log('Housekeeping...'); assproc.housekeep(pubAssetsFull); console.log('Prerendering templates...'); const renderCommand = path.join(rootDir, 'tools/render-tpl'); console.log(renderCommand); for(const info of renderTemplates) { console.log(); console.log(info.in); const { stdout, stderr } = await exec(`${renderCommand} ${info.in}`); if(stdout.trim() === '') { console.error(stderr); continue; } let body = await htmlminify(stdout, htmlMinifyOptions); fs.writeFileSync(path.join(pubDir, info.out), body); } })();