// IMPORTS const fs = require('fs'); const swc = require('@swc/core'); const path = require('path'); const util = require('util'); const postcss = require('postcss'); const htmlminify = require('html-minifier-terser').minify; const childProcess = require('child_process'); const utils = require('./src/utils.js'); const assproc = require('./src/assproc.js'); // CONFIG const rootDir = __dirname; const configFile = path.join(rootDir, 'config/config.json'); const srcDir = path.join(rootDir, 'src'); const pubDir = path.join(rootDir, 'public'); const pubAssetsDir = path.join(pubDir, 'assets'); const isDebugBuild = fs.existsSync(path.join(rootDir, '.debug')); const buildTasks = { js: [ { source: 'ami.js', target: '/assets', name: 'ami.{hash}.js', }, ], css: [ { source: 'ami.css', target: '/assets', name: 'ami.{hash}.css', }, ], html: [ { source: 'ami.html', target: '/', name: 'index.html', }, ], }; // PREP const config = JSON.parse(fs.readFileSync(configFile)); 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: 'es5', 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, }; // BUILD (async () => { const htmlVars = { 'title': config.title }; const buildVars = { FUTAMI_DEBUG: isDebugBuild, FUTAMI_URL: config.common_url, MAMI_URL: config.modern_url, AMI_URL: config.compat_url, GIT_HASH: await (() => { return new Promise((resolve, reject) => { childProcess.exec('git log --pretty="%H" -n1 HEAD', (err, stdout) => { if(err) reject(err); else resolve(stdout.trim()); }); }); })(), }; console.log('Ensuring assets directory exists...'); fs.mkdirSync(pubAssetsDir, { recursive: true }); console.log(); console.log('JS assets'); for(const info of buildTasks.js) { console.log(`=> Building ${info.source}...`); let origTarget = undefined; if('es' in info) { origTarget = swcJscOptions.target; swcJscOptions.target = info.es; } const assprocOpts = { prefix: '#', entry: info.entry || 'main.js', buildVars: buildVars, }; 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; htmlVars[info.source] = pubName; if(typeof info.buildVar === 'string') buildVars[info.buildVar] = pubName; } console.log(); console.log('CSS assets'); for(const info of buildTasks.css) { console.log(`=> Building ${info.source}...`); const sourcePath = path.join(srcDir, info.source); const assprocOpts = { prefix: '@', entry: info.entry || 'main.css', }; const postcssOpts = { from: sourcePath }; htmlVars[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('HTML assets'); for(const info of buildTasks.html) { console.log(`=> Building ${info.source}...`); try { let data = fs.readFileSync(path.join(srcDir, info.source)); data = utils.strtr(data, htmlVars); if(!isDebugBuild) data = await htmlminify(data, htmlMinifyOptions); const name = utils.strtr(info.name, { hash: utils.shortHash(data) }); const pubName = path.join(info.target || '', name); console.log(` Saving to ${pubName}...`); const fullPath = path.join(pubDir, pubName); const dirPath = path.dirname(fullPath); if(!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true }); fs.writeFileSync(fullPath, data); htmlVars[info.source] = pubName; } catch(err) { console.error(err); } } console.log(); console.log('Cleaning up old builds...'); assproc.housekeep(pubAssetsDir); })();