Skip to content

Commit 64fb7ef

Browse files
JasonVMotido64
andauthored
feat(scripts): Rework bundle script to support bundling yarn plugins (#3519)
* add script to bundle yarn plugins * update bundle command to treat platform as a preset selector, including yarn * docs(changeset): * dedupe packages in lockfile * Apply suggestions from code review Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com> * add missing yarn bundle setting and code review feedback --------- Co-authored-by: Tommy Nguyen <4123478+tido64@users.noreply.github.com>
1 parent d2960b1 commit 64fb7ef

File tree

4 files changed

+1472
-56
lines changed

4 files changed

+1472
-56
lines changed

.changeset/mean-lions-hammer.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

scripts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"lint": "rnx-kit-scripts lint"
1414
},
1515
"dependencies": {
16+
"@yarnpkg/cli": "^4.6.0",
1617
"esbuild": "^0.25.0",
1718
"eslint": "^9.0.0",
1819
"fast-glob": "^3.2.7",

scripts/src/commands/bundle.js

Lines changed: 130 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,155 @@
11
// @ts-check
22

3+
import { getDynamicLibs } from "@yarnpkg/cli";
34
import * as fs from "node:fs";
45

5-
const defaultOptions = {
6-
minify: false,
7-
platform: "node",
6+
/**
7+
* @typedef {import("esbuild").BuildOptions} BuildOptions
8+
* @typedef {BuildOptions["platform"]} Platform
9+
* @typedef {{ node?: string }} Engines
10+
* @typedef {{ name: string, main: string, dependencies?: Record<string, string>, peerDependencies?: Record<string, string>, engines?: Record<string, string> }} Manifest
11+
* @typedef {{ minify?: boolean, platform?: string, sourceMap?: boolean }} BundleOptions
12+
* @typedef {(manifest: Manifest) => Partial<BuildOptions>} OptionPreset
13+
*/
14+
15+
const defaultTarget = "es2021";
16+
const defaultNodeTarget = "node16.17";
17+
18+
/**
19+
* @param {Manifest} manifest
20+
* @returns {Partial<BuildOptions>}
21+
*/
22+
function presetBase(manifest) {
23+
const { dependencies, peerDependencies } = manifest;
24+
return {
25+
external: [
26+
...(dependencies ? Object.keys(dependencies) : []),
27+
...(peerDependencies ? Object.keys(peerDependencies) : []),
28+
"./package.json",
29+
],
30+
conditions: ["typescript"],
31+
};
32+
}
33+
34+
/**
35+
* @param {Manifest} manifest
36+
* @returns {Partial<BuildOptions>}
37+
*/
38+
function nodePreset(manifest) {
39+
return {
40+
...presetBase(manifest),
41+
banner: { js: "#!/usr/bin/env node" },
42+
platform: "node",
43+
target: getNodeTarget(manifest),
44+
};
45+
}
46+
47+
/**
48+
* @param {Manifest} manifest
49+
* @returns {Partial<BuildOptions>}
50+
*/
51+
function browserPreset(manifest) {
52+
return {
53+
...presetBase(manifest),
54+
platform: "browser",
55+
target: defaultTarget,
56+
};
57+
}
58+
59+
/**
60+
* @param {Manifest} manifest
61+
* @returns {Partial<BuildOptions>}
62+
*/
63+
function neutralPreset(manifest) {
64+
return {
65+
...presetBase(manifest),
66+
platform: "neutral",
67+
target: defaultTarget,
68+
};
69+
}
70+
71+
/**
72+
* @param {Manifest} manifest
73+
* @returns {Partial<BuildOptions>}
74+
*/
75+
function yarnPreset(manifest) {
76+
const name = manifest.name;
77+
return {
78+
banner: {
79+
js: [
80+
`/* eslint-disable */`,
81+
`//prettier-ignore`,
82+
`module.exports = {`,
83+
`name: ${JSON.stringify(name)},`,
84+
`factory: function (require) {`,
85+
].join(`\n`),
86+
},
87+
globalName: `plugin`,
88+
format: "iife",
89+
footer: {
90+
js: [`return plugin;`, `}`, `};`].join(`\n`),
91+
},
92+
resolveExtensions: [`.tsx`, `.ts`, `.jsx`, `.mjs`, `.js`, `.css`, `.json`],
93+
external: [...getDynamicLibs().keys()],
94+
platform: "node",
95+
target: getNodeTarget(manifest),
96+
supported: {
97+
/*
98+
Yarn plugin-runtime did not support builtin modules prefixed with "node:".
99+
See https://github.com/yarnpkg/berry/pull/5997
100+
As a solution, and for backwards compatibility, esbuild should strip these prefixes.
101+
*/
102+
"node-colon-prefix-import": false,
103+
"node-colon-prefix-require": false,
104+
},
105+
};
106+
}
107+
108+
/** @type {Record<string, OptionPreset>} */
109+
const presets = {
110+
node: nodePreset,
111+
browser: browserPreset,
112+
neutral: neutralPreset,
113+
yarn: yarnPreset,
8114
};
9115

10116
/**
11117
* @param {unknown} platform
12-
* @returns {"browser" | "neutral" | "node"}
118+
* @param {Manifest} manifest
119+
* @returns {Partial<BuildOptions>}
13120
*/
14-
function ensureValidPlatform(platform) {
15-
switch (platform) {
16-
case "browser":
17-
case "neutral":
18-
case "node":
19-
return platform;
121+
function platformOptions(platform, manifest) {
122+
const platformKey = typeof platform === "string" ? platform : "node";
123+
const preset = presets[platformKey] || presets.node;
124+
return preset(manifest);
125+
}
20126

21-
default:
22-
return "node";
23-
}
127+
/**
128+
* @param {Manifest} manifest
129+
* @returns {string}
130+
*/
131+
function getNodeTarget(manifest) {
132+
const enginesNode = manifest.engines?.node;
133+
const match = enginesNode?.match(/(\d+)\.(\d+)/);
134+
return match ? `node${match[1]}.${match[2]}` : defaultNodeTarget;
24135
}
25136

26137
/**
27138
* @param {Record<string, unknown> | undefined} options
28139
*/
29140
export async function bundle(options) {
30-
const { minify, platform } = { ...defaultOptions, ...options };
31-
const targetPlatform = ensureValidPlatform(platform);
141+
const { minify, platform, sourceMap } = options || {};
32142

33-
const manifest = fs.readFileSync("package.json", { encoding: "utf-8" });
34-
const { main, dependencies, peerDependencies } = JSON.parse(manifest);
143+
const manifestFile = fs.readFileSync("package.json", { encoding: "utf-8" });
144+
const manifest = JSON.parse(manifestFile);
35145

36146
const esbuild = await import("esbuild");
37147
await esbuild.build({
148+
...platformOptions(platform, manifest),
38149
bundle: true,
39-
outfile: main,
40-
external: [
41-
...(dependencies ? Object.keys(dependencies) : []),
42-
...(peerDependencies ? Object.keys(peerDependencies) : []),
43-
"./package.json",
44-
],
45-
conditions: ["typescript"],
46-
banner:
47-
targetPlatform === "node" ? { js: "#!/usr/bin/env node" } : undefined,
150+
outfile: manifest.main,
48151
entryPoints: ["src/index.ts"],
49-
target: targetPlatform === "node" ? "node16.17.0" : "es2021",
50-
platform: targetPlatform,
51152
minify: Boolean(minify),
153+
sourcemap: Boolean(sourceMap),
52154
});
53155
}

0 commit comments

Comments
 (0)