JRL About

Publishing a Webpack2 Package on NPM

March 1, 2017
Updated: November 30, 2021

Twitter Hacker News Reddit LinkedIn
Article Header Image
W

hile working on Birdu and other Phaser Games, I decided that I wanted to reuse some UI components between them. There are a couple of UI libraries for Phaser already - EZGUI, SlickUI, phaseslider, RSGUI, and more. However none of them fit my use cases, so I decided to write my own and publish the package to NPM. In this article I cover how to write your own ES6+ library, bundle it with Webpack2+Babel+ESLint, consume it in a calling application, and publish to NPM.

Writing the Library

First, make sure you have a good use-case for your library. Make sure someone hasn't already written one and go search Google. Libraries exist to avoid re-writing solved problems, so if you are copy+pasting code between projects then a library could be good for you. Write it once, have it updated everywhere, and share with others.

Now we can get started. Create a new directory and initialize your new NPM package with npm init.

1
2
3
4
5
6
mkdir MY_NEW_PROJECT && cd MY_NEW_PROJECT
mkdir src && mkdir test && mkdir build
touch webpack.config.js && touch .editorconfig && touch .eslintrc && touch .gitignore && touch README.md
git init            #I like to use Git for my VCS, you can use whatever
npm init
npm install --save-dev webpack babel-core babel-loader babel-preset-es2015 eslint eslint-loader

The npm init command will ask you some questions about your project so it can initilize your package info. You can leave the prompts empty for now, and we will overwrite them as we go (or you can do some research and fill them out now). You may notice this little script also initilizes src, test, and build folders as well as webpack.config.js, editor config, eslint, gitignore configurations and a README file. Check out my library to copy+paste default configurations; these tools make it easy to manage your code and editor. The node modules we install are Webpack2, Babel, and eslint. They bundle JS code into a single file, transpile it (make it work on old browsers), and check your source code for errors, respectively.

If you're using es2015, you want to download that preset for babel (it is included in the above script). Then you need to add that configuration to your package.json:

1
2
3
4
5
6
7
{ ...
  "babel": {
    "presets": [
      "es2015"
    ]
},
... }

Now that we have the basic goodies we can start writing source code. Go to your src/ folder and create an index.js file. Here, export all your JS components in order to make them useable by external code (consumers).

1
2
export { default as ProgressBar } from './Progress/ProgressBar.js';
// More exports...

Now we need to create the ProgressBar class that we have exported in index.js.

1
2
#Bash script to create ProgressBar
mkdir Progress && cd Progress && touch ProgressBar.js

Open the ProgressBar file and fill it with a default class. This is the class we are exporting from index.js.

1
2
3
4
5
import Graphics from '../Misc/Graphics';

export default class ProgressBar extends Progress {
  //...
}

Notice that we only create one 'class' (I know, JS doesn't use real classes) in this file, a default class. You don't have to do this, but it helps to keep things modular. We also imported the Graphics object, just to give an example of how you might do so. Graphics would be created in the same way as ProgressBar, and since it's imported you can use its functions as you write ProgressBar.

Bundling with Webpack2

Webpack is similar to Browserify, both bundle your many JS files into a single file. They also can process your files via loaders (Webpack) or plugins (Browserify). This is also pretty similar to Gulp and Grunt. Gulp/Grunt are task runners so they perform different functions from Webpack/Browserify, but it is possible to use Webpack without a task runner - that's what I'm doing here. Task runner require code, Webpack requires configuration.

Webpack recently upgraded to Webpack2, so a lot of the online tutorials use outdated Webpack1 configuration - stuff that is not compatible. Here is the new config documentation. For phaser-ui I currently have entry (define first file Webpack should process - index.js), output (configure the build), externals (allows use of yet-undefined variables e.g. Phaser, jQuery, React), and rules (modifies how module is created e.g. Babel+ESLint) defined on my webpack.config.js.

The only parts I found confusing were accidentally using old documentation, and defining the right output attributes. Since we are making a library, you must add library and libraryTarget properties to your output config. If you want users to import your library into JS files, then make the target 'umd', if you want them to download it via a script tag and use a global variable, you can leave the target blank (var is default). You can see how to do this in more detail at Webpack's 'Authoring Libraries' article.

While you're modifying the output object, you should specify the path and filename of your build output. Then, copy that value over to your package.json's main configuration - this allows consumers of your package to use the right files after downloading.

Once you have the output portion set up correctly, you can add rules in the module section. Rules can be used to setup parser options, loaders, and more. We are using the Babel and ESLint loaders, but you may also want to add in a live reloading development server or more.

At the end, your webpack may look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var webpack = require('webpack');

module.exports = {
  entry: './src/index.js',
  //setup the webpack output as a library
  output: {
    path: 'build/',
    filename: 'phaser-ui.js',
    libraryTarget: 'umd',
    library: 'phaserUi'
  },
  //needed in src library when extending/using Phaser objects/code. Relies on Consumers to import/include phaser and thus have the 'Phaser' variable globally available
  externals: {
    Phaser: 'Phaser'
  },
  module: {
    rules: [{
        enforce: 'pre', //check source files, not modified by other loaders (like babel-loader)
        test: /(\.jsx|\.js)$/, //files to check
        exclude: /(node_modules|bower_components)/, //files to ignore
        loader: 'eslint-loader',
        options: {
          emitWarning: true //do not crash build when finding a linting error
        }
      },
      {
        test: /(\.jsx|\.js)$/,
        exclude: /(node_modules|bower_components)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015']
        }
      }
    ]
  }
};

Publishing on NPM

This is a pretty easy process, outlined in NPM documentation here. Before you publish, you should make sure your package.json looks ok, here's mine for reference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  "name": "phaser-ui",
  "version": "0.0.3",
  "description": "Easy to use UI components for the Phaser.io game engine",
  "main": "build/phaser-ui.js",
  "scripts": {
    "test": "npm run build && cd test && npm install phaser-ui && npm start",
    "build": "webpack --display-error-details"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/jarlowrey/phaser-ui.git"
  },
  "keywords": [
    "phaser",
    "game-dev",
    "UI"
  ],
  "author": "jarlowrey",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/jarlowrey/phaser-ui/issues"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  },
  "homepage": "https://github.com/jarlowrey/phaser-ui#readme",
  "devDependencies": {
    "babel-core": "^6.23.1",
    "babel-loader": "^6.3.2",
    "babel-preset-es2015": "^6.22.0",
    "eslint": "^3.16.1",
    "eslint-loader": "^1.6.3",
    "webpack": "^2.2.1"
  }
}

Now that your package is good, you can publish!

1
2
npm adduser #create or login to your NPM account
npm publish #publish! Everything not ignored (by .gitignore or .npmignore) will be sent to NPM. Users will use the 'main' file when they download your package

If you ever want to update, simply up your version number and publish again

1
2
npm version <new_version_number> #semantic versioning release types, patch, minor, or major
npm publish

Now you can navigate to https://www.npmjs.com/package/MY_PACKAGE_NAME and it will be live!