UNPKG

@discoveryjs/cli

Version:

CLI tools to serve & build projects based on Discovery.js

135 lines (112 loc) 4.45 kB
const fs = require('fs'); const path = require('path'); const mime = require('mime'); const makeBundle = require('../shared/bundle'); const configUtils = require('../shared/config'); const { logMsg, logError } = require('../shared/utils'); const refreshConfigMap = new WeakMap(); async function alwaysFreshConfig(config, options) { if (!refreshConfigMap.has(config)) { refreshConfigMap.set(config, { requireCacheDigest: Object.create(null), lastRequireCacheCheck: 0, config }); } const cfg = refreshConfigMap.get(config); const newDigest = Object.create(null); let changed = false; // don't check if last check was less than 1 second ago if (Date.now() - cfg.lastRequireCacheCheck < 1000) { return cfg.config; } // avoid update on first check if (cfg.lastRequireCacheCheck === 0) { cfg.requireCacheDigest = newDigest; } // check modules in cache is changed for (const ref of [...new Set([...Object.keys(require.cache), ...Object.keys(cfg.requireCacheDigest)])]) { const { mtime } = fs.statSync(ref); newDigest[ref] = Number(mtime); if (cfg.requireCacheDigest[ref] !== Number(mtime)) { changed = true; } } cfg.lastRequireCacheCheck = Date.now(); if (changed) { // drop require cache for (const ref of Object.keys(require.cache)) { delete require.cache[ref]; } logMsg('Config change detected, reload config...'); // load fresh config and update configuration try { const { config } = await configUtils.loadConfigWithFallback(options); cfg.config = config; cfg.requireCacheDigest = newDigest; } catch (e) { logError('Config load failed', e); throw e; } } // fresh config or the same if nothing changed return cfg.config; } async function responseBundleAsset(res, next, type, content) { try { const resContent = await content; res.set('Content-Type', type); res.send(resContent); } catch (e) { next(e); } } module.exports = function addAssetRoutes(router, name, config, options, modelConfig, cacheDispatcher) { let currentBundles = new Map(); const getAsset = async (type) => { const filename = (modelConfig ? modelConfig.slug + '/' : '') + type.replace(/\[name\]/g, modelConfig ? 'model' : 'index'); const fileRef = filename.replace(/\.map$/, ''); let currentBundle = currentBundles.get(fileRef); if (!currentBundle || !/\.map$/.test(type)) { currentBundle = makeBundle(await alwaysFreshConfig(config, options), { ...options, serveOnlyAssets: true }, { outdir: '/', // FIXME: Disable incremental build for now, since it's as twice as slower // incremental: true, minify: typeof options.minify === 'boolean' ? options.minify : true, sourcemap: true }, { cacheDispatcher, filter: entrypoint => entrypoint === fileRef }); currentBundles.set(fileRef, currentBundle); } const { outputFiles } = await currentBundle; const outputFile = outputFiles.find(file => path.basename(file.path) === path.basename(filename) ); return outputFile.text; }; const addAssetRoute = (filepath, assetpath = filepath) => { router.get(`/${filepath}`, (_, res, next) => responseBundleAsset(res, next, mime.getType(path.extname(filepath)), getAsset(assetpath)) ); router.get(`/${filepath}.map`, (_, res, next) => responseBundleAsset(res, next, 'application/json', getAsset(assetpath + '.map')) ); }; addAssetRoute(`${name}.js`, '[name].js'); addAssetRoute(`${name}.css`, '[name].css'); addAssetRoute(`${name}-loader.js`, '[name]-loader.js'); addAssetRoute(`${name}-loader.css`, '[name]-loader.css'); if (modelConfig && modelConfig.view && modelConfig.view.bundles) { for (const relpath of Object.keys(modelConfig.view.bundles)) { switch (path.extname(relpath)) { case '.js': case '.css': addAssetRoute(relpath); break; } } } };