Can I use webpack for creating a single js file for all my application code with its libraries?

Viewed 744

I've created this web app on codesandbox that prints some GeoJSON data on a map with Deck.gl. It has a webpack.config.js file and it's working fine... However, I'd like to know a way of compiling this application into a single js file, including the code that I've developed with its libraries included. I want to do that because with this js file I'd be able to embed this application inside a different app that uses only pure Javascript.

Basically, the webpack.config.js from the project is:

const CONFIG = {
  mode: "development",

  entry: {
    app: "./app.js"
  },

  output: {
    library: "App"
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules)/,
        loader: "babel-loader",
        options: {
          presets: [
            "@babel/preset-env",
            "@babel/react",
            {
              plugins: ["@babel/plugin-proposal-class-properties"]
            }
          ]
        }
      }
    ]
  }
};

module.exports = (env) =>
  env ? require("../../webpack.config.local")(CONFIG)(env) : CONFIG;

And its current package.json is:

{
  "name": "deck-gl-point",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "start-local": "webpack-dev-server --env.local --progress --hot --open",
    "start": "webpack-dev-server --progress --hot --open"
  },
  "dependencies": {
    "@types/react": "^17.0.37",
    "@types/react-dom": "^17.0.11",
    "@deck.gl/aggregation-layers": "8.3.3",
    "csvtojson": "2.0.10",
    "global": "4.3.2",
    "react-scripts": "2.1.8",
    "deck.gl": "^8.6.0-alpha.0",
    "react": "^16.14.0",
    "react-dom": "^16.14.0",
    "react-map-gl": "^6.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.4.0",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.5",
    "webpack": "^4.20.2",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.1"
  },
  "keywords": [],
  "description": ""
}

I know webpack is made for bundling projects in single files. But I don't know how to start modifying these files for generating a single js file for my project... Is it possible to do? What do I have to modify in order to bundle this project into a single file?

1 Answers

This is exactly the purpose of Webpack. If your code is just JavaScript, your configuration is mostly right. But you also, have some HTML and CSS along with that JS code which I assume must also be shipped with your single file.

Adding CSS is easy. You need to use css-loader and style-loader. Import the CSS code from JavaScript instead of referencing the file directly inside the index.html.

// app.js file
import './style.css';

For your HTML part, you have multiple options. Write the HTML code inside the JavaScript file using template literal and when the app loads, add this HTML to DOM using innerHTML or DocumentFragment. Alternately, you can use html-loader to load HTML from separate file and then inject using innerHTML, etc. But since, you are already using Vue and React, you probably can move the initial HTML to respective libraries.

Once, everything is moved to JavaScript, we can use same configuration that you have provided with minor tweaks for the entry and output configuration:

module.exports = {
  entry: "./app.js",

  output: {
    filename: 'bundle.js'
  }
}

Since you don't have multiple entry points, I changed entry to use string instead of object format. You can then use it as a CDN link or equivalent:

<script src="https://example.com/path-to-bundle-library/bundle.js"></script>

This also assumes that the page where you are adding this JS code will have required DOM elements.

To go into more details, Webpack provides two ways to bundle your code. First is a application bundle and second is a library bundle which can then be used in other packages.

When using Webpack for application bundling, you do not get any exported object. You simply take the bundled JS file, add it to your HTML using script and then it executes immediately like IIFE. This is what we have done here.

In case of a library bundle, things are a bit more complex. With this way, whatever you export from app.js file would be available for consumption using global object App in your calling application. So, it is advisable to not have any direct side-effect (directly running the code as soon as file is added to the page). In your code, the top-most for loop, jQuery functions $(#range), etc. are those side effects. Ideally, you should wrap all of this code inside some main or initialize function and export that.

When bundling using library, you get all the the exports available under the global object with a name you provide to the library field in the output configuration:

module.exports = {
  entry: "./app.js",

  output: {
    library: "App"
  }
};

You can then use it as:

<script src="https://example.com/path-to-bundle-library/my-library.js"></script>
<script>
  // Run the code
  App.initialize();
</script>

Further as an optional step, you should consider few more things if you need to bundle the code as library:

  1. The module format you want to use - ES Module, CommonJS, UMD, etc.
  2. How to ship your library for others to consume - CDN, NPM registry, etc.

These decisions will change Webpack configuration slightly.

Related