There is a project SlickGrid that have multiple files that are all written as iife (it was originally built with jQuery namespace as iife). Most files are optional and the user can choose which feature they are interested (ie slick.contextmenu.js
, slick.headermenu.js
, ...) by loading the associated JavaScript feature file(s) and by doing so it will simply extend the Slick
object that exist on the window object (basically the core file has an esbuild globalName: 'Slick'
defined and the other files are simply extending on it whenever they are loaded, it's not tree shakeable but it's a nice way to keep a build size small by only loading what feature they want).
I'd like to keep these iife file separate for the users who still want to use standalone <script>
loading but also want to provide ESM (a single file bundle) in a separate build folder for ESM. I think that I can achieve this with esbuild by writing an esbuild plugin by using onResolve
. I manage to make it work but it's not the most elegant, I'd like help to find a better solution
import {
Event as SlickEvent_,
EventData as SlickEventData_,
EditorLock as SlickEditorLock_,
Utils as SlickUtils_,
} from './slick.core.js';
import { Draggable as SlickDraggable_, MouseWheel as SlickMouseWheel_, Resizable as SlickResizable_ } from './slick.interactions.js';
// TODO: I would like to avoid having to write all of the following lines which are only useful for iife
// for iife, pull from window.Slick object but for ESM use named import
const SlickEvent = window.Slick ? Slick.Event : SlickEvent_;
const EventData = window.Slick ? Slick.EventData : SlickEventData_;
const EditorLock = window.Slick ? Slick.EditorLock : SlickEditorLock_;
const Utils = window.Slick ? Slick.Utils : SlickUtils_;
const Draggable = window.Slick ? Slick.Draggable : SlickDraggable_;
const MouseWheel = window.Slick ? Slick.MouseWheel : SlickMouseWheel_;
const Resizable = window.Slick ? Slick.Resizable : SlickResizable_;
// ...
// then use it normally in the code...
const options = Utils.extend(true, {}, defaults, options);
So the custom plugin that I wrote seems to work, but it's a bit hacky, and it will use either window.Slick
for iife (when found) OR use the named import for ESM usage. Running a build for ESM will be roughly the same but without using any plugin since we want to bundle everything into a single bundled file and keep named imports like a regular build.
However please note the intentation is to still produce multiple files for the iife build, that is even if we use bundle :true
because the plugin will simple replace any of the imports with an empty string.
in other words, the plugin is simply loading the code from the associated window.Slick.featureXYZ
and replaces the import with an empty string because the code exist in the window.Slick
object already so we don't need to use the imported code again (hence why we replace that part with an empty string)
import { build } from 'esbuild';
const myPlugin = {
name: 'my-plugin',
setup(build) {
build.onResolve({ filter: /.*/ }, args => {
if (args.kind !== 'entry-point') {
return { path: args.path + '.js', namespace: 'import-ns' }
}
})
build.onLoad({ filter: /.*/, namespace: 'import-ns' }, (args) => {
return {
contents: `// empty string, do nothing`,
loader: 'js',
};
})
}
};
build({
entryPoints: ['slick.grid.js'],
color: true,
bundle: true,
minify: false,
target: 'es2015',
sourcemap: false,
logLevel: 'error',
format: 'iife',
// globalName: 'Slick', // only for the core file
outfile: 'dist/iife/slick.grid.js',
plugins: [myPlugin],
});
So this approach seems to work but is not very elegant, ideally it would be great if I could get the named imports and replace them directly in the code and avoid having to write all these extra lines after the imports in my codebase.
Does anyone have a better solution? Is there a way to get named imports in esbuild onResolve
and onLoad
?
So far what I found is that esbuild only provides the kind
property as import-statement
but it doesn't provide the named import that goes with it. If by any chance I could find how to get them, then I could maybe write my own code in the onLoad
to override it with something like var Utils = window.Slick.${namedImport}
for iife without having to write all these extra lines by myself in the codebase (ie: const SlickEvent = window.Slick ? Slick.Event : SlickEvents;
), this would also cleanup these unused lines in my ESM build (it's only useful for the iife build).
EDIT
I found this esbuild request issue Request: Expose list of imports in onLoad/onResolve argument to allow custom tree-shaking which is asking for the same thing I was looking for. The feature request was rejected because it might not be possible within esbuild itself but a suggestion was posted to find the named imports, so I'll give that a try