Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expose javascript globals bundled via webpack

Tags:

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.

like image 230
Anselm Avatar asked Jun 23 '17 15:06

Anselm


People also ask

What is libraryTarget in webpack?

This is according to their documentation: "libraryTarget: "umd" - This exposes your library under all the module definitions, allowing it to work with CommonJS, AMD and as global variable." Also, I built the exact same code with Webpack 3 and it produced a proper bundle.

What is bundling in webpack?

In the wake of React—Facebook's UI library—came Webpack, a simple but awesome module bundler. Module bundlers are just what they are called, they bundle up JavaScript modules into one file. This way, when the client makes a request to your server, it doesn't have to make multiple requests for static files.

Does webpack bundle Node_modules?

Webpack allows you to define externals - modules that should not be bundled. When bundling with Webpack for the backend - you usually don't want to bundle its node_modules dependencies. This library creates an externals function that ignores node_modules when bundling in Webpack.


2 Answers

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({})     ], }]; 
like image 180
mhodges Avatar answered Sep 21 '22 01:09

mhodges


Note: This is not the ideal scenario but because I have a constant amount of new globals being added, I needed to make a plugin to bundle my javascript for me.

webpack-raw-bundler

This simply stacks your code together to be included on the frontend. Here is my usage example:

Usage

From 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> 

Installing to the config

  var RawBundlerPlugin = require('webpack-raw-bundler');    module.exports = {     plugins: [        new RawBundlerPlugin({              excludedFilenames: [/angulartics/],              readEncoding: "utf-8",              includeFilePathComments: false,              bundles: [ "vendor.js", "styles.css" ],              "vendor.js": [                 'js/*.js'              ],              "styles.css": [                 'css/bootstrap.css',                 'css/edits.css'              ]        })     ]  } 

A Fair Warning:

This should not be your go-to solution, but I had a bad case that made this the easiest option. Using expose-loader and import or window['module'] = require('module.js') is much safer as that is what webpack was built around. However, if you are having some headaches and just want a simple bundler, feel free to use this plugin.

like image 33
Anselm Avatar answered Sep 21 '22 01:09

Anselm