Expose javascript globals bundled via webpack

Anselm picture Anselm · Jun 23, 2017 · Viewed 7.1k times · Source

The Problem

I feel like this should be more straightforward than it is. I need to access all my javascript libraries from the frontend and because I'm integrating it into an old system, I cannot call require("bundle.js"); from the frontend. Everything in the global scope of the bundled files must be accessible from the global scope of the frontend page importing them through the <script> tag.

So I need to change the old:

<script src="js/jquery.js"></script>
<script src="js/silly.js"></script>
<script>
    $(silly()); // Some function in silly.js's global scope
</script>

To the new:

<script src="js/bundle.js"></script>
<script>
    $(silly()); // Some function in silly.js's global scope
</script>

Things I've tried

  1. expose-loader: This would totally work if I didn't have 100 global variables that I don't want to explicitly tell it to look for.

  2. ProvidePlugin: Only really lets the libraries see the other libraries. I also cannot explicitly write all the globals I need with my current setup (More are added constantly).

What I need

So for more clarity, I need my webpack.config.js to look like one of these options:

// Everything is wrapped in module.exports and other irrelevant things
plugins: [
         new StaticLibraryMergerSuperNeatPlugin("js/*.js")
]
// ...

Or:

rules: [
        {
            test: /\.js$/,
            use: [
                "neat-merging-cool-loader",
                "babel-loader"]
            
        }
]
// ...

Am I going about this wrong?

Is there an obvious solution I am missing?

Tl;Dr: How do I make globals from my bundled js files, be exposed to the global scope when imported on a frontend html page via <script src="js/bundle.js"></script>?

Btw: If anyone is a webpack legend and knows why this is a bad approach, please post below with a brief explanation so I can fix my life.

Answer

mhodges picture mhodges · Jun 23, 2017

Here's an example of how I do it in my own site. I'm not sure if it's the only way, or even the best way, but it's clean, simple, and it works for me.

Important side note - Use window["propName"] when declaring things on the window because when you run webpack -p it will uglify any non-strings, so if you define it as window.propName, it can get changed to something like s.c and the rest of your code does not know what it is. Declaring it with bracket notation as a string will force webpack to keep the name intact so you can access it from anywhere with the same name.

site.ts (can be .js, doesn't matter)

/*************************/
/*** JQUERY + JQUERYUI ***/
/*************************/
/* var declaration for typescript - not needed if not using .ts */
declare var $:JQueryStatic; declare var jQuery:JQueryStatic;
window["$"] = window["jQuery"] = require("jquery");
require("jquery-ui/effects/effect-slide");
require("jquery-ui/widgets/autocomplete");
require("jquery-ui/widgets/button");
require("jquery-ui/widgets/datepicker");
require("jquery-ui/widgets/tooltip");
/*************************/
/* END JQUERY + JQUERYUI */
/*************************/

/***************/
/*** ANGULAR ***/
/***************/
/* var declaration for typescript - not needed if not using .ts */
declare var angular:ng.IAngularStatic;
window["angular"] = require("angular");
require("angular-sanitize");
/***************/
/* END ANGULAR */
/***************/

/************************/
/*** MISC THIRD-PARTY ***/
/************************/
window["moment"] = require("moment");
window["saveAs"] = require("FileSaver").saveAs;
window["JSZip"] = require("jszip");
/************************/
/* END MISC THIRD-PARTY */
/************************/

/* var declaration for typescript - not needed if not using .ts */
declare var globals:Globals;
window["globals"] = require("./globals");

Layout.html (loaded on every page)

.....
<script src="/dist/scripts/site.bundle.js"></script>
.....

webpack.config.js

var path = require('path');
var resolve = path.resolve;
var AssetsPlugin = require('assets-webpack-plugin');
var WebpackCleanupPlugin = require("webpack-cleanup-plugin");
'use strict';

var babelOptions = {
    "presets": [
      [
        "es2015",
        {
            "modules": false
        }
      ],
      "es2016"
    ]
};

module.exports = [{
    cache: true,
    context: resolve('Scripts'),
    devtool: "source-map",
    entry: {
        site: './site.ts',
    },
    output: {
        path: path.resolve(__dirname, './dist/scripts'),
        filename: '[name].bundle.js',
    },
    module: {
        rules: [{
            test: /\.ts$/,
            exclude: /node_modules/,
            use: [
              {
                  loader: 'babel-loader',
                  options: babelOptions
              },
              {
                  loader: 'ts-loader'
              }
            ]
        }, {
            test: /\.js$/,
            exclude: /node_modules/,
            use: [
              {
                  loader: 'babel-loader',
                  options: babelOptions
              }
            ]
        }]
    },
    plugins: [
        new AssetsPlugin({ path: path.resolve(__dirname, './dist/assets') }),
        new WebpackCleanupPlugin({})
    ],
}];