Left file: appwork-v1_3_1/src/webpack.config.js  
Right file: appwork-v1_4_0/src/webpack.config.js  
const path = require('path') = const path = require('path')
const glob = require('glob')   const glob = require('glob')
  -+ const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
  =  
// -------------------------------------------------------------------------------   // -------------------------------------------------------------------------------
// Config   // Config
     
const conf = (() => {   const conf = (() => {
  const _conf = require('./build-config')     const _conf = require('./build-config')
  return require('deepmerge').all([{}, _conf.base || {}, _conf[process.env.NODE_ENV] || {}])     return require('deepmerge').all([{}, _conf.base || {}, _conf[process.env.NODE_ENV] || {}])
})()   })()
     
conf.buildPath = path.resolve(__dirname, conf.buildPath)   conf.buildPath = path.resolve(__dirname, conf.buildPath)
     
// -------------------------------------------------------------------------------   // -------------------------------------------------------------------------------
// Modules <>  
    // NPM packages to transpile
const webpack = require('webpack')    
const StringReplacePlugin = require('string-replace-webpack-plugin')   const TRANSPILE_PACKAGES = [
      'bootstrap',
      'bootstrap-slider',
      'popper.js',
      'bootstrap-table',
      'shepherd.js',
      'flot',
    ]
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')    
    const packageRejex = package => `(?:\\\\|\\/)${package}(?:\\\\|\\/).+?\\.js$`
  =  
// -------------------------------------------------------------------------------   // -------------------------------------------------------------------------------
// Custom template <>  
    // Build config
const ConcatSource = require('webpack-sources').ConcatSource    
    const collectEntries = () => {
class CustomTemplatePlugin {    
        apply(compilation) {    
    const { mainTemplate, chunkTemplate } = compilation;    
      const fileList = glob.sync(`!(${conf.exclude.join('|')})/**/!(_)*.@(js|es6)`) || []
    const onRenderWithEntry = (source, chunk, hash) => {    
      return new ConcatSource(`    
(function(r,f) {    
        var a=f();    
        if(typeof a!=='object')return;    
        var e=[typeof module==='object'&&typeof module.exports==='object'?module.exports:null,typeof window!=='undefined'?window:null,r&&r!==window?r:null];    
        for(var i in a){e[0]&&(e[0][i]=a[i]);e[1]&&i!=='__esModule'&&(e[1][i] = a[i]);e[2]&&(e[2][i]=a[i]);}    
})(this,function(){    
        return `, source, `;    
});`)    
                };    
      return fileList.reduce((entries, file) => {
                for (const template of [mainTemplate, chunkTemplate]) {    
                        template.hooks.renderWithEntry.tap(    
                                'CustomTemplatePlugin',    
                                onRenderWithEntry    
                        );    
                }    
        const filePath = file.replace(/\\/g, '/')
                mainTemplate.hooks.globalHashPaths.tap("CustomTemplatePlugin", paths => {    
                        return paths;    
                });    
        return { ...entries, [filePath.replace(/\.(?:js|es6)$/, '')]: `./${filePath}` }
                mainTemplate.hooks.hash.tap("CustomTemplatePlugin", hash => {    
      hash.update('CustomTemplatePlugin');    
                        hash.update('2');    
                });     }, {})
        }   }
}    
    const babelLoader = () => ({
class CustomLibraryTemplatePlugin {     loader: 'babel-loader',
        apply(compiler) {     options: {
    compiler.hooks.thisCompilation.tap('CustomLibraryTemplatePlugin', compilation => {       presets: [['@babel/preset-env', { targets: 'last 2 versions, ie >= 10' }]],
      new CustomTemplatePlugin().apply(compilation);       plugins: ['@babel/plugin-transform-destructuring', '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-transform-template-literals'],
    });       babelrc: false
        }     }
}   })
  =  
// ------------------------------------------------------------------------------- +-  
// Build config    
     
const webpackConfig = { = const webpackConfig = {
        target: 'web', +-  
  mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', =   mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
  <>  
  // Collect entries     entry: collectEntries(),
        entry: (glob.sync(`!(${conf.exclude.join('|')})/**/!(_)*.@(js|es6)`) || [])    
                .reduce((entries, file) => {    
      const filePath = file.replace(/\\/g, '/')    
                        entries[filePath.replace(/\.(?:js|es6)$/, '')] = `./${filePath}`    
                        return entries    
                }, {}),    
  =  
        output: { <>   output: {
                path: conf.buildPath,       path: conf.buildPath,
                filename: '[name].js'       filename: '[name].js',
        libraryTarget: 'window'
        },     },
  =  
  module: {     module: {
  +-  
    rules: [{ =     rules: [{
      test: /(?:\.es6|\.es6\.js)$|(?:node_modules(?:\\|\/)bootstrap(?:\\|\/)js(?:\\|\/).+?\.js$)|(?:bootstrap-sweetalert(?:\/|\\)dev.+?\.js)$|(?:bootstrap-slider\.js)$|(?:popper\.js)$|(?:bootstrap-table(?:\\|\/).+?\.js$)|(?:shepherd\.js)|(?:fullcalendar\.js)|(?:sweetalert2\.js)/, <>       // Transpile sources
      use: [         test: /\.es6$|\.js$/,
       {         exclude: [path.resolve(__dirname, 'node_modules')],
          loader: 'babel-loader',         use: [babelLoader()]
          options: {    
            presets: [['@babel/preset-env', {    
              targets: 'last 2 versions, ie >= 10'    
                              }]],       }, {
                                                plugins: ['@babel/plugin-transform-destructuring', '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-transform-template-literals'],        // Pranspile required packages
          test: new RegExp(`(?:${TRANSPILE_PACKAGES.map(packageRejex).join(')|(?:')})`),
          include: [path.resolve(__dirname, 'node_modules')],
            babelrc: false         use: [babelLoader()]
          }    
        }    
      ]    
    }, { =     }, {
                        test: /\.css$/, <>       test: /\.css$/,
                        use: [{         use: [
                                loader: 'style-loader',           { loader: 'style-loader' },
                                options: {    
                                        hmr: false    
                                }    
                        }, {    
                                loader: 'css-loader'           { loader: 'css-loader' }
                        }]         ]
                }, {       }, {
                        test: /\.scss$/,         test: /\.scss$/,
                        use: [{         use: [
                                loader: 'style-loader',           { loader: 'style-loader' },
                                options: {    
                                        hmr: false    
                                }    
                        }, {    
                                loader: 'css-loader'           { loader: 'css-loader' },
                        }, {    
          loader: 'sass-loader'           { loader: 'sass-loader' }
      }]         ]
                }, {       }, {
                        test: /\.html$/,         test: /\.html$/,
                        use: [{         use: [{
                                loader: 'html-loader',           loader: 'html-loader',
                                options: {    
                                        minimize: true           options: { minimize: true }
                                }    
                        }]         }]
                }, { // Remove imports    
      test: /node_modules(?:\\|\/)bootstrap(?:\\|\/)js(?:\\|\/)src(?:\\|\/)\w+\.js$/,    
      use: {    
        loader: StringReplacePlugin.replace({    
          replacements: [{    
            pattern: /import.+?from\s+['"](.+?)['"];?\n/ig,    
            replacement: (m, $1) => $1.toLowerCase() !== './tooltip' ? '' : 'import Tooltip from \'./tooltip\'\n'    
          }]    
        })    
      }    
    }, { // Fix flot.resize plugin    
      test: /node_modules.+jquery\.flot\.resize\.js$/,    
      use: {    
        loader: StringReplacePlugin.replace({    
          replacements: [{    
            pattern: /\(\s*jQuery\s*,\s*this\s*\)/ig,    
            replacement: () => '(jQuery,window)'    
          }]    
        })    
      }    
    }, { // Fix markdown plugin    
      test: /node_modules(?:\\|\/)markdown(?:\\|\/).+\.js$/,    
      use: {    
        loader: StringReplacePlugin.replace({    
          replacements: [{    
            pattern: /if \(typeof define !== 'function'\) \{ var define = require\('amdefine'\)\(module\) \}/ig,    
            replacement: () => ''    
          }]    
        })    
      }    
    }] =     }]
  +-  
  }, =   },
     
  plugins: [ +-  
                new webpack.DefinePlugin({    
                        'process.env': process.env.NODE_ENV    
                }),    
    new webpack.IgnorePlugin(/codemirror/),    
    new CustomLibraryTemplatePlugin()    
  ],    
     
  externals: { =   externals: {
    'jquery': 'window.jQuery', <>     'jquery': 'jQuery',
    'moment': 'window.moment',       'moment': 'moment',
    'datatables.net': '$.fn.dataTable', =     'datatables.net': '$.fn.dataTable',
    'spin.js': 'window.Spinner', <>     'spin.js': 'Spinner',
    'jsdom': 'window.jsdom',       'jsdom': 'jsdom',
    'd3': 'window.d3',       'd3': 'd3',
    'eve': 'window.eve',       'eve': 'eve',
    'velocity': 'window.Velocity',       'velocity': 'Velocity',
    'hammer': 'window.Hammer',       'hammer': 'Hammer',
                'raphael': 'window.Raphael',       'raphael': 'Raphael',
    'jquery-mapael': 'window.Mapael',       'jquery-mapael': 'Mapael',
                'pace': '"pace-progress"',       'pace': '"pace-progress"',
        'popper.js': 'Popper',
        'jquery-validation': 'jQuery',
  =  
    // blueimp-file-upload plugin       // blueimp-file-upload plugin
    'canvas-to-blob': 'window.blueimpDataURLtoBlob', <>     'canvas-to-blob': 'blueimpDataURLtoBlob',
    'blueimp-tmpl': 'window.blueimpTmpl',       'blueimp-tmpl': 'blueimpTmpl',
    'load-image': 'window.blueimpLoadImage',       'load-image': 'blueimpLoadImage',
    'load-image-meta': 'null', =     'load-image-meta': 'null',
    'load-image-scale': 'null',       'load-image-scale': 'null',
    'load-image-exif': 'null',       'load-image-exif': 'null',
                'jquery-ui/ui/widget': 'null', <>     'jquery-ui/ui/widget': 'null',
    './jquery.fileupload': 'null', =     './jquery.fileupload': 'null',
    './jquery.fileupload-process': 'null',       './jquery.fileupload-process': 'null',
    './jquery.fileupload-image': 'null',       './jquery.fileupload-image': 'null',
    './jquery.fileupload-video': 'null',       './jquery.fileupload-video': 'null',
    './jquery.fileupload-validate': 'null',       './jquery.fileupload-validate': 'null',
     
    // blueimp-gallery plugin       // blueimp-gallery plugin
    './blueimp-helper': 'window.jQuery', <>     './blueimp-helper': 'jQuery',
    './blueimp-gallery': 'window.blueimpGallery',       './blueimp-gallery': 'blueimpGallery',
    './blueimp-gallery-video': 'window.blueimpGallery'       './blueimp-gallery-video': 'blueimpGallery'
  } =   }
}   }
     
// -------------------------------------------------------------------------------   // -------------------------------------------------------------------------------
// Sourcemaps   // Sourcemaps
     
if (conf.sourcemaps) {   if (conf.sourcemaps) {
        webpackConfig.devtool = conf.devtool <>   webpackConfig.devtool = conf.devtool
} = }
     
// -------------------------------------------------------------------------------   // -------------------------------------------------------------------------------
// Minify   // Minify
     
// Minifies sources by default in production mode   // Minifies sources by default in production mode
if (process.env.NODE_ENV !== 'production' && conf.minify) {   if (process.env.NODE_ENV !== 'production' && conf.minify) {
        webpackConfig.plugins.push( <>   webpackConfig.plugins.push(
                new UglifyJsPlugin({       new UglifyJsPlugin({
                        uglifyOptions: {         uglifyOptions: {
                                compress: {           compress: {
                                        warnings: false             warnings: false
                                }           }
                        },         },
                        sourceMap: conf.sourcemaps,         sourceMap: conf.sourcemaps,
                        parallel: true         parallel: true
                })       })
        )     )
} = }
     
module.exports = webpackConfig   module.exports = webpackConfig