UNPKG

quasar-app-extension-ssg

Version:
321 lines (268 loc) 8.77 kB
const parseArgs = require('minimist'); const { engine } = require('../api'); const argv = parseArgs(process.argv.slice(4), { alias: { p: 'port', H: 'hostname', g: 'gzip', s: 'silent', colors: 'colors', o: 'open', c: 'cache', B: ['base-url', 'prefix-path'], cors: 'cors', m: 'micro', https: 'https', C: 'cert', K: 'key', P: 'proxy', h: 'help', }, boolean: ['g', 'https', 'colors', 'h', 'cors'], string: ['H', 'C', 'K', 'B'], default: { p: process.env.PORT || 4000, H: process.env.HOSTNAME || '0.0.0.0', g: true, c: 24 * 60 * 60, m: 1, B: '/', colors: true, }, }); if (argv.help) { console.log(` Description Start a HTTP(S) server on a folder. Usage $ quasar ssg serve [path] $ quasar ssg serve . # serve current folder Options --port, -p Port to use (default: 4000) --hostname, -H Address to use (default: 0.0.0.0) --base-url, -B Serve at base public path (default: /) --gzip, -g Compress content (default: true) --silent, -s Suppress log message --colors Log messages with colors (default: true) --open, -o Open browser window after starting --cache, -c <number> Cache time (max-age) in seconds; Does not apply to /service-worker.js (default: 86400 - 24 hours) --micro, -m <seconds> Use micro-cache (default: 1 second) --https Enable HTTPS --cert, -C [path] Path to SSL cert file (Optional) --key, -K [path] Path to SSL key file (Optional) --proxy <file.js> Proxy specific requests defined in file; File must export Array ({ path, rule }) See example below. "rule" is defined at: https://github.com/chimurai/http-proxy-middleware --cors Enable CORS for all requests --help, -h Displays this message Proxy file example module.exports = [ { path: '/api', rule: { target: 'http://www.example.org' } } ] --> will be transformed into app.use(path, httpProxyMiddleware(rule)) `); process.exit(0); } (async () => { const { join, resolve, isAbsolute, basename, posix, } = require('path'); const { format } = require('util'); const { existsSync } = require('fs'); const { readFile } = require('fs/promises'); const express = require('express'); const ssgPkg = require('../../package.json'); function getAbsolutePath(p) { return isAbsolute(p) ? p : join(process.cwd(), p); } const root = getAbsolutePath(argv._[0] || '.'); const resolveUrlPath = (url) => resolve(root, url); const baseUrl = posix.join('/', argv['base-url']); // enforce Posix path if (!argv.colors) { process.env.FORCE_COLOR = '0'; } const { green, gray, red } = require('kolorist'); const microCacheSeconds = argv.micro ? parseInt(argv.micro, 10) : false; function serve(url, cache) { const opts = { maxAge: cache ? parseInt(argv.cache, 10) * 1000 : 0, setHeaders(res) { if (res.req.method === 'GET' && url.endsWith('.html')) { res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); res.set('Pragma', 'no-cache'); res.set('Expires', '0'); res.set('Surrogate-Control', 'no-store'); } }, extensions: ['html'], }; return express.static(resolveUrlPath(url), opts); } const app = express(); if (argv.cors) { const cors = require('cors'); app.use(cors()); } if (!argv.silent) { app.get('*', (req, _, next) => { console.log( ` GET ${green(req.url)} ${gray(`[${req.ip}]`)} ${new Date()}`, ); next(); }); } if (argv.gzip) { const compression = require('compression'); app.use(compression({ threshold: 0 })); } const serviceWorkerFile = resolveUrlPath('service-worker.js'); if (existsSync(serviceWorkerFile)) { app.use(posix.join(baseUrl, 'service-worker.js'), serve('service-worker.js')); } if (argv.proxy) { argv.proxy = getAbsolutePath(argv.proxy); let file = argv.proxy; if (!existsSync(file)) { console.error(`Proxy definition file not found! ${file}`); process.exit(1); } file = require(file); const { createProxyMiddleware } = require('http-proxy-middleware'); file.forEach((entry) => { app.use(entry.path, createProxyMiddleware(entry.rule)); }); } app.use(baseUrl, serve('.', true)); const { globby } = await import('globby'); const [fallbackFile] = await globby(resolveUrlPath('./*.html'), { ignore: [resolveUrlPath('./index.html')] }); if (fallbackFile) { app.use(baseUrl, (req, res, next) => { const ext = posix.extname(req.url) || '.html'; if (ext !== '.html') { return next(); } res.setHeader('Content-Type', 'text/html'); if (fallbackFile.endsWith('404.html')) { res.status(404); if (!argv.silent) { console.log(red(` 404 on ${req.url}`)); } } else { res.status(200); } return res.sendFile(fallbackFile); }); } if (microCacheSeconds) { const microcache = require('route-cache'); app.use( microcache.cacheSeconds( microCacheSeconds, (req) => req.originalUrl, ), ); } app.get('*', (req, res) => { res.setHeader('Content-Type', 'text/html'); res.status(404).send('404 | Page Not Found'); if (!argv.silent) { console.log(red(` 404 on ${req.url}`)); } }); async function getServer() { if (!argv.https) { return app; } let fakeCert; let key; let cert; if (argv.key && argv.cert) { key = getAbsolutePath(argv.key); cert = getAbsolutePath(argv.cert); if (existsSync(key)) { key = await readFile(key); } else { console.error(`SSL key file not found!${key}`); process.exit(1); } if (existsSync(cert)) { cert = await readFile(cert); } else { console.error(`SSL cert file not found!${cert}`); process.exit(1); } } else { const { getCertificate } = await import('@quasar/ssl-certificate'); fakeCert = getCertificate({ log: (msg) => { console.log(msg ? ` ${format(msg)}` : ''); }, fatal: (msg) => { console.error(msg ? `\n ⚠️ ${format(msg)}\n` : ''); process.exit(1); }, }); } return require('https').createServer({ key: key || fakeCert, cert: cert || fakeCert, }, app); } const server = await getServer(); /** @type { import('<%= engine %>/lib/helpers/net')} */ const { getIPs } = require(`${engine}/lib/helpers/net`); const getListeningUrl = (hostname) => `http${argv.https ? 's' : ''}://${hostname}:${argv.port}`; const getListeningBanner = () => { let { hostname } = argv; if (hostname === '0.0.0.0') { const acc = getIPs().map((ip) => (['', getListeningUrl(ip)])); if (acc.length !== 0) { acc[0][0] = 'Listening at'; return acc; } hostname = 'localhost'; } return [ ['Listening at', getListeningUrl(hostname)], ]; }; server.listen(argv.port, argv.hostname, async () => { const filler = ''.padEnd(20, ' '); const listeningBanner = getListeningBanner(); const info = [ ['SSG Extension', `v${ssgPkg.version}`], process.env.QUASAR_CLI_VERSION ? ['Quasar CLI', `v${process.env.QUASAR_CLI_VERSION}`] : '', ...listeningBanner, ['Base url', baseUrl], ['Web server root', root], fallbackFile ? ['SPA fallback', basename(fallbackFile)] : '', argv.https ? ['HTTPS', 'enabled'] : '', argv.gzip ? ['Gzip', 'enabled'] : '', ['Cache (max-age)', argv.cache || 'disabled'], microCacheSeconds ? ['Micro-cache', `${microCacheSeconds}s`] : '', argv.cors ? ['CORS', 'enabled'] : '', argv.proxy ? ['Proxy definitions', argv.proxy] : '', ] .filter((msg) => msg) .map((msg) => ` ${msg[0] !== '' ? msg[0].padEnd(20, '.') : filler} ${green(msg[1])}`); console.log(`\n${info.join('\n')}\n`); if (argv.open) { /** @type { import('<%= engine %>/lib/helpers/is-minimal-terminal')} */ const isMinimalTerminal = require(`${engine}/lib/helpers/is-minimal-terminal`); if (!isMinimalTerminal) { const open = (await import('open')).default; await open(listeningBanner[0][1] + baseUrl); } } }); })();