From 1fd2eba6f279b606cc52858511b8a83ae2180939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Wed, 14 Oct 2020 22:49:39 -0400 Subject: [PATCH] refactor: async/await src/cli/manage.js src/meta/build.js src/meta/css.js src/meta/js.js --- src/cli/manage.js | 217 +++++++++-------------- src/meta/build.js | 186 +++++++++----------- src/meta/css.js | 205 ++++++++------------- src/meta/js.js | 381 ++++++++++++++++------------------------ src/meta/minifier.js | 32 ++-- src/middleware/index.js | 3 +- 6 files changed, 407 insertions(+), 617 deletions(-) diff --git a/src/cli/manage.js b/src/cli/manage.js index 69d2902ad72d..94f185f5b44e 100644 --- a/src/cli/manage.js +++ b/src/cli/manage.js @@ -1,6 +1,5 @@ 'use strict'; -const async = require('async'); const winston = require('winston'); const childProcess = require('child_process'); const _ = require('lodash'); @@ -35,59 +34,40 @@ function buildTargets() { ); } -function activate(plugin) { +async function activate(plugin) { if (themeNamePattern.test(plugin)) { - reset.reset({ + await reset.reset({ theme: plugin, - }, function (err) { - if (err) { throw err; } - process.exit(); }); - return; + process.exit(); } - - async.waterfall([ - function (next) { - db.init(next); - }, - function (next) { - if (!pluginNamePattern.test(plugin)) { - // Allow omission of `nodebb-plugin-` - plugin = 'nodebb-plugin-' + plugin; - } - plugins.isInstalled(plugin, next); - }, - function (isInstalled, next) { - if (!isInstalled) { - return next(new Error('plugin not installed')); - } - plugins.isActive(plugin, next); - }, - function (isActive, next) { - if (isActive) { - winston.info('Plugin `%s` already active', plugin); - process.exit(0); - } - - db.sortedSetCard('plugins:active', next); - }, - function (numPlugins, next) { - winston.info('Activating plugin `%s`', plugin); - db.sortedSetAdd('plugins:active', numPlugins, plugin, next); - }, - function (next) { - events.log({ - type: 'plugin-activate', - text: plugin, - }, next); - }, - ], function (err) { - if (err) { - winston.error('An error occurred during plugin activation', err.stack); - throw err; + try { + await db.init(); + if (!pluginNamePattern.test(plugin)) { + // Allow omission of `nodebb-plugin-` + plugin = 'nodebb-plugin-' + plugin; + } + const isInstalled = await plugins.isInstalled(plugin); + if (!isInstalled) { + throw new Error('plugin not installed'); + } + const isActive = await plugins.isActive(plugin); + if (isActive) { + winston.info('Plugin `%s` already active', plugin); + process.exit(0); } + const numPlugins = await db.sortedSetCard('plugins:active'); + winston.info('Activating plugin `%s`', plugin); + await db.sortedSetAdd('plugins:active', numPlugins, plugin); + await events.log({ + type: 'plugin-activate', + text: plugin, + }); process.exit(0); - }); + } catch (err) { + winston.error('An error occurred during plugin activation', err.stack); + throw err; + } } async function listPlugins() { @@ -125,103 +105,74 @@ async function listPlugins() { process.exit(); } -function listEvents(count) { - async.waterfall([ - function (next) { - db.init(next); - }, - async.apply(events.getEvents, '', 0, (count || 10) - 1), - function (eventData) { - console.log(('\nDisplaying last ' + count + ' administrative events...').bold); - eventData.forEach(function (event) { - console.log(' * ' + String(event.timestampISO).green + ' ' + String(event.type).yellow + (event.text ? ' ' + event.text : '') + ' (uid: '.reset + (event.uid ? event.uid : 0) + ')'); - }); - process.exit(); - }, - ], function (err) { - throw err; +async function listEvents(count) { + await db.init(); + const eventData = await events.getEvents('', 0, (count || 10) - 1); + console.log(('\nDisplaying last ' + count + ' administrative events...').bold); + eventData.forEach(function (event) { + console.log(' * ' + String(event.timestampISO).green + ' ' + String(event.type).yellow + (event.text ? ' ' + event.text : '') + ' (uid: '.reset + (event.uid ? event.uid : 0) + ')'); }); + process.exit(); } -function info() { +async function info() { console.log(''); - async.waterfall([ - function (next) { - var version = require('../../package.json').version; - console.log(' version: ' + version); + const version = require('../../package.json').version; + console.log(' version: ' + version); - console.log(' Node ver: ' + process.version); - next(); - }, - function (next) { - var hash = childProcess.execSync('git rev-parse HEAD'); - console.log(' git hash: ' + hash); - next(); - }, - function (next) { - var config = require('../../config.json'); - console.log(' database: ' + config.database); - next(); - }, - function (next) { - db.init(next); - }, - function (next) { - db.info(db.client, next); - }, - function (info, next) { - var config = require('../../config.json'); - - switch (config.database) { - case 'redis': - console.log(' version: ' + info.redis_version); - console.log(' disk sync: ' + info.rdb_last_bgsave_status); - break; - - case 'mongo': - console.log(' version: ' + info.version); - console.log(' engine: ' + info.storageEngine); - break; - } - - next(); - }, - async.apply(analytics.getHourlyStatsForSet, 'analytics:pageviews', Date.now(), 24), - function (data, next) { - var graph = new CliGraph({ - height: 12, - width: 25, - center: { - x: 0, - y: 11, - }, - }); - var min = Math.min(...data); - var max = Math.max(...data); + console.log(' Node ver: ' + process.version); - data.forEach(function (point, idx) { - graph.addPoint(idx + 1, Math.round(point / max * 10)); - }); + const hash = childProcess.execSync('git rev-parse HEAD'); + console.log(' git hash: ' + hash); + + const config = require('../../config.json'); + console.log(' database: ' + config.database); - console.log(''); - console.log(graph.toString()); - console.log('Pageviews, last 24h (min: ' + min + ' max: ' + max + ')'); - next(); + await db.init(); + const info = await db.info(db.client); + + switch (config.database) { + case 'redis': + console.log(' version: ' + info.redis_version); + console.log(' disk sync: ' + info.rdb_last_bgsave_status); + break; + + case 'mongo': + console.log(' version: ' + info.version); + console.log(' engine: ' + info.storageEngine); + break; + } + + const analyticsData = await analytics.getHourlyStatsForSet('analytics:pageviews', Date.now(), 24); + const graph = new CliGraph({ + height: 12, + width: 25, + center: { + x: 0, + y: 11, }, - ], function (err) { - if (err) { throw err; } - process.exit(); }); + const min = Math.min(...analyticsData); + const max = Math.max(...analyticsData); + + analyticsData.forEach(function (point, idx) { + graph.addPoint(idx + 1, Math.round(point / max * 10)); + }); + + console.log(''); + console.log(graph.toString()); + console.log('Pageviews, last 24h (min: ' + min + ' max: ' + max + ')'); + process.exit(); } -function buildWrapper(targets, options) { - build.build(targets, options, function (err) { - if (err) { - winston.error(err.stack); - process.exit(1); - } +async function buildWrapper(targets, options) { + try { + await build.build(targets, options); process.exit(0); - }); + } catch (err) { + winston.error(err.stack); + process.exit(1); + } } exports.build = buildWrapper; diff --git a/src/meta/build.js b/src/meta/build.js index 4e20f94824fd..03b98a5c53d6 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -1,7 +1,6 @@ 'use strict'; const os = require('os'); -const async = require('async'); const winston = require('winston'); const nconf = require('nconf'); const _ = require('lodash'); @@ -9,35 +8,18 @@ const _ = require('lodash'); const cacheBuster = require('./cacheBuster'); let meta; -function step(target, callback) { - var startTime = Date.now(); - winston.info('[build] ' + target + ' build started'); - - return function (err) { - if (err) { - winston.error('[build] ' + target + ' build failed'); - return callback(err); - } - - var time = (Date.now() - startTime) / 1000; - - winston.info('[build] ' + target + ' build completed in ' + time + 'sec'); - callback(); - }; -} - -var targetHandlers = { - 'plugin static dirs': function (parallel, callback) { - meta.js.linkStatics(callback); +const targetHandlers = { + 'plugin static dirs': async function () { + await meta.js.linkStatics(); }, - 'requirejs modules': function (parallel, callback) { - meta.js.buildModules(parallel, callback); + 'requirejs modules': async function (parallel) { + await meta.js.buildModules(parallel); }, - 'client js bundle': function (parallel, callback) { - meta.js.buildBundle('client', parallel, callback); + 'client js bundle': async function (parallel) { + await meta.js.buildBundle('client', parallel); }, - 'admin js bundle': function (parallel, callback) { - meta.js.buildBundle('admin', parallel, callback); + 'admin js bundle': async function (parallel) { + await meta.js.buildBundle('admin', parallel); }, javascript: [ 'plugin static dirs', @@ -45,25 +27,25 @@ var targetHandlers = { 'client js bundle', 'admin js bundle', ], - 'client side styles': function (parallel, callback) { - meta.css.buildBundle('client', parallel, callback); + 'client side styles': async function (parallel) { + await meta.css.buildBundle('client', parallel); }, - 'admin control panel styles': function (parallel, callback) { - meta.css.buildBundle('admin', parallel, callback); + 'admin control panel styles': async function (parallel) { + await meta.css.buildBundle('admin', parallel); }, styles: [ 'client side styles', 'admin control panel styles', ], - templates: function (parallel, callback) { - meta.templates.compile(callback); + templates: async function () { + await meta.templates.compile(); }, - languages: function (parallel, callback) { - meta.languages.build(callback); + languages: async function () { + await meta.languages.build(); }, }; -var aliases = { +let aliases = { 'plugin static dirs': ['staticdirs'], 'requirejs modules': ['rjs', 'modules'], 'client js bundle': ['clientjs', 'clientscript', 'clientscripts'], @@ -91,53 +73,59 @@ aliases = Object.keys(aliases).reduce(function (prev, key) { return prev; }, {}); -function beforeBuild(targets, callback) { - var db = require('../database'); +async function beforeBuild(targets) { + const db = require('../database'); require('colors'); process.stdout.write(' started'.green + '\n'.reset); - - async.series([ - function (next) { - db.init(next); - }, - function (next) { - meta = require('./index'); - meta.themes.setupPaths(next); - }, - function (next) { - var plugins = require('../plugins'); - plugins.prepareForBuild(targets, next); - }, - ], function (err) { - if (err) { - winston.error('[build] Encountered error preparing for build\n' + err.stack); - return callback(err); - } - - callback(); - }); + try { + await db.init(); + meta = require('./index'); + await meta.themes.setupPaths(); + const plugins = require('../plugins'); + await plugins.prepareForBuild(targets); + } catch (err) { + winston.error('[build] Encountered error preparing for build\n' + err.stack); + throw err; + } } -var allTargets = Object.keys(targetHandlers).filter(function (name) { +const allTargets = Object.keys(targetHandlers).filter(function (name) { return typeof targetHandlers[name] === 'function'; }); -function buildTargets(targets, parallel, callback) { - var all = parallel ? async.each : async.eachSeries; - var length = Math.max.apply(Math, targets.map(function (name) { - return name.length; - })); +async function buildTargets(targets, parallel) { + const length = Math.max.apply(Math, targets.map(name => name.length)); + + if (parallel) { + await Promise.all( + targets.map( + target => step(target, parallel, _.padStart(target, length) + ' ') + ) + ); + } else { + for (const target of targets) { + // eslint-disable-next-line no-await-in-loop + await step(target, parallel, _.padStart(target, length) + ' '); + } + } +} - all(targets, function (target, next) { - targetHandlers[target](parallel, step(_.padStart(target, length) + ' ', next)); - }, callback); +async function step(target, parallel, targetStr) { + const startTime = Date.now(); + winston.info('[build] ' + targetStr + ' build started'); + try { + await targetHandlers[target](parallel); + const time = (Date.now() - startTime) / 1000; + + winston.info('[build] ' + targetStr + ' build completed in ' + time + 'sec'); + } catch (err) { + winston.error('[build] ' + targetStr + ' build failed'); + throw err; + } } -exports.build = function (targets, options, callback) { - if (!callback && typeof options === 'function') { - callback = options; - options = {}; - } else if (!options) { +exports.build = async function (targets, options) { + if (!options) { options = {}; } @@ -186,45 +174,35 @@ exports.build = function (targets, options, callback) { if (!targets) { winston.info('[build] No valid targets supplied. Aborting.'); - callback(); + return; } - var startTime; - var totalTime; - async.series([ - async.apply(beforeBuild, targets), - function (next) { - var threads = parseInt(nconf.get('threads'), 10); - if (threads) { - require('./minifier').maxThreads = threads - 1; - } - - if (!series) { - winston.info('[build] Building in parallel mode'); - } else { - winston.info('[build] Building in series mode'); - } + try { + await beforeBuild(targets); + const threads = parseInt(nconf.get('threads'), 10); + if (threads) { + require('./minifier').maxThreads = threads - 1; + } - startTime = Date.now(); - buildTargets(targets, !series, next); - }, - function (next) { - totalTime = (Date.now() - startTime) / 1000; - cacheBuster.write(next); - }, - ], function (err) { - if (err) { - winston.error('[build] Encountered error during build step\n' + (err.stack ? err.stack : err)); - return callback(err); + if (!series) { + winston.info('[build] Building in parallel mode'); + } else { + winston.info('[build] Building in series mode'); } + const startTime = Date.now(); + await buildTargets(targets, !series); + const totalTime = (Date.now() - startTime) / 1000; + await cacheBuster.write(); winston.info('[build] Asset compilation successful. Completed in ' + totalTime + 'sec.'); - callback(); - }); + } catch (err) { + winston.error('[build] Encountered error during build step\n' + (err.stack ? err.stack : err)); + throw err; + } }; -exports.buildAll = function (callback) { - exports.build(allTargets, callback); +exports.buildAll = async function () { + await exports.build(allTargets); }; require('../promisify')(exports); diff --git a/src/meta/css.js b/src/meta/css.js index bf9cae0178fb..4a58f3c06f99 100644 --- a/src/meta/css.js +++ b/src/meta/css.js @@ -1,18 +1,19 @@ 'use strict'; -var winston = require('winston'); -var nconf = require('nconf'); -var fs = require('fs'); -var path = require('path'); -var async = require('async'); -var rimraf = require('rimraf'); +const winston = require('winston'); +const nconf = require('nconf'); +const fs = require('fs'); +const util = require('util'); +const path = require('path'); +const rimraf = require('rimraf'); +const rimrafAsync = util.promisify(rimraf); -var plugins = require('../plugins'); -var db = require('../database'); -var file = require('../file'); -var minifier = require('./minifier'); +const plugins = require('../plugins'); +const db = require('../database'); +const file = require('../file'); +const minifier = require('./minifier'); -var CSS = module.exports; +const CSS = module.exports; CSS.supportedSkins = [ 'cerulean', 'cyborg', 'flatly', 'journal', 'lumen', 'paper', 'simplex', @@ -20,7 +21,7 @@ CSS.supportedSkins = [ 'slate', 'superhero', 'yeti', ]; -var buildImports = { +const buildImports = { client: function (source) { return '@import "./theme";\n' + source + '\n' + [ '@import "font-awesome";', @@ -50,21 +51,22 @@ var buildImports = { }, }; -function filterMissingFiles(filepaths, callback) { - async.filter(filepaths, function (filepath, next) { - file.exists(path.join(__dirname, '../../node_modules', filepath), function (err, exists) { +async function filterMissingFiles(filepaths) { + const exists = await Promise.all( + filepaths.map(async (filepath) => { + const exists = await file.exists(path.join(__dirname, '../../node_modules', filepath)); if (!exists) { winston.warn('[meta/css] File not found! ' + filepath); } - - next(err, exists); - }); - }, callback); + return exists; + }) + ); + return filepaths.filter((filePath, i) => exists[i]); } -function getImports(files, prefix, extension, callback) { - var pluginDirectories = []; - var source = ''; +async function getImports(files, prefix, extension) { + const pluginDirectories = []; + let source = ''; files.forEach(function (styleFile) { if (styleFile.endsWith(extension)) { @@ -73,26 +75,17 @@ function getImports(files, prefix, extension, callback) { pluginDirectories.push(styleFile); } }); - - async.each(pluginDirectories, function (directory, next) { - file.walk(directory, function (err, styleFiles) { - if (err) { - return next(err); - } - - styleFiles.forEach(function (styleFile) { - source += prefix + path.sep + styleFile + '";'; - }); - - next(); + await Promise.all(pluginDirectories.map(async function (directory) { + const styleFiles = await file.walk(directory); + styleFiles.forEach(function (styleFile) { + source += prefix + path.sep + styleFile + '";'; }); - }, function (err) { - callback(err, source); - }); + })); + return source; } -function getBundleMetadata(target, callback) { - var paths = [ +async function getBundleMetadata(target) { + const paths = [ path.join(__dirname, '../../node_modules'), path.join(__dirname, '../../public/less'), path.join(__dirname, '../../public/vendor/fontawesome/less'), @@ -107,106 +100,48 @@ function getBundleMetadata(target, callback) { target = 'client'; } } + let skinImport = []; + if (target === 'client') { + const themeData = await db.getObjectFields('config', ['theme:type', 'theme:id', 'bootswatchSkin']); + const themeId = (themeData['theme:id'] || 'nodebb-theme-persona'); + const baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')); + paths.unshift(baseThemePath); + + themeData.bootswatchSkin = skin || themeData.bootswatchSkin; + if (themeData && themeData.bootswatchSkin) { + skinImport.push('\n@import "./@nodebb/bootswatch/' + themeData.bootswatchSkin + '/variables.less";'); + skinImport.push('\n@import "./@nodebb/bootswatch/' + themeData.bootswatchSkin + '/bootswatch.less";'); + } + skinImport = skinImport.join(''); + } - async.waterfall([ - function (next) { - if (target !== 'client') { - return next(null, null); - } + const [lessImports, cssImports, acpLessImports] = await Promise.all([ + moo(plugins.lessFiles, '\n@import ".', '.less'), + moo(plugins.cssFiles, '\n@import (inline) ".', '.css'), + target === 'client' ? '' : moo(plugins.acpLessFiles, '\n@import ".', '.less'), + ]); - db.getObjectFields('config', ['theme:type', 'theme:id', 'bootswatchSkin'], next); - }, - function (themeData, next) { - if (target === 'client') { - var themeId = (themeData['theme:id'] || 'nodebb-theme-persona'); - var baseThemePath = path.join(nconf.get('themes_path'), (themeData['theme:type'] && themeData['theme:type'] === 'local' ? themeId : 'nodebb-theme-vanilla')); - paths.unshift(baseThemePath); + async function moo(files, prefix, extension) { + const filteredFiles = await filterMissingFiles(files); + return await getImports(filteredFiles, prefix, extension); + } - themeData.bootswatchSkin = skin || themeData.bootswatchSkin; - } + let imports = skinImport + '\n' + cssImports + '\n' + lessImports + '\n' + acpLessImports; + imports = buildImports[target](imports); - async.parallel({ - less: function (cb) { - async.waterfall([ - function (next) { - filterMissingFiles(plugins.lessFiles, next); - }, - function (lessFiles, next) { - getImports(lessFiles, '\n@import ".', '.less', next); - }, - ], cb); - }, - acpLess: function (cb) { - if (target === 'client') { - return cb(null, ''); - } - - async.waterfall([ - function (next) { - filterMissingFiles(plugins.acpLessFiles, next); - }, - function (acpLessFiles, next) { - getImports(acpLessFiles, '\n@import ".', '.less', next); - }, - ], cb); - }, - css: function (cb) { - async.waterfall([ - function (next) { - filterMissingFiles(plugins.cssFiles, next); - }, - function (cssFiles, next) { - getImports(cssFiles, '\n@import (inline) ".', '.css', next); - }, - ], cb); - }, - skin: function (cb) { - const skinImport = []; - if (themeData && themeData.bootswatchSkin) { - skinImport.push('\n@import "./@nodebb/bootswatch/' + themeData.bootswatchSkin + '/variables.less";'); - skinImport.push('\n@import "./@nodebb/bootswatch/' + themeData.bootswatchSkin + '/bootswatch.less";'); - } - - cb(null, skinImport.join('')); - }, - }, next); - }, - function (result, next) { - var skinImport = result.skin; - var cssImports = result.css; - var lessImports = result.less; - var acpLessImports = result.acpLess; - - var imports = skinImport + '\n' + cssImports + '\n' + lessImports + '\n' + acpLessImports; - imports = buildImports[target](imports); - - next(null, { paths: paths, imports: imports }); - }, - ], callback); + return { paths: paths, imports: imports }; } -CSS.buildBundle = function (target, fork, callback) { - async.waterfall([ - function (next) { - if (target === 'client') { - rimraf(path.join(__dirname, '../../build/public/client*'), next); - } else { - setImmediate(next); - } - }, - function (next) { - getBundleMetadata(target, next); - }, - function (data, next) { - var minify = process.env.NODE_ENV !== 'development'; - minifier.css.bundle(data.imports, data.paths, minify, fork, next); - }, - function (bundle, next) { - var filename = target + '.css'; - - fs.writeFile(path.join(__dirname, '../../build/public', filename), bundle.code, function (err) { - next(err, bundle.code); - }); - }, - ], callback); +CSS.buildBundle = async function (target, fork) { + if (target === 'client') { + await rimrafAsync(path.join(__dirname, '../../build/public/client*')); + } + + const data = await getBundleMetadata(target); + const minify = process.env.NODE_ENV !== 'development'; + const bundle = await minifier.css.bundle(data.imports, data.paths, minify, fork); + + const filename = target + '.css'; + await fs.promises.writeFile(path.join(__dirname, '../../build/public', filename), bundle.code); + return bundle.code; }; diff --git a/src/meta/js.js b/src/meta/js.js index 28be889c4c67..17c301726b1a 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -1,25 +1,22 @@ 'use strict'; -var path = require('path'); -var async = require('async'); -var fs = require('fs'); +const path = require('path'); +const fs = require('fs'); const util = require('util'); -var mkdirp = require('mkdirp'); -var mkdirpCallback; -if (mkdirp.hasOwnProperty('native')) { - mkdirpCallback = util.callbackify(mkdirp); -} else { - mkdirpCallback = mkdirp; +let mkdirp = require('mkdirp'); +// TODO: remove in 1.16.0 +if (!mkdirp.hasOwnProperty('native')) { mkdirp = util.promisify(mkdirp); } -var rimraf = require('rimraf'); +const rimraf = require('rimraf'); +const rimrafAsync = util.promisify(rimraf); -var file = require('../file'); -var plugins = require('../plugins'); -var minifier = require('./minifier'); +const file = require('../file'); +const plugins = require('../plugins'); +const minifier = require('./minifier'); -var JS = module.exports; +const JS = module.exports; JS.scripts = { base: [ @@ -109,93 +106,74 @@ JS.scripts = { }, }; -function linkIfLinux(srcPath, destPath, next) { +async function linkIfLinux(srcPath, destPath) { if (process.platform === 'win32') { - fs.copyFile(srcPath, destPath, next); + await fs.promises.copyFile(srcPath, destPath); } else { - file.link(srcPath, destPath, true, next); + await file.link(srcPath, destPath, true); } } -var basePath = path.resolve(__dirname, '../..'); +const basePath = path.resolve(__dirname, '../..'); -function minifyModules(modules, fork, callback) { - var moduleDirs = modules.reduce(function (prev, mod) { - var dir = path.resolve(path.dirname(mod.destPath)); +async function minifyModules(modules, fork) { + const moduleDirs = modules.reduce(function (prev, mod) { + const dir = path.resolve(path.dirname(mod.destPath)); if (!prev.includes(dir)) { prev.push(dir); } return prev; }, []); - async.each(moduleDirs, mkdirpCallback, function (err) { - if (err) { - return callback(err); - } + await Promise.all(moduleDirs.map(dir => mkdirp(dir))); - var filtered = modules.reduce(function (prev, mod) { - if (mod.srcPath.endsWith('.min.js') || path.dirname(mod.srcPath).endsWith('min')) { - prev.skip.push(mod); - } else { - prev.minify.push(mod); - } + const filtered = modules.reduce(function (prev, mod) { + if (mod.srcPath.endsWith('.min.js') || path.dirname(mod.srcPath).endsWith('min')) { + prev.skip.push(mod); + } else { + prev.minify.push(mod); + } - return prev; - }, { minify: [], skip: [] }); + return prev; + }, { minify: [], skip: [] }); - async.parallel([ - function (cb) { - minifier.js.minifyBatch(filtered.minify, fork, cb); - }, - function (cb) { - async.each(filtered.skip, function (mod, next) { - linkIfLinux(mod.srcPath, mod.destPath, next); - }, cb); - }, - ], callback); - }); + await Promise.all([ + minifier.js.minifyBatch(filtered.minify, fork), + ...filtered.skip.map(mod => linkIfLinux(mod.srcPath, mod.destPath)), + ]); } -function linkModules(callback) { - var modules = JS.scripts.modules; +async function linkModules() { + const modules = JS.scripts.modules; - async.each(Object.keys(modules), function (relPath, next) { - var srcPath = path.join(__dirname, '../../', modules[relPath]); - var destPath = path.join(__dirname, '../../build/public/src/modules', relPath); + await Promise.all(Object.keys(modules).map(async function (relPath) { + const srcPath = path.join(__dirname, '../../', modules[relPath]); + const destPath = path.join(__dirname, '../../build/public/src/modules', relPath); + const [stats] = await Promise.all([ + fs.promises.stat(srcPath), + mkdirp(path.dirname(destPath)), + ]); - async.parallel({ - dir: function (cb) { - mkdirpCallback(path.dirname(destPath), function (err) { - cb(err); - }); - }, - stats: function (cb) { - fs.stat(srcPath, cb); - }, - }, function (err, res) { - if (err) { - return next(err); - } - if (res.stats.isDirectory()) { - return file.linkDirs(srcPath, destPath, true, next); - } - - linkIfLinux(srcPath, destPath, next); - }); - }, callback); + if (stats.isDirectory()) { + await file.linkDirs(srcPath, destPath, true); + return; + } + + await linkIfLinux(srcPath, destPath); + })); } -var moduleDirs = ['modules', 'admin', 'client']; +const moduleDirs = ['modules', 'admin', 'client']; -function getModuleList(callback) { - var modules = Object.keys(JS.scripts.modules).map(function (relPath) { +async function getModuleList() { + let modules = Object.keys(JS.scripts.modules).map(function (relPath) { return { srcPath: path.join(__dirname, '../../', JS.scripts.modules[relPath]), destPath: path.join(__dirname, '../../build/public/src/modules', relPath), }; }); - var coreDirs = moduleDirs.map(function (dir) { + const coreDirs = moduleDirs.map(function (dir) { return { srcPath: path.join(__dirname, '../../public/src', dir), destPath: path.join(__dirname, '../../build/public/src', dir), @@ -204,75 +182,55 @@ function getModuleList(callback) { modules = modules.concat(coreDirs); - var moduleFiles = []; - async.each(modules, function (module, next) { - var srcPath = module.srcPath; - var destPath = module.destPath; - - fs.stat(srcPath, function (err, stats) { - if (err) { - return next(err); - } - if (!stats.isDirectory()) { - moduleFiles.push(module); - return next(); - } - - file.walk(srcPath, function (err, files) { - if (err) { - return next(err); - } - - var mods = files.filter(function (filePath) { - return path.extname(filePath) === '.js'; - }).map(function (filePath) { - return { - srcPath: path.normalize(filePath), - destPath: path.join(destPath, path.relative(srcPath, filePath)), - }; - }); - - moduleFiles = moduleFiles.concat(mods).map(function (mod) { - mod.filename = path.relative(basePath, mod.srcPath).replace(/\\/g, '/'); - return mod; - }); - - next(); - }); + const moduleFiles = []; + await Promise.all(modules.map(async function (module) { + const srcPath = module.srcPath; + const destPath = module.destPath; + + const stats = await fs.promises.stat(srcPath); + if (!stats.isDirectory()) { + moduleFiles.push(module); + return; + } + + const files = await file.walk(srcPath); + + const mods = files.filter( + filePath => path.extname(filePath) === '.js' + ).map(function (filePath) { + return { + srcPath: path.normalize(filePath), + destPath: path.join(destPath, path.relative(srcPath, filePath)), + }; }); - }, function (err) { - callback(err, moduleFiles); - }); -} -function clearModules(callback) { - var builtPaths = moduleDirs.map(function (p) { - return path.join(__dirname, '../../build/public/src', p); - }); - async.each(builtPaths, function (builtPath, next) { - rimraf(builtPath, next); - }, function (err) { - callback(err); - }); + moduleFiles.concat(mods).forEach(function (mod) { + mod.filename = path.relative(basePath, mod.srcPath).replace(/\\/g, '/'); + }); + })); + return moduleFiles; } -JS.buildModules = function (fork, callback) { - async.waterfall([ - clearModules, - function (next) { - if (process.env.NODE_ENV === 'development') { - return linkModules(callback); - } +async function clearModules() { + const builtPaths = moduleDirs.map( + p => path.join(__dirname, '../../build/public/src', p) + ); + await Promise.all( + builtPaths.map(builtPath => rimrafAsync(builtPath)) + ); +} - getModuleList(next); - }, - function (modules, next) { - minifyModules(modules, fork, next); - }, - ], callback); +JS.buildModules = async function (fork) { + await clearModules(); + if (process.env.NODE_ENV === 'development') { + await linkModules(); + return; + } + const modules = await getModuleList(); + await minifyModules(modules, fork); }; -function requirejsOptimize(target, callback) { +async function requirejsOptimize(target) { const requirejs = require('requirejs'); let scriptText = ''; const sharedCfg = { @@ -305,52 +263,41 @@ function requirejsOptimize(target, callback) { name: 'Sortable', }, ], - client: [ - - ], + client: [], }; - async.eachSeries(bundledModules.concat(targetModules[target]), function (moduleCfg, next) { - requirejs.optimize({ ...sharedCfg, ...moduleCfg }, function () { - next(); - }, function (err) { - next(err); - }); - }, function (err) { - if (err) { - return callback(err); - } - const filePath = path.join(__dirname, '../../build/public/rjs-bundle-' + target + '.js'); - fs.writeFile(filePath, scriptText, callback); + const optimizeAsync = util.promisify(function (config, cb) { + requirejs.optimize(config, () => cb(), err => cb(err)); }); + + const allModules = bundledModules.concat(targetModules[target]); + + for (const moduleCfg of allModules) { + // eslint-disable-next-line no-await-in-loop + await optimizeAsync({ ...sharedCfg, ...moduleCfg }); + } + const filePath = path.join(__dirname, '../../build/public/rjs-bundle-' + target + '.js'); + await fs.promises.writeFile(filePath, scriptText); } -JS.linkStatics = function (callback) { - rimraf(path.join(__dirname, '../../build/public/plugins'), function (err) { - if (err) { - return callback(err); - } - async.each(Object.keys(plugins.staticDirs), function (mappedPath, next) { - var sourceDir = plugins.staticDirs[mappedPath]; - var destDir = path.join(__dirname, '../../build/public/plugins', mappedPath); - - mkdirpCallback(path.dirname(destDir), function (err) { - if (err) { - return next(err); - } - - file.linkDirs(sourceDir, destDir, true, next); - }); - }, callback); - }); +JS.linkStatics = async function () { + await rimrafAsync(path.join(__dirname, '../../build/public/plugins')); + + await Promise.all(Object.keys(plugins.staticDirs).map(async function (mappedPath) { + const sourceDir = plugins.staticDirs[mappedPath]; + const destDir = path.join(__dirname, '../../build/public/plugins', mappedPath); + + await mkdirp(path.dirname(destDir)); + await file.linkDirs(sourceDir, destDir, true); + })); }; -function getBundleScriptList(target, callback) { - var pluginDirectories = []; +async function getBundleScriptList(target) { + const pluginDirectories = []; if (target === 'admin') { target = 'acp'; } - var pluginScripts = plugins[target + 'Scripts'].filter(function (path) { + let pluginScripts = plugins[target + 'Scripts'].filter(function (path) { if (path.endsWith('.js')) { return true; } @@ -359,73 +306,51 @@ function getBundleScriptList(target, callback) { return false; }); - async.each(pluginDirectories, function (directory, next) { - file.walk(directory, function (err, scripts) { - if (err) { - return next(err); - } + await Promise.all(pluginDirectories.map(async function (directory) { + const scripts = await file.walk(directory); + pluginScripts = pluginScripts.concat(scripts); + })); - pluginScripts = pluginScripts.concat(scripts); - next(); - }); - }, function (err) { - if (err) { - return callback(err); - } - - var scripts = JS.scripts.base; - - if (target === 'client' && process.env.NODE_ENV !== 'development') { - scripts = scripts.concat(JS.scripts.rjs); - } else if (target === 'acp') { - scripts = scripts.concat(JS.scripts.admin); - } + let scripts = JS.scripts.base; - scripts = scripts.concat(pluginScripts).map(function (script) { - var srcPath = path.resolve(basePath, script).replace(/\\/g, '/'); - return { - srcPath: srcPath, - filename: path.relative(basePath, srcPath).replace(/\\/g, '/'), - }; - }); + if (target === 'client' && process.env.NODE_ENV !== 'development') { + scripts = scripts.concat(JS.scripts.rjs); + } else if (target === 'acp') { + scripts = scripts.concat(JS.scripts.admin); + } - callback(null, scripts); + scripts = scripts.concat(pluginScripts).map(function (script) { + const srcPath = path.resolve(basePath, script).replace(/\\/g, '/'); + return { + srcPath: srcPath, + filename: path.relative(basePath, srcPath).replace(/\\/g, '/'), + }; }); + + return scripts; } -JS.buildBundle = function (target, fork, callback) { - var fileNames = { +JS.buildBundle = async function (target, fork) { + const fileNames = { client: 'nodebb.min.js', admin: 'acp.min.js', }; + await requirejsOptimize(target); + const files = await getBundleScriptList(target); + await mkdirp(path.join(__dirname, '../../build/public')); - async.waterfall([ - function (next) { - requirejsOptimize(target, next); - }, - function (next) { - getBundleScriptList(target, next); - }, - function (files, next) { - mkdirpCallback(path.join(__dirname, '../../build/public'), function (err) { - next(err, files); - }); - }, - function (files, next) { - files.push({ - srcPath: path.join(__dirname, '../../build/public/rjs-bundle-' + target + '.js'), - }); - - var minify = process.env.NODE_ENV !== 'development'; - var filePath = path.join(__dirname, '../../build/public', fileNames[target]); - - minifier.js.bundle({ - files: files, - filename: fileNames[target], - destPath: filePath, - }, minify, fork, next); - }, - ], callback); + files.push({ + srcPath: path.join(__dirname, '../../build/public/rjs-bundle-' + target + '.js'), + }); + + const minify = process.env.NODE_ENV !== 'development'; + const filePath = path.join(__dirname, '../../build/public', fileNames[target]); + + await minifier.js.bundle({ + files: files, + filename: fileNames[target], + destPath: filePath, + }, minify, fork); }; JS.killMinifier = function () { diff --git a/src/meta/minifier.js b/src/meta/minifier.js index 00456e658385..7b85a6f23d22 100644 --- a/src/meta/minifier.js +++ b/src/meta/minifier.js @@ -1,24 +1,24 @@ 'use strict'; -var fs = require('fs'); -var os = require('os'); -var uglify = require('uglify-es'); -var async = require('async'); -var winston = require('winston'); -var less = require('less'); -var postcss = require('postcss'); -var autoprefixer = require('autoprefixer'); -var clean = require('postcss-clean'); - -var fork = require('./debugFork'); +const fs = require('fs'); +const os = require('os'); +const uglify = require('uglify-es'); +const async = require('async'); +const winston = require('winston'); +const less = require('less'); +const postcss = require('postcss'); +const autoprefixer = require('autoprefixer'); +const clean = require('postcss-clean'); + +const fork = require('./debugFork'); require('../file'); // for graceful-fs -var Minifier = module.exports; +const Minifier = module.exports; -var pool = []; -var free = []; +const pool = []; +const free = []; -var maxThreads = 0; +let maxThreads = 0; Object.defineProperty(Minifier, 'maxThreads', { get: function () { @@ -300,3 +300,5 @@ Minifier.css.bundle = function (source, paths, minify, fork, callback) { minify: minify, }, fork, callback); }; + +require('../promisify')(exports); diff --git a/src/middleware/index.js b/src/middleware/index.js index 98eb224e3367..8a33f12b2fef 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -208,8 +208,7 @@ middleware.buildSkinAsset = helpers.try(async function buildSkinAsset(req, res, } await plugins.prepareForBuild(['client side styles']); - const buildBundle = util.promisify(meta.css.buildBundle); - const css = await buildBundle(target[0], true); + const css = await meta.css.buildBundle(target[0], true); require('../meta/minifier').killAll(); res.status(200).type('text/css').send(css); });