From 50db051abcc698010182193e96e679b309bd6e84 Mon Sep 17 00:00:00 2001 From: Tim Hostetler <6970899+thostetler@users.noreply.github.com> Date: Mon, 24 Jan 2022 17:53:38 -0500 Subject: [PATCH] Updates to bundler (optimizer) Moves config into options Invokes requirejs directly, instead of generating config Fixes context errors Speed improvements --- grunt/optimize-build.js | 571 ++++++++++++++++++--------------- src/config/common.config.js | 2 +- src/config/discovery.config.js | 1 - 3 files changed, 306 insertions(+), 268 deletions(-) diff --git a/grunt/optimize-build.js b/grunt/optimize-build.js index 2f3492298..d44012a47 100644 --- a/grunt/optimize-build.js +++ b/grunt/optimize-build.js @@ -1,231 +1,166 @@ -module.exports = function(grunt) { - var PATHS = { - 'backbone-validation': 'empty:', - 'backbone.stickit': 'empty:', - 'backbone.wreqr': 'empty:', - backbone: 'empty:', - bootstrap: 'empty:', - bowser: 'empty:', - classnames: 'empty:', - clipboard: 'empty:', - 'create-react-class': 'empty:', - 'd3-cloud': 'empty:', - d3: 'empty:', - filesaver: 'empty:', - 'google-recaptcha': 'empty:', - 'google-analytics': 'empty:', - jquery: 'empty:', - 'jquery-ui': 'empty:', - 'jquery-querybuilder': 'empty:', - jsonpath: 'empty:', - marionette: 'empty:', - mathjax: 'empty:', - moment: 'empty:', - 'persist-js': 'empty:', - 'react-bootstrap': 'empty:', - 'react-dom': 'empty:', - 'prop-types': 'empty:', - 'react-redux': 'empty:', - react: 'empty:', - 'redux-thunk': 'empty:', - redux: 'empty:', - requirejs: 'empty:', - reselect: 'empty:', - select2: 'empty:', - sprintf: 'empty:', - underscore: 'empty:', - 'discovery.vars': 'empty:', - yup: 'empty:', - 'react-hook-form': 'empty:', - 'react-flexview': 'empty:', - 'styled-components': 'empty:', - 'react-is': 'empty:', - 'react-data-table-component': 'empty:', - 'react-window': 'empty:', - 'react-async': 'empty:', - diff: 'empty:', - hotkeys: 'empty:', - 'react-transition-group': 'empty:', - 'regenerator-runtime': 'empty:', - '@hookform/resolvers': 'empty:', - 'react-aria-menubutton': 'empty:', - }; - - var baseConfig = { - waitSeconds: 7, - logLevel: 1, - baseUrl: 'dist', - optimize: 'none', - mainConfigFile: 'dist/config/discovery.config.js', - deps: [], - findNestedDependencies: false, - create: true, - paths: PATHS, - stubModules: ['es6', 'babel'], - }; - - grunt.registerMultiTask( - 'optimize-build', - 'Generate config and optimize build', - function() { - var _ = require('lodash'); - var path = require('path'); - var fullConfig = {}; - - // callback called with URL and name pulled from folder - var getByGlob = function(globs, cb) { - var urls = []; - grunt.file.expand({ filter: 'isFile' }, globs).forEach(function(url) { - var parts = url.split(path.sep); - if (/jsx\.js$/.test(url)) { - url = url.replace(/^/, 'es6!'); - } - url = url.replace('src/', '').replace(/\.js$/, ''); - urls.push(url); - cb && cb(url, parts[3]); - }); - return urls; - }; - - var writeOutConfig = function(config, done) { - var output = ` - // GENERATED FILE (edits will be overwritten) - module.exports = ${JSON.stringify(config, null, 2)}; - `; - fullConfig = config; - grunt.file.write(path.resolve(__dirname, 'requirejs.js'), output); - (function check(i) { - if ( - i >= 30 || - grunt.file.exists(path.resolve(__dirname, 'requirejs.js')) - ) { - grunt.log.writeln('Configuration Generated...'); - return done(); - } - setTimeout(check, 500, ++i); - })(0); - }; - - grunt.registerTask('generateConfig', function() { - var config = {}; - var done = this.async(); - var addConfig = function(name, cfg) { - config[name] = {}; - config[name].options = _.extend( - {}, - baseConfig, - { - name: `${name}.bundle`, - out: `dist/config/${name}.bundle.js`, - }, - cfg - ); - }; - - const globFiles = (arrOfPaths) => { - const filter = (path) => { - // filter out all es6 modules, for now - return !/\.jsx\.js$/.test(path); - }; - - return grunt.file - .expand({ cwd: 'dist', filter }, arrOfPaths) - .map((path) => { - return path.replace(/\.js$/, ''); - }); - }; +/* eslint-disable global-require */ - addConfig('landing-page', { - include: [ - ...globFiles([ - 'js/react/Recommender/**/*.js', - 'js/widgets/paper_search_form/**/*.js', +module.exports = function(grunt) { + const buildConfig = { + options: { + paths: { + 'backbone-validation': 'empty:', + 'backbone.stickit': 'empty:', + 'backbone.wreqr': 'empty:', + backbone: 'empty:', + bootstrap: 'empty:', + bowser: 'empty:', + classnames: 'empty:', + clipboard: 'empty:', + 'create-react-class': 'empty:', + 'd3-cloud': 'empty:', + d3: 'empty:', + filesaver: 'empty:', + 'google-recaptcha': 'empty:', + 'google-analytics': 'empty:', + jquery: 'empty:', + 'jquery-ui': 'empty:', + 'jquery-querybuilder': 'empty:', + jsonpath: 'empty:', + marionette: 'empty:', + mathjax: 'empty:', + moment: 'empty:', + 'persist-js': 'empty:', + 'react-bootstrap': 'empty:', + 'react-dom': 'empty:', + 'prop-types': 'empty:', + 'react-redux': 'empty:', + react: 'empty:', + 'redux-thunk': 'empty:', + redux: 'empty:', + requirejs: 'empty:', + reselect: 'empty:', + select2: 'empty:', + sprintf: 'empty:', + underscore: 'empty:', + 'discovery.vars': 'empty:', + yup: 'empty:', + 'react-hook-form': 'empty:', + 'react-flexview': 'empty:', + 'styled-components': 'empty:', + 'react-is': 'empty:', + 'react-data-table-component': 'empty:', + 'react-window': 'empty:', + 'react-async': 'empty:', + diff: 'empty:', + hotkeys: 'empty:', + 'react-transition-group': 'empty:', + 'regenerator-runtime': 'empty:', + '@hookform/resolvers': 'empty:', + 'react-aria-menubutton': 'empty:', + }, + stubModules: ['es6', 'babel'], + }, + release: { + options: { + bundles: { + 'landing-page': { + files: [ + 'js/wraps/*_facet.js', + 'js/widgets/metrics/**/*.js', + 'js/widgets/list_of_things/**/*.js', 'js/widgets/search_bar/**/*.js', 'js/widgets/facet/**/*.js', 'js/widgets/alerts/**/*.js', - ]), - 'js/widgets/base/base_widget', - 'js/widgets/classic_form/widget', - 'js/widgets/footer/widget', - 'js/widgets/navbar/widget', - 'js/widgets/widget_states', - 'js/wraps/alerts_mediator', - 'js/wraps/discovery_mediator', - 'js/wraps/landing_page_manager/landing_page_manager', - ], - }); - - addConfig('search-page', { - include: [ - ...globFiles([ + ], + requireModules: [ + 'js/widgets/abstract/widget', + 'js/widgets/api_query/widget', + 'js/widgets/base/base_widget', + 'js/widgets/bubble_chart/widget', + 'js/widgets/citation_helper/widget', + 'js/widgets/dropdown-menu/widget', + 'js/widgets/filter_visualizer/widget', + 'js/widgets/footer/widget', + 'js/widgets/navbar/widget', + 'js/widgets/network_vis/network_widget', + 'js/widgets/query_info/query_info_widget', + 'js/widgets/results/widget', + 'js/widgets/tabs/tabs_widget', + 'js/widgets/widget_states', + 'js/widgets/wordcloud/widget', + 'js/wraps/alerts_mediator', + 'js/wraps/discovery_mediator', + 'js/wraps/export_dropdown', + 'js/wraps/graph_tabs', + 'js/wraps/results_page_manager', + 'js/wraps/visualization_dropdown', + 'libs/select2/matcher', + ], + }, + 'search-page': { + files: [ 'js/wraps/*_facet.js', 'js/widgets/metrics/**/*.js', 'js/widgets/list_of_things/**/*.js', 'js/widgets/search_bar/**/*.js', 'js/widgets/facet/**/*.js', 'js/widgets/alerts/**/*.js', - ]), - 'js/widgets/abstract/widget', - 'js/widgets/api_query/widget', - 'js/widgets/base/base_widget', - 'js/widgets/bubble_chart/widget', - 'js/widgets/citation_helper/widget', - 'js/widgets/dropdown-menu/widget', - 'js/widgets/filter_visualizer/widget', - 'js/widgets/footer/widget', - 'js/widgets/navbar/widget', - 'js/widgets/network_vis/network_widget', - 'js/widgets/query_info/query_info_widget', - 'js/widgets/results/widget', - 'js/widgets/tabs/tabs_widget', - 'js/widgets/widget_states', - 'js/widgets/wordcloud/widget', - 'js/wraps/alerts_mediator', - 'js/wraps/discovery_mediator', - 'js/wraps/export_dropdown', - 'js/wraps/graph_tabs', - 'js/wraps/results_page_manager', - 'js/wraps/visualization_dropdown', - 'libs/select2/matcher', - ], - }); - - addConfig('abstract-page', { - include: [ - ...globFiles([ + ], + requireModules: [ + 'js/widgets/abstract/widget', + 'js/widgets/api_query/widget', + 'js/widgets/base/base_widget', + 'js/widgets/bubble_chart/widget', + 'js/widgets/citation_helper/widget', + 'js/widgets/dropdown-menu/widget', + 'js/widgets/filter_visualizer/widget', + 'js/widgets/footer/widget', + 'js/widgets/navbar/widget', + 'js/widgets/network_vis/network_widget', + 'js/widgets/query_info/query_info_widget', + 'js/widgets/results/widget', + 'js/widgets/tabs/tabs_widget', + 'js/widgets/widget_states', + 'js/widgets/wordcloud/widget', + 'js/wraps/alerts_mediator', + 'js/wraps/discovery_mediator', + 'js/wraps/export_dropdown', + 'js/wraps/graph_tabs', + 'js/wraps/results_page_manager', + 'js/wraps/visualization_dropdown', + 'libs/select2/matcher', + ], + }, + 'abstract-page': { + files: [ 'js/wraps/*_facet.js', 'js/widgets/metrics/**/*.js', 'js/widgets/list_of_things/**/*.js', 'js/widgets/search_bar/**/*.js', 'js/widgets/facet/**/*.js', 'js/widgets/alerts/**/*.js', - ]), - 'js/widgets/abstract/widget', - 'js/widgets/base/base_widget', - 'js/widgets/footer/widget', - 'js/widgets/graphics/widget', - 'js/widgets/meta_tags/widget', - 'js/widgets/navbar/widget', - 'js/widgets/query_info/query_info_widget', - 'js/widgets/widget_states', - 'js/wraps/abstract_page_library_add/widget', - 'js/wraps/abstract_page_manager/abstract_page_manager', - 'js/wraps/alerts_mediator', - 'js/wraps/citations', - 'js/wraps/coreads', - 'js/wraps/discovery_mediator', - 'js/wraps/paper_export', - 'js/wraps/paper_metrics', - 'js/wraps/references', - 'js/wraps/sidebar-graphics-widget', - 'js/wraps/table_of_contents', - 'libs/select2/matcher', - ], - }); - - addConfig('main', { - include: [ - ...globFiles([ + ], + requireModules: [ + 'js/widgets/abstract/widget', + 'js/widgets/base/base_widget', + 'js/widgets/footer/widget', + 'js/widgets/graphics/widget', + 'js/widgets/meta_tags/widget', + 'js/widgets/navbar/widget', + 'js/widgets/query_info/query_info_widget', + 'js/widgets/widget_states', + 'js/wraps/abstract_page_library_add/widget', + 'js/wraps/abstract_page_manager/abstract_page_manager', + 'js/wraps/alerts_mediator', + 'js/wraps/citations', + 'js/wraps/coreads', + 'js/wraps/discovery_mediator', + 'js/wraps/paper_export', + 'js/wraps/paper_metrics', + 'js/wraps/references', + 'js/wraps/sidebar-graphics-widget', + 'js/wraps/table_of_contents', + 'libs/select2/matcher', + ], + }, + main: { + files: [ 'config/**/*.js', '!config/discovery.vars.js', '!config/shim.js', @@ -238,65 +173,174 @@ module.exports = function(grunt) { 'js/modules/**/*.js', 'js/page_managers/**/*.js', 'js/services/**/*.js', - ]), - 'analytics', - 'router', - 'utils', - 'recaptcha', - 'reactify', - 'js/bugutils/diagnostics', - 'js/react/BumblebeeWidget', + ], + requireModules: [ + 'analytics', + 'router', + 'utils', + 'recaptcha', + 'reactify', + 'js/bugutils/diagnostics', + 'js/react/BumblebeeWidget', + 'js/dark-mode-switch', - // vendor libraries - 'cache', - 'hbs', - 'async', - 'hbs/handlebars', - 'hbs/json2', - 'hbs/underscore', - 'array-flat-polyfill', - 'regenerator-runtime', - 'hotkeys', - 'react-transition-group', - '@hookform/resolvers', - ], + // vendor libraries + 'cache', + 'hbs', + 'async', + 'hbs/handlebars', + 'hbs/json2', + 'hbs/underscore', + 'array-flat-polyfill', + 'regenerator-runtime', + 'hotkeys', + 'react-transition-group', + '@hookform/resolvers', + ], + }, + }, + }, + }, + }; + + grunt.registerMultiTask( + 'optimize-build', + 'Generate config and optimize build', + function() { + const _ = require('lodash'); + const rjs = require('requirejs'); + const fullConfig = {}; + const options = this.options({ + waitSeconds: 7, + logLevel: 4, + baseUrl: 'dist', + optimize: 'none', + mainConfigFile: 'dist/config/discovery.config.js', + deps: [], + findNestedDependencies: false, + create: true, + bundles: {}, + }); + + const { bundles, ...baseConfig } = options; + + // fail here if no bundles are passed + if (Object.keys(options.bundles).length === 0) { + return grunt.fail.fatal('No bundles configured'); + } + + /** + * Generate the bundles based on the passed in config + */ + grunt.registerTask('generateBundles', function() { + const done = this.async(); + + // log out a message on each successful completion + const onBundleComplete = ({ name }) => { + grunt.log.ok(`${name} generated!`); + }; + + // generate the config based on the base and bundle name + const getConfig = (name, cfg) => ({ + ...baseConfig, + name: `${name}.bundle`, + out: `dist/config/${name}.bundle.js`, + onModuleBundleComplete: onBundleComplete, + ...cfg, }); - writeOutConfig(config, done); + // expand file globs + const expand = ({ files, requireModules }) => { + const filter = (path) => { + // filter out all es6 modules, for now + return !/\.jsx\.js$/.test(path); + }; + + const paths = grunt.file + .expand({ cwd: 'dist', filter }, files) + .map((path) => { + return path.replace(/\.js$/, ''); + }); + + return [...paths, ...requireModules]; + }; + + // wraps requirejs optimizer in promise + const optimize = (name, modules) => + new Promise((res, rej) => { + const config = getConfig(name, { include: modules }); + + // update fullConfig variable (used in other task) + fullConfig[name] = config; + + // call optimize on the configuration, passing in our promise handlers + rjs.optimize( + config, + () => res(), + (args) => rej(args) + ); + }); + + const promises = []; + Object.keys(bundles).forEach((name) => + promises.push(optimize(name, expand(bundles[name]))) + ); + + // wait on all the promises to finish, otherwise fail here + Promise.all(promises) + .catch((err) => grunt.fail.fatal(err)) + .finally(() => done()); }); - var getDiscoveryConfig = function() { - var content = grunt.file.read('dist/config/discovery.config.js'); - var cfg = {}; + const getDiscoveryConfig = function() { + const content = grunt.file.read('dist/config/discovery.config.js', { + encoding: 'utf-8', + }); + let cfg = {}; (function() { - var require = (requirejs = { - config: function(data) { + // mocks "require" in context for the eval call, this allows us to extract the config + // eslint-disable-next-line no-unused-vars + const require = { + config(data) { cfg = data; }, - }); + }; + + // eslint-disable-next-line no-eval eval(content.toString()); })(); + return cfg; }; - var generateConfigFileString = function(name, cnts) { - return ` + const generateConfigFileString = (name, contents) => + Buffer.from( + ` /** * GENERATED FILE (edits will be overwritten): * This is the configuration for ${name}. */ -requirejs.config(${JSON.stringify(cnts, null, 2)}); -`; - }; +requirejs.config(${JSON.stringify(contents)}); +`, + 'utf-8' + ); + + /** + * Generates the requirejs configs based on a default one which we grab first + * + * Basically we generate bundles above, which now need to be the target of several paths. In + * these generated configs, we point to our bundle (if necessary) otherwise leave it be. + */ + grunt.registerTask('generateConfigs', function() { + const cfg = getDiscoveryConfig(); - grunt.registerTask('applyIncludesToConfig', function() { - var cfg = getDiscoveryConfig(); + console.log(fullConfig); // generate the rest of the bundles - _.forEach(fullConfig, function(bundle, name) { - var _cfg = _.extend({}, cfg, { + _.forEach(fullConfig, function({ name, include }, moduleName) { + const _cfg = _.extend({}, cfg, { // set the main dependency to the bundle name - deps: [`config/${bundle.options.name}`], + deps: [`config/${name}`], // update the paths config with new revved names paths: _.extend( @@ -305,35 +349,30 @@ requirejs.config(${JSON.stringify(cnts, null, 2)}); // add all additional revved filenames to the paths _.reduce( - bundle.options.include, - function(acc, p) { - acc[p] = `config/${bundle.options.name}`; - return acc; - }, + include, + (acc, k) => ({ ...acc, [k]: `config/${name}` }), {} ), // some explicit path changes { - 'discovery.config': `config/${bundle.options.name}`, + 'discovery.config': `config/${name}`, } ), }); - var out = generateConfigFileString(`dist/${name}.config.js`, _cfg); - grunt.file.write(`dist/config/${name}.config.js`, out); - grunt.log.writeln(`${name}.config.js has been created`); + const out = generateConfigFileString( + `dist/${moduleName}.config.js`, + _cfg + ); + grunt.file.write(`dist/config/${moduleName}.config.js`, out); + grunt.log.writeln(`${moduleName}.config.js has been created`); }); }); - grunt.task.run(['generateConfig', 'babel:release', 'requirejs']); - grunt.task.run(['applyIncludesToConfig']); - // grunt.task.run(['uglify']); + grunt.task.run(['babel:release', 'generateBundles', 'generateConfigs']); } ); - return { - options: {}, - release: {}, - }; + return buildConfig; }; diff --git a/src/config/common.config.js b/src/config/common.config.js index 57ba8fc25..1d8ba4336 100644 --- a/src/config/common.config.js +++ b/src/config/common.config.js @@ -47,7 +47,7 @@ define([], function() { 'regenerator-runtime', 'array-flat-polyfill', 'polyfill', - 'darkMode', + 'js/dark-mode-switch', ], function(config) { // rca: not sure why the ganalytics is loaded here instead of inside analytics.js // perhaps it is because it is much/little sooner this way? diff --git a/src/config/discovery.config.js b/src/config/discovery.config.js index 8790ed9be..c0f6655cc 100644 --- a/src/config/discovery.config.js +++ b/src/config/discovery.config.js @@ -176,7 +176,6 @@ require.config({ router: 'js/apps/discovery/router', analytics: 'js/components/analytics', utils: 'js/utils', - darkMode: 'js/dark-mode-switch', recaptcha: 'js/plugins/recaptcha', reactify: 'js/plugins/reactify', es6: 'js/plugins/es6',