Open raikasdev opened 7 months ago
Would love to see this as well. I have been investigating bun as a potential replacement to our current bundling solution, and bumped into this.
You can do it as a plugin:
import type { BunPlugin } from "bun";
// port of a https://github.com/a-b-r-o-w-n/esbuild-plugin-globals to Bun
type GlobalResolveFunc = (moduleName: string) => string | undefined;
type PluginGlobalsOptions = {
[key: string]: string | GlobalResolveFunc;
};
const generateResolveFilter = (globals:PluginGlobalsOptions) => {
const moduleNames = Object.keys(globals);
return new RegExp(`^(${moduleNames.join("|")})$`);
};
const generateExport = (globals:PluginGlobalsOptions, name:string) => {
const match = Object.entries(globals).find(([pattern]) => {
return new RegExp(`^${pattern}$`).test(name);
});
if (match) {
const output = typeof match[1] === "function" ? match[1](name) : match[1];
return output ? `module.exports = ${output}` : undefined;
}
};
const pluginGlobals = (globals:PluginGlobalsOptions = {}) => {
const filter = generateResolveFilter(globals);
return {
name: "globals",
setup(build) {
build.onResolve({ filter }, (args) => {
return { path: args.path, namespace: "globals" };
});
build.onLoad({ filter: /.*/, namespace: "globals" }, (args) => {
const name = args.path;
const contents = generateExport(globals, name);
if (contents) {
return { contents };
}
return null;
});
},
} as BunPlugin;
};
export default pluginGlobals;
then your build script goes like this
import { build } from "bun";
import globalsPlugin from "./globalsPlugin";
build({
entrypoints: ["src/app.tsx"],
format: "esm",
outdir: "dist",
splitting: true,
minify: false,
plugins: [
globalsPlugin({
react: "react",
}),
],
})
It would be nice to avoid declaring globals requiring any Bunfig changes (or creating a Bunfig at all), and to follow TS/JS conventions.
At least for TypeScript projects, just making use of how globals are declared in TypeScript already seems best. That is, put in (for example) global.d.ts
something like:
export {} // (needed so this is considered a module)
import type * as JQuery from 'jquery';
declare global {
interface Window {
$: JQuery;
}
}
For vanilla JavaScript inputs, I'd propose recognizing ESLint's /* global ... */
as that's the most common idiom I know. Another consideration would be JSDoc's @global
tag. One potential drawback of the ESLint convention is that someone reasonably might specify their globals in eslint.config.js
and then get confused as to why Bun isn't recognizing them as globals.
Ok did a quick experiment, and (to the original question) as best as I can tell this works perfectly well in Bun 1.1.30.
No type checker or build errors, the output looks correct (and works), etc. This only requires bun add @types/jquery
, the implementation is not needed.
example.ts:
import type * as JQuery from 'jquery';
export {}
declare global {
interface Window {
$: JQuery
}
}
$(() => {
$("#title").text("Text set by jQuery!");
console.log("It works!");
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="example.js"></script>
</head>
<body>
<h1 id="title">It didn't work.</h1>
</body>
</html>
Built with bun build --target browser --format iife example.ts > example.js
.
(() => {
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __moduleCache = /* @__PURE__ */ new WeakMap;
var __toCommonJS = (from) => {
var entry = __moduleCache.get(from), desc;
if (entry)
return entry;
entry = __defProp({}, "__esModule", { value: true });
if (from && typeof from === "object" || typeof from === "function")
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
}));
__moduleCache.set(from, entry);
return entry;
};
// index.ts
var exports_bun_global_names = {}; // author note: folder/project was named bun-global-names
$(() => {
$("#title").text("Text set by jQuery!");
console.log("It works!");
});
})();
This seems to work fine for me?
What is the problem this feature would solve?
Allow using global variables instead of bundling dependencies in environments like the WordPress Block Editor, where React is available already at window.React, etc. Discord thread
What is the feature you are proposing to solve the problem?
Allow mapping dependencies to global variables and not bundle them.
What alternatives have you considered?
No response