Reputation: 688
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
Reputation: 4761
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.
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
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
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
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
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