Sanjay Purswani

@sanjsanj

Webpack: Creating dynamically named outputs for wildcarded entry files

If that isn’t a sexy title I don’t know what is.

Recently I hit a Webpack-sized brick wall whilst trying to get it to output bundles dynamically named after their entry files. The tricky part is that I wanted it to do it prospectively on all files of a certain type that I may want to write in the future.

I couldn’t find any answers online or node packages I could enlist, so I decided to write my own.

https://www.npmjs.com/package/webpack-entry-plus

The problem was as follows; I have a folder for all my core JavaScript:

|-- core
|-- file1.js
|-- file2.js

I know what those files are called, I also know that I want them to be bundled together and where to put that output file:

|-- build
|-- main.js << Bundle all core files here
|-- core
|-- file1.js
|-- file2.js

Easily done:

// webpack.config.js
module.exports = {
entry: ['./core/file1.js', './core/file2.js'],
output: {
filename: './build/[name].js', << [name] defaults to 'main'
}, << more on that later
}

Then I have a bunch of other folders somewhere that relate only to certain pieces of content and I want their bundles to be output relative to where the source entry files are:

|-- build
|-- main.js
|-- core
|-- file1.js
|-- file2.js
|-- content
|-- one
|-- index.js << This index
|-- main.js << Gets bundled here
|-- two
|-- sub
|-- index.js << This index
|-- main.js << Gets bundled here

As devs, we write the index.js but we want Webpack to pick it up and process the output main.js right next to it in the same folder. We were already able to do that by passing an Object to Webpack’s entry value, overriding the default [name] , and process multiple bundles. This does work as long as we know about the files we’re dealing with:

// webpack.config.js
module.exports = {
entry: {
build: ['./core/file1.js', './core/file2.js'],
content/one: ['./content/one/index.js'],
content/two/sub: ['./content/two/sub/index.js'],
},
output: {
filename: './[name]/main.js',
},
}

Webpack will build a bundle with each of the entry Object’s entries. The Keys (build, content/one, content/two/sub) will be passed down to output as the [name] value, and the files to be bundled will be the array values for each Key.

This technique is quite powerful and can be utilised to produce absolute filenames with extensions if you so wish, e.g:

...
entry: {
build/main.js: ['./core/file1.js', './core/file2.js'],
},
output: '[name]',
...

The final challenge in our puzzle is the fact that we don’t know all the content bundles we’re going to want to build, and even if we did we wouldn’t necessarily want to hard-code entries for all of them. So we use the glob node package to match a wildcard and return an array of all files that match it:

// webpack.config.js
const glob = require('glob');
entry: glob.sync('content/**/index.js'),  << Returns Array of files

And here, finally, is where the problem lay. There was no way to create a bundle with each of those wildcarded files, which was then output to the same folder as the entry.

But knowing what we know about the entry value Object and being able to manipulate the [name] to our advantage, it was a simple matter of one afternoon of coding to come up with a solution. Manipulate the filepath value to return a suitable [name] for our build:

// webpack.config.js
const glob = require('glob');
const entryArray = glob.sync('content/**/index.js');
const entryObject = entryArray.reduce((acc, item) => {
const name = item.replace('/index.js', '');
acc[name] = item;
return acc;
}, {});
module.exports = {
entry: entryObject,
output: '[name]/main.js',
}

The entryObject from above would return this, a suitable value for Webpack’s entry value:

{
content/one: ['./content/one/index.js'],
content/two/sub: ['./content/two/sub/index.js'],
}

If necessary we could use Object.assign() to combine multiple Objects of specified or wildcarded entry files to create appropriate output bundles using this technique, or use .reduce() or .map() or whatever we like. And that’s exactly what I did :)

But because I saw this as a common enough problem without an existing solution I created a simple package that can take care of all that for us, as well as give us a reasonable API for most other use cases.

Whether you want to roll your own custom implementation or want an out-of-the-box solution, I would recommend checking it out:

More by Sanjay Purswani

Topics of interest

More Related Stories