From eb96b4f0ad14e8ea498a2a2d48962b6a2ba0c89a Mon Sep 17 00:00:00 2001 From: flashwave Date: Wed, 24 Apr 2024 01:12:54 +0000 Subject: [PATCH] Updated asset build script. --- assets/assproc.js | 24 ++++-- assets/utils.js | 4 + build.js | 209 ++++++++++++++++++++++++++++++---------------- 3 files changed, 157 insertions(+), 80 deletions(-) diff --git a/assets/assproc.js b/assets/assproc.js index d4144b9..bf457ca 100644 --- a/assets/assproc.js +++ b/assets/assproc.js @@ -17,15 +17,10 @@ exports.process = async function(root, options) { return ''; included.push(fullPath); - if(!fullPath.startsWith(root)) { - console.error('INVALID PATH: ' + fullPath); + if(!fullPath.startsWith(root)) return '/* *** INVALID PATH: ' + fullPath + ' */'; - } - - if(!fs.existsSync(fullPath)) { - console.error('FILE NOT FOUND: ' + fullPath); + if(!fs.existsSync(fullPath)) return '/* *** FILE NOT FOUND: ' + fullPath + ' */'; - } const lines = readline.createInterface({ input: fs.createReadStream(fullPath), @@ -58,6 +53,19 @@ exports.process = async function(root, options) { break; } + case 'buildvars': + if(typeof options.buildVars === 'object') { + const bvTarget = options.buildVarsTarget || 'window'; + const bvProps = []; + + for(const bvName in options.buildVars) + bvProps.push(`${bvName}: { value: ${JSON.stringify(options.buildVars[bvName])} }`); + + if(Object.keys(bvProps).length > 0) + output += `Object.defineProperties(${bvTarget}, { ${bvProps.join(', ')} });\n`; + } + break; + default: output += line; output += "\n"; @@ -84,7 +92,7 @@ exports.housekeep = function(assetsPath) { }; }).sort((a, b) => b.lastMod - a.lastMod).map(info => info.name); - const regex = /^(.+)-([a-f0-9]+)\.(.+)$/i; + const regex = /^(.+)[\-\.]([a-f0-9]+)\.(.+)$/i; const counts = {}; for(const fileName of files) { diff --git a/assets/utils.js b/assets/utils.js index fb7a1dd..d825d83 100644 --- a/assets/utils.js +++ b/assets/utils.js @@ -1,5 +1,9 @@ const crypto = require('crypto'); +exports.strtr = (str, replacements) => str.toString().replace( + /{([^}]+)}/g, (match, key) => replacements[key] || match +); + const trim = function(str, chars, flags) { if(chars === undefined) chars = " \n\r\t\v\0"; diff --git a/build.js b/build.js index 5a4d8a1..777f98d 100644 --- a/build.js +++ b/build.js @@ -1,3 +1,4 @@ +// IMPORTS const fs = require('fs'); const swc = require('@swc/core'); const path = require('path'); @@ -5,32 +6,57 @@ const util = require('util'); const exec = util.promisify(require('child_process').exec); const postcss = require('postcss'); const htmlminify = require('html-minifier-terser').minify; +const childProcess = require('child_process'); const utils = require('./assets/utils.js'); const assproc = require('./assets/assproc.js'); +// CONFIG const rootDir = __dirname; - -const assetsDir = path.join(rootDir, 'assets'); -const assetsInfo = path.join(assetsDir, 'current.json'); - +const srcDir = path.join(rootDir, 'assets'); +const srcCurrentInfo = path.join(srcDir, '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 pubAssetsDir = path.join(pubDir, 'assets'); 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 buildTasks = { + js: [ + { source: 'common.js', target: '/assets', name: 'common.{hash}.js', }, + { source: '2021.js', target: '/assets', name: '2021.{hash}.js', }, + { source: 'ascii.js', target: '/assets', name: 'ascii.{hash}.js', }, + { source: 'whois.js', target: '/assets', name: 'whois.{hash}.js', }, + ], + css: [ + { source: 'common.css', target: '/assets', name: 'common.{hash}.css', }, + { source: '2021.css', target: '/assets', name: '2021.{hash}.css', }, + { source: 'ascii.css', target: '/assets', name: 'ascii.{hash}.css', }, + { source: 'whois.css', target: '/assets', name: 'whois.{hash}.css', }, + ], + twig: [ + { source: 'errors/401', target: '/', name: 'error-401.html', }, + { source: 'errors/403', target: '/', name: 'error-403.html', }, + { source: 'errors/404', target: '/', name: 'error-404.html', }, + { source: 'errors/500', target: '/', name: 'error-500.html', }, + { source: 'errors/503', target: '/', name: 'error-503.html', }, + ], +}; + +// PREP +const postcssPlugins = [ require('autoprefixer')({ remove: false }) ]; +if(!isDebugBuild) + postcssPlugins.push(require('cssnano')({ + preset: [ + 'cssnano-preset-default', + { + minifyGradients: false, + reduceIdents: false, + zindex: true, + } + ], + })); const swcJscOptions = { - target: 'es2016', + target: 'es2020', loose: false, externalHelpers: false, keepClassNames: true, @@ -73,83 +99,122 @@ const htmlMinifyOptions = { sortClassName: true, }; -const postcssPlugins = []; -if(!isDebugBuild) postcssPlugins.push(require('cssnano')); -postcssPlugins.push(require('autoprefixer')({ - remove: false, -})); - -fs.mkdirSync(pubAssetsFull, { recursive: true }); +// BUILD (async () => { - console.log('Building assets...'); + const files = {}; - const dirs = fs.readdirSync(assetsDir); - const assets = {}; + console.log('Ensuring assets directory exists...'); + fs.mkdirSync(pubAssetsDir, { recursive: true }); - const _postcss = postcss(postcssPlugins); - for(const dir of dirs) { - const dirPath = path.join(assetsDir, dir); - if(!fs.statSync(dirPath).isDirectory()) - continue; + console.log(); + console.log('JS assets'); + for(const info of buildTasks.js) { + console.log(`=> Building ${info.source}...`); - 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))); + let origTarget = undefined; + if('es' in info) { + origTarget = swcJscOptions.target; + swcJscOptions.target = info.es; } - if(filePath !== '') { - assets[dir] = filePath; - fs.writeFileSync(path.join(pubDir, filePath), fileBody); - } else console.error(`Unknown type: ${parts[1]}`); + const assprocOpts = { + prefix: '#', + entry: info.entry || 'main.js', + }; + const swcOpts = { + filename: info.source, + sourceMaps: false, + isModule: false, + minify: !isDebugBuild, + jsc: swcJscOptions, + }; + + const pubName = await assproc.process(path.join(srcDir, info.source), assprocOpts) + .then(output => swc.transform(output, swcOpts)) + .then(output => { + const name = utils.strtr(info.name, { hash: utils.shortHash(output.code) }); + const pubName = path.join(info.target || '', name); + + console.log(` Saving to ${pubName}...`); + fs.writeFileSync(path.join(pubDir, pubName), output.code); + + return pubName; + }); + + if(origTarget !== undefined) + swcJscOptions.target = origTarget; + + files[info.source] = pubName; } - console.log('Writing assets info...'); - fs.writeFileSync(assetsInfo, JSON.stringify(assets)); - console.log('Housekeeping...'); - assproc.housekeep(pubAssetsFull); + console.log(); + console.log('CSS assets'); + for(const info of buildTasks.css) { + console.log(`=> Building ${info.source}...`); - console.log('Prerendering templates...'); + const sourcePath = path.join(srcDir, info.source); + const assprocOpts = { + prefix: '@', + entry: info.entry || 'main.css', + }; + const postcssOpts = { from: sourcePath }; + + files[info.source] = await assproc.process(sourcePath, assprocOpts) + .then(output => postcss(postcssPlugins).process(output, postcssOpts) + .then(output => { + const name = utils.strtr(info.name, { hash: utils.shortHash(output.css) }); + const pubName = path.join(info.target || '', name); + + console.log(` Saving to ${pubName}...`); + fs.writeFileSync(path.join(pubDir, pubName), output.css); + + return pubName; + })); + } + + + console.log(); + console.log('Twig assets'); const renderCommand = path.join(rootDir, 'tools/render-tpl'); - console.log(renderCommand); - for(const info of renderTemplates) { - console.log(); - console.log(info.in); + for(const info of buildTasks.twig) { + console.log(`=> Building ${info.source}...`); - const { stdout, stderr } = await exec(`${renderCommand} ${info.in}`); + const { stdout, stderr } = await exec(`${renderCommand} ${info.source}`); + let data = stdout; - if(stdout.trim() === '') { + if(data.trim() === '') { console.error(stderr); continue; } - let body = await htmlminify(stdout, htmlMinifyOptions); + if(!isDebugBuild) + data = await htmlminify(data, htmlMinifyOptions); - fs.writeFileSync(path.join(pubDir, info.out), body); + const name = utils.strtr(info.name, { hash: utils.shortHash(data) }); + const pubName = path.join(info.target || '', name); + + const fullPath = path.join(pubDir, pubName); + const dirPath = path.dirname(fullPath); + if(!fs.existsSync(dirPath)) + fs.mkdirSync(dirPath, { recursive: true }); + + fs.writeFileSync(fullPath, data); + + files[info.source] = pubName; } + + + console.log(); + console.log('Writing assets info...'); + fs.writeFileSync(srcCurrentInfo, JSON.stringify(files)); + + + console.log(); + console.log('Cleaning up old builds...'); + assproc.housekeep(pubAssetsDir); })();