Lee Morgan
Lee Morgan

Reputation: 688

esbuild not bundling files into a single output

I am trying to use esbuild to bundle and minify my files for an npm project. It is minimizing every file that I pass in, but it is not bundling them into a single output file. It gives me the error that I must use the outdir property when there are multiple files. However, using outdir gives me back all of those files, minimized, in a folder. This is not the behavior that I want - I need it to take all of those files and merge them into one.

My build script is as follows:

let {build} = require("esbuild");
let files = ["file1.js", "file2.js"];

build({
    entryPoints: files,
    outdir: "./views/dashboardPage/bundle",
    minify: true,
    bundle: true
}).catch(() => process.exit(1));

I have bundle set to true, but it still demands that I use outdir and it just returns the input files to me, minimized.

How can I bundle all of the files into a single output?

Upvotes: 14

Views: 20406

Answers (5)

jla
jla

Reputation: 4761

Bundling vs concatenating

In this case, esbuild is doing what it's intended to do. It does bundle files - that is, if file 1 has an import statement pointing to file 2, it will include the compilation result of file 2 into the overall output for file 1. However it doesn't concatenate files - that is, if file 1 and file 2 do not have any internal reference to each other, if they are both passed to esbuild their output will be kept as separate files.

It does not appear that esbuild will natively support file concatenation any time soon. From a related issue on the esbuild Github the maintainer says:

Bundling means each input file is a module and modules are isolated from each other by design. Since esbuild is a bundler, it's not going to be able to do [that]

...This is explicitly not what a bundler does.

The recommendation from esbuild is to keep a single entry point file with imports to all your other source files, and bundle just that one file with esbuild. However this isn't very feasible if you have a dynamic set of input files that is only determined at build time, or if it's a large project with many files that are constantly being added.

How to concatenate

We can work around this by using the build script to generate a temporary file of imports just before building. We then pass the temporary file as an entrypoint to esbuild, and remove the file once the build is complete.

import { build } from 'esbuild'
import fs from 'fs'
import path from 'path'

// Array of input files. Can be dynamically generated at build time
const entries = [
    'file-1.js',
    'subdirectory/file-2.js',
    'very/deep/subdirectory/file-3.js'
]

// Generate tmp file of imports
const tmpFile = 'build/tmp.js'
fs.writeFileSync(
    tmpFile,
    entries
        .map( entry => `import "${ path.resolve( entry ) }"` )
        .join( ';\n' )
)

// esbuild
await esbuild.build( {
    entryPoints: [ tmpFile ],
    bundle: true,
    minify: true
    target: 'es6',
    outfile: 'build/main.js',
} )

// Clean up temporary file
fs.unlinkSync( tmpFile )

The temporary file will look like

import "/path/to/file-1.js";
import "/path/to/subdirectory/file-2.js";
import "/path/to/very/deep/subdirectory/file-3.js";

and this will be sufficient for esbuild to concatenate all files into a single output.

Note that if you're using watch with esbuild, you might run into problems with this method, as the temporary file won't get regenerated when esbuild detects a file change. You may need to use an external file watcher like Chokidar to handle watching.

Upvotes: 1

Marcos Viana
Marcos Viana

Reputation: 171

The problem is because the files are not being found.


Install this package to grab all files from the folder:

npm i files-folder

Your esbuild.config.js file will look like this one:

import { filesFromFolder } from 'files-folder'
import esbuild from 'esbuild'

esbuild
    .build({
        entryPoints: filesFromFolder('src'),
        bundle: true,
        minify: true,
        sourcemap: true,
        target: 'node16',
        define: { 'require.resolve': undefined },
        platform: 'node',
        allowOverwrite: true,
        outdir: 'views/dashboardPage/bundle'
    })
    .catch((error) => {
        process.exit(1)
    })

Upvotes: 0

txigreman
txigreman

Reputation: 359

Based on Alexander response I finally came to this solution to pack a whole folder into a single file:

const esbuild = require('esbuild');
const glob = require('glob');

esbuild
    .build({
        stdin: { contents: '' },
        inject: glob.sync('src/js/**/*.js'),
        bundle: true,
        sourcemap: true,
        minify: true,
        outfile: 'web/js/bundle.js',
    })
    .then(() => console.log("⚡ Javascript build complete! ⚡"))
    .catch(() => process.exit(1))

Upvotes: 6

Alexander
Alexander

Reputation: 61

You can use inject option (https://esbuild.github.io/api/#inject), for example,

const options = {
  entryPoints: ['index.js'],
  inject: ['file1.js', ..., 'fileN.js'],
  bundle: true,
  minify: true,
  sourcemap: true,
  outdir: path,
};
esbuild.buildSync(options);

Upvotes: 6

constexpr
constexpr

Reputation: 1041

Each entry point file will become a separate bundle. Each bundle includes the entry point file and all files it imports. Passing two entry points will create two separate bundles. The bundling process is not the same thing as file concatenation.

If you want all of the files in a single bundle, you can reference them all from a single file and use that file as the entry point:

import "./file1.js"
import "./file2.js"

Doing that with esbuild could look something like this:

let {build} = require("esbuild");
let files = ["./file1.js", "./file2.js"];

build({
    stdin: { contents: files.map(f => `import "${f}"`).join('\n') },
    outfile: "./views/dashboardPage/bundle.js",
    minify: true,
    bundle: true
}).catch(() => process.exit(1));

Upvotes: 12

Related Questions