@jxsuite/compiler 7.0.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +133 -135
- package/dist/compiler.js +43 -43
- package/package.json +15 -8
- package/src/site/bundler-backend.bun.ts +84 -0
- package/src/site/bundler-backend.node.ts +58 -0
- package/src/site/bundler-backend.ts +53 -0
- package/src/site/bundler.ts +49 -110
- package/src/site/client-runtime.ts +6 -11
- package/src/site/format-host.ts +98 -48
- package/src/site/image-cache.ts +38 -12
- package/src/site/schema-command.ts +26 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/compiler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Jx static HTML compiler, island detector, and site builder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"type": "module",
|
|
20
|
+
"imports": {
|
|
21
|
+
"#bundler-backend": {
|
|
22
|
+
"bun": "./src/site/bundler-backend.bun.ts",
|
|
23
|
+
"default": "./src/site/bundler-backend.node.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
20
26
|
"exports": {
|
|
21
27
|
".": "./src/compiler.ts",
|
|
22
28
|
"./asset-mounts": "./src/site/asset-mounts.ts",
|
|
@@ -30,7 +36,8 @@
|
|
|
30
36
|
"./site": "./src/site/site-build.ts",
|
|
31
37
|
"./site-loader": "./src/site/site-loader.ts",
|
|
32
38
|
"./types": "./src/types.ts",
|
|
33
|
-
"./db-push": "./src/site/db-push.ts"
|
|
39
|
+
"./db-push": "./src/site/db-push.ts",
|
|
40
|
+
"./bundler": "./src/site/bundler.ts"
|
|
34
41
|
},
|
|
35
42
|
"publishConfig": {
|
|
36
43
|
"provenance": true
|
|
@@ -45,10 +52,10 @@
|
|
|
45
52
|
"upgrade": "bun update --latest && bun install"
|
|
46
53
|
},
|
|
47
54
|
"dependencies": {
|
|
48
|
-
"@jxsuite/create": "^1.
|
|
49
|
-
"@jxsuite/runtime": "^6.0.
|
|
50
|
-
"@jxsuite/schema": "^
|
|
51
|
-
"@jxsuite/site": "^2.2.
|
|
55
|
+
"@jxsuite/create": "^1.5.0",
|
|
56
|
+
"@jxsuite/runtime": "^6.0.1",
|
|
57
|
+
"@jxsuite/schema": "^5.0.0",
|
|
58
|
+
"@jxsuite/site": "^2.2.2",
|
|
52
59
|
"@vue/reactivity": "3.5.44",
|
|
53
60
|
"esbuild": "^0.28.2",
|
|
54
61
|
"lit-html": "^3.3.3",
|
|
@@ -56,8 +63,8 @@
|
|
|
56
63
|
},
|
|
57
64
|
"devDependencies": {
|
|
58
65
|
"@happy-dom/global-registrator": "^21.0.4",
|
|
59
|
-
"@jxsuite/connector": "^0.8.
|
|
60
|
-
"@jxsuite/parser": "^4.1.
|
|
66
|
+
"@jxsuite/connector": "^0.8.2",
|
|
67
|
+
"@jxsuite/parser": "^4.1.2",
|
|
61
68
|
"@jxsuite/test-support": "^0.0.0",
|
|
62
69
|
"@types/mdast": "^4.0.4"
|
|
63
70
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Bun bundler backend: `Bun.build` and `Bun.resolveSync`. Selected by the `bun` condition of
|
|
3
|
+
* `#bundler-backend` (`bundler-backend.ts`), so only a Bun process ever loads this file.
|
|
4
|
+
*
|
|
5
|
+
* `JX_BUNDLER=esbuild` hands every call to the esbuild backend instead, so a parity check can run
|
|
6
|
+
* both under one Bun.
|
|
7
|
+
*/
|
|
8
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import type { BuildConfig } from "bun";
|
|
11
|
+
import type { BackendBuild, BundleTargetName } from "./bundler-backend.ts";
|
|
12
|
+
import { TRANSIENT_ENTRY_PREFIX } from "./bundler-backend.ts";
|
|
13
|
+
import * as esbuildBackend from "./bundler-backend.node.ts";
|
|
14
|
+
|
|
15
|
+
/** True when this process was told to bundle with esbuild instead. */
|
|
16
|
+
function forcedToEsbuild(): boolean {
|
|
17
|
+
return process.env.JX_BUNDLER === "esbuild";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Which bundler this backend runs. */
|
|
21
|
+
export function backendName(): "bun" | "esbuild" {
|
|
22
|
+
return forcedToEsbuild() ? "esbuild" : "bun";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Resolve a bare specifier as an import from a module in `fromDir`. Throws when it does not. */
|
|
26
|
+
export function resolveBare(specifier: string, fromDir: string): string {
|
|
27
|
+
if (forcedToEsbuild()) {
|
|
28
|
+
return esbuildBackend.resolveBare(specifier, fromDir);
|
|
29
|
+
}
|
|
30
|
+
return Bun.resolveSync(specifier, fromDir);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Map a bundle target to Bun.build's target. */
|
|
34
|
+
function bunTarget(target: BundleTargetName): "browser" | "node" | "bun" {
|
|
35
|
+
if (target === "workerd") {
|
|
36
|
+
return "browser";
|
|
37
|
+
}
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Bundle one file with `Bun.build` and return the output's text. */
|
|
42
|
+
async function buildFile(entryPath: string, request: BackendBuild): Promise<string> {
|
|
43
|
+
const config: BuildConfig = {
|
|
44
|
+
entrypoints: [entryPath],
|
|
45
|
+
external: request.external,
|
|
46
|
+
format: "esm",
|
|
47
|
+
minify: request.minify,
|
|
48
|
+
plugins: request.plugins,
|
|
49
|
+
target: bunTarget(request.target),
|
|
50
|
+
throw: false,
|
|
51
|
+
};
|
|
52
|
+
if (request.conditions) {
|
|
53
|
+
config.conditions = request.conditions;
|
|
54
|
+
}
|
|
55
|
+
if (request.define) {
|
|
56
|
+
config.define = request.define;
|
|
57
|
+
}
|
|
58
|
+
const result = await Bun.build(config);
|
|
59
|
+
if (!result.success || result.outputs.length === 0) {
|
|
60
|
+
const logs = result.logs.map(String).join("\n");
|
|
61
|
+
throw new Error(`Bun.build failed for ${entryPath}:\n${logs}`);
|
|
62
|
+
}
|
|
63
|
+
return result.outputs[0]!.text();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Bundle to one ESM module and return its text. Throws with the backend's diagnostics on failure. */
|
|
67
|
+
export async function build(request: BackendBuild): Promise<string> {
|
|
68
|
+
if (forcedToEsbuild()) {
|
|
69
|
+
return esbuildBackend.build(request);
|
|
70
|
+
}
|
|
71
|
+
const { entry } = request;
|
|
72
|
+
if ("path" in entry) {
|
|
73
|
+
return buildFile(entry.path, request);
|
|
74
|
+
}
|
|
75
|
+
// Bun.build has no stdin input: write a transient entry in `resolveDir` so resolution matches
|
|
76
|
+
// The stdin/resolveDir behaviour, and always remove it.
|
|
77
|
+
const entryPath = join(entry.resolveDir, `${TRANSIENT_ENTRY_PREFIX}${process.pid}.mjs`);
|
|
78
|
+
writeFileSync(entryPath, entry.source, "utf8");
|
|
79
|
+
try {
|
|
80
|
+
return await buildFile(entryPath, request);
|
|
81
|
+
} finally {
|
|
82
|
+
rmSync(entryPath, { force: true });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The esbuild bundler backend: the default of `#bundler-backend` (`bundler-backend.ts`), so every
|
|
3
|
+
* runtime that is not Bun bundles with it, and Bun does too under `JX_BUNDLER=esbuild`.
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import type { BuildOptions } from "esbuild";
|
|
8
|
+
import type { BackendBuild, BundleTargetName } from "./bundler-backend.ts";
|
|
9
|
+
|
|
10
|
+
/** Which bundler this backend runs. */
|
|
11
|
+
export function backendName(): "bun" | "esbuild" {
|
|
12
|
+
return "esbuild";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Resolve a bare specifier as an import from a module in `fromDir`. Throws when it does not. */
|
|
16
|
+
export function resolveBare(specifier: string, fromDir: string): string {
|
|
17
|
+
return createRequire(join(fromDir, "package.json")).resolve(specifier);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Map a bundle target to esbuild's platform (conditions carry the workerd specifics). */
|
|
21
|
+
function esbuildPlatform(target: BundleTargetName): "browser" | "node" {
|
|
22
|
+
return target === "node" || target === "bun" ? "node" : "browser";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Bundle to one ESM module and return its text. Throws with esbuild's diagnostics on failure. */
|
|
26
|
+
export async function build(request: BackendBuild): Promise<string> {
|
|
27
|
+
// Dynamic import keeps esbuild (a native-binary dependency) off the load path until a bundle runs.
|
|
28
|
+
const esbuild = await import("esbuild");
|
|
29
|
+
const { entry } = request;
|
|
30
|
+
const options: BuildOptions = {
|
|
31
|
+
bundle: true,
|
|
32
|
+
external: request.external,
|
|
33
|
+
format: "esm",
|
|
34
|
+
logLevel: "silent",
|
|
35
|
+
minify: request.minify,
|
|
36
|
+
platform: esbuildPlatform(request.target),
|
|
37
|
+
plugins: request.plugins,
|
|
38
|
+
write: false,
|
|
39
|
+
};
|
|
40
|
+
if (request.conditions) {
|
|
41
|
+
options.conditions = request.conditions;
|
|
42
|
+
}
|
|
43
|
+
if (request.define) {
|
|
44
|
+
options.define = request.define;
|
|
45
|
+
}
|
|
46
|
+
if ("path" in entry) {
|
|
47
|
+
options.entryPoints = [entry.path];
|
|
48
|
+
} else {
|
|
49
|
+
options.stdin = {
|
|
50
|
+
contents: entry.source,
|
|
51
|
+
loader: "js",
|
|
52
|
+
resolveDir: entry.resolveDir,
|
|
53
|
+
sourcefile: "jx-entry.js",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const result = await esbuild.build({ ...options, write: false });
|
|
57
|
+
return result.outputFiles[0]!.text;
|
|
58
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between `bundler.ts` and the backend that does the bundling.
|
|
3
|
+
*
|
|
4
|
+
* The backend is chosen at **resolution time** by the `#bundler-backend` import's conditions
|
|
5
|
+
* (Node's conditional exports): `bundler-backend.bun.ts` under Bun, `bundler-backend.node.ts`
|
|
6
|
+
* (esbuild) everywhere else. A build of this package for Node therefore contains no Bun code at
|
|
7
|
+
* all, which is what lets the desktop backend run on Electron's Node (specs/desktop.md §7.1). Under
|
|
8
|
+
* Bun, `JX_BUNDLER=esbuild` still forces the esbuild backend for parity checks.
|
|
9
|
+
*
|
|
10
|
+
* `bundler.ts` owns everything both backends agree on (the defines, the minify default, the hook
|
|
11
|
+
* plugin); a backend owns only the call into its bundler and the spelling of its target.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Resolution/runtime targets: client sidecars use "browser"; the site worker uses the rest. */
|
|
15
|
+
export type BundleTargetName = "browser" | "node" | "bun" | "workerd";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The part of a plugin builder the hooks use, typed so that both Bun's `PluginBuilder` and
|
|
19
|
+
* esbuild's `PluginBuild` are assignable to it: a plugin written against it is a plugin for either
|
|
20
|
+
* backend.
|
|
21
|
+
*/
|
|
22
|
+
export interface PluginBuild {
|
|
23
|
+
onResolve: (
|
|
24
|
+
options: { filter: RegExp },
|
|
25
|
+
callback: (args: {
|
|
26
|
+
path: string;
|
|
27
|
+
importer: string;
|
|
28
|
+
}) => { path: string; external: true } | undefined,
|
|
29
|
+
) => void;
|
|
30
|
+
/** An `undefined` result hands the file back to the backend's own loader. */
|
|
31
|
+
onLoad: (options: { filter: RegExp }, callback: (args: { path: string }) => undefined) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A plugin in the one shape both `Bun.build` and `esbuild.build` accept. */
|
|
35
|
+
export interface BundlePlugin {
|
|
36
|
+
name: string;
|
|
37
|
+
setup: (build: PluginBuild) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One bundle, with every option already decided by `bundler.ts`. */
|
|
41
|
+
export interface BackendBuild {
|
|
42
|
+
/** A file to start from, or source text resolved as though it sat in `resolveDir`. */
|
|
43
|
+
entry: { path: string } | { source: string; resolveDir: string };
|
|
44
|
+
target: BundleTargetName;
|
|
45
|
+
external: string[];
|
|
46
|
+
conditions?: string[];
|
|
47
|
+
define?: Record<string, string>;
|
|
48
|
+
minify: boolean;
|
|
49
|
+
plugins: BundlePlugin[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Basename prefix of the throwaway entry the Bun backend writes for a source entry. */
|
|
53
|
+
export const TRANSIENT_ENTRY_PREFIX = ".jx-entry-";
|
package/src/site/bundler.ts
CHANGED
|
@@ -2,20 +2,30 @@
|
|
|
2
2
|
* Bundler.js — timing-aware module bundling for compiled sites
|
|
3
3
|
*
|
|
4
4
|
* One backend abstraction serving every bundling need of the build (spec.md §12, compiler.md):
|
|
5
|
-
* client-timing `$src` sidecars today, the server worker in a follow-up.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* client-timing `$src` sidecars today, the server worker in a follow-up. The backend is chosen at
|
|
6
|
+
* resolution time by the `#bundler-backend` import (`bundler-backend.ts`): `Bun.build` under Bun,
|
|
7
|
+
* esbuild everywhere else, so a Node build of this module carries no Bun code. Options are kept
|
|
8
|
+
* minimal and identical across backends so output stays as stable as possible; set
|
|
9
|
+
* `JX_BUNDLER=esbuild` to force esbuild under Bun (e.g. for parity checks).
|
|
9
10
|
*
|
|
10
11
|
* `timing: "compiler"` code is never bundled — it executes in the build host via the extension
|
|
11
12
|
* registry's `importImplementation` path.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { rmSync, writeFileSync } from "node:fs";
|
|
15
|
-
import { createRequire } from "node:module";
|
|
16
16
|
import { tmpdir } from "node:os";
|
|
17
17
|
import { basename, join, resolve } from "node:path";
|
|
18
18
|
import { isNpmSpecifier, NPM_SPECIFIER_PREFIX } from "@jxsuite/schema/asset-paths";
|
|
19
|
+
import * as backend from "#bundler-backend";
|
|
20
|
+
import type {
|
|
21
|
+
BackendBuild,
|
|
22
|
+
BundlePlugin,
|
|
23
|
+
BundleTargetName,
|
|
24
|
+
PluginBuild,
|
|
25
|
+
} from "./bundler-backend.ts";
|
|
26
|
+
import { TRANSIENT_ENTRY_PREFIX } from "./bundler-backend.ts";
|
|
27
|
+
|
|
28
|
+
export type { BundleTargetName } from "./bundler-backend.ts";
|
|
19
29
|
|
|
20
30
|
/** Import specifiers left external in client bundles — provided by the page importmap. */
|
|
21
31
|
export const CLIENT_EXTERNALS = ["@vue/reactivity", "lit-html"];
|
|
@@ -29,11 +39,11 @@ export function isBundleableSrc(specifier: string): boolean {
|
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
|
-
* True when the
|
|
33
|
-
*
|
|
42
|
+
* True when the esbuild backend is the one bundling: always outside Bun, or when
|
|
43
|
+
* `JX_BUNDLER=esbuild` forces it for parity checks.
|
|
34
44
|
*/
|
|
35
45
|
export function useNodeFallback(): boolean {
|
|
36
|
-
return
|
|
46
|
+
return backend.backendName() === "esbuild";
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
@@ -47,15 +57,19 @@ export function resolveSidecarEntry(
|
|
|
47
57
|
projectRoot: string,
|
|
48
58
|
): string {
|
|
49
59
|
if (isNpmSpecifier(specifier)) {
|
|
50
|
-
|
|
51
|
-
if (useNodeFallback()) {
|
|
52
|
-
return createRequire(join(projectRoot, "package.json")).resolve(bare);
|
|
53
|
-
}
|
|
54
|
-
return Bun.resolveSync(bare, projectRoot);
|
|
60
|
+
return backend.resolveBare(specifier.slice(NPM_SPECIFIER_PREFIX.length), projectRoot);
|
|
55
61
|
}
|
|
56
62
|
return resolve(docDir, specifier);
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a bare specifier as an import from a module in `fromDir`, through the active backend's
|
|
67
|
+
* resolver. Throws when it does not resolve.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveBareSpecifier(specifier: string, fromDir: string): string {
|
|
70
|
+
return backend.resolveBare(specifier, fromDir);
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
export interface BundleRequest {
|
|
60
74
|
/** Absolute path of the module to bundle. */
|
|
61
75
|
entryPath: string;
|
|
@@ -63,9 +77,6 @@ export interface BundleRequest {
|
|
|
63
77
|
outfile: string;
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
/** Resolution/runtime targets: client sidecars use "browser"; the site worker uses the rest. */
|
|
67
|
-
export type BundleTargetName = "browser" | "node" | "bun" | "workerd";
|
|
68
|
-
|
|
69
80
|
/**
|
|
70
81
|
* A resolution hook, in the one shape both backends accept.
|
|
71
82
|
*
|
|
@@ -110,26 +121,6 @@ export interface BundleOptions {
|
|
|
110
121
|
onFileLoaded?: (path: string) => void;
|
|
111
122
|
}
|
|
112
123
|
|
|
113
|
-
/**
|
|
114
|
-
* The part of a plugin builder the hooks use, typed so that both Bun's `PluginBuilder` and
|
|
115
|
-
* esbuild's `PluginBuild` are assignable to it: a plugin written against it is a plugin for either
|
|
116
|
-
* backend.
|
|
117
|
-
*/
|
|
118
|
-
interface PluginBuild {
|
|
119
|
-
onResolve: (
|
|
120
|
-
options: { filter: RegExp },
|
|
121
|
-
callback: (args: { path: string; importer: string }) => ReturnType<BundleResolver["resolve"]>,
|
|
122
|
-
) => void;
|
|
123
|
-
/** An `undefined` result hands the file back to the backend's own loader. */
|
|
124
|
-
onLoad: (options: { filter: RegExp }, callback: (args: { path: string }) => undefined) => void;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** A plugin in the one shape both `Bun.build` and `esbuild.build` accept. */
|
|
128
|
-
interface BundlePlugin {
|
|
129
|
-
name: string;
|
|
130
|
-
setup: (build: PluginBuild) => void;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
124
|
/** Wrap the hooks as the one-plugin array both backends take. */
|
|
134
125
|
function hookPlugins(opts: BundleOptions): BundlePlugin[] {
|
|
135
126
|
const { onFileLoaded, resolver } = opts;
|
|
@@ -213,17 +204,15 @@ function sharedResolution(opts: BundleOptions): SharedResolution {
|
|
|
213
204
|
return shared;
|
|
214
205
|
}
|
|
215
206
|
|
|
216
|
-
/**
|
|
217
|
-
function
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
return target;
|
|
207
|
+
/** Every option {@link BundleOptions} decides, in the shape a backend takes. */
|
|
208
|
+
function backendOptions(opts: BundleOptions): Omit<BackendBuild, "entry"> {
|
|
209
|
+
return {
|
|
210
|
+
...sharedResolution(opts),
|
|
211
|
+
external: opts.external ?? [],
|
|
212
|
+
minify: shouldMinify(opts),
|
|
213
|
+
plugins: hookPlugins(opts),
|
|
214
|
+
target: opts.target,
|
|
215
|
+
};
|
|
227
216
|
}
|
|
228
217
|
|
|
229
218
|
/**
|
|
@@ -231,46 +220,19 @@ function bunTarget(target: BundleTargetName): "browser" | "node" | "bun" {
|
|
|
231
220
|
* diagnostics on failure.
|
|
232
221
|
*/
|
|
233
222
|
export async function bundleEntry(request: BundleRequest, opts: BundleOptions): Promise<void> {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (useNodeFallback()) {
|
|
238
|
-
// Dynamic import keeps esbuild (a native-binary dependency) out of Bun-run builds entirely.
|
|
239
|
-
const esbuild = await import("esbuild");
|
|
240
|
-
await esbuild.build({
|
|
241
|
-
bundle: true,
|
|
242
|
-
...sharedResolution(opts),
|
|
243
|
-
entryPoints: [request.entryPath],
|
|
244
|
-
external,
|
|
245
|
-
format: "esm",
|
|
246
|
-
logLevel: "silent",
|
|
247
|
-
minify: shouldMinify(opts),
|
|
248
|
-
outfile: request.outfile,
|
|
249
|
-
platform: esbuildPlatform(opts.target),
|
|
250
|
-
plugins,
|
|
251
|
-
});
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const result = await Bun.build({
|
|
256
|
-
...sharedResolution(opts),
|
|
257
|
-
entrypoints: [request.entryPath],
|
|
258
|
-
external,
|
|
259
|
-
format: "esm",
|
|
260
|
-
minify: shouldMinify(opts),
|
|
261
|
-
plugins,
|
|
262
|
-
target: bunTarget(opts.target),
|
|
263
|
-
throw: false,
|
|
264
|
-
});
|
|
265
|
-
if (!result.success || result.outputs.length === 0) {
|
|
266
|
-
const logs = result.logs.map(String).join("\n");
|
|
267
|
-
throw new Error(`Bun.build failed for ${request.entryPath}:\n${logs}`);
|
|
268
|
-
}
|
|
269
|
-
writeFileSync(request.outfile, await result.outputs[0]!.text(), "utf8");
|
|
223
|
+
const code = await backend.build({ entry: { path: request.entryPath }, ...backendOptions(opts) });
|
|
224
|
+
writeFileSync(request.outfile, code, "utf8");
|
|
270
225
|
}
|
|
271
226
|
|
|
272
|
-
/**
|
|
273
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Bundle one entry module and return the code rather than writing it.
|
|
229
|
+
*
|
|
230
|
+
* For a host serving modules on demand (the dev server's and the desktop project server's bare
|
|
231
|
+
* specifier routes, the live preview's runtime): what it needs is the text for a response body.
|
|
232
|
+
*/
|
|
233
|
+
export async function bundleToString(entryPath: string, opts: BundleOptions): Promise<string> {
|
|
234
|
+
return backend.build({ entry: { path: entryPath }, ...backendOptions(opts) });
|
|
235
|
+
}
|
|
274
236
|
|
|
275
237
|
/**
|
|
276
238
|
* True for the throwaway entry a {@link bundleSource} call is bundling, under either backend.
|
|
@@ -328,31 +290,8 @@ export async function bundleSource(
|
|
|
328
290
|
{ resolveDir, outfile }: { resolveDir: string; outfile: string },
|
|
329
291
|
opts: BundleOptions,
|
|
330
292
|
): Promise<void> {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
await esbuild.build({
|
|
334
|
-
bundle: true,
|
|
335
|
-
...sharedResolution(opts),
|
|
336
|
-
external: opts.external ?? [],
|
|
337
|
-
format: "esm",
|
|
338
|
-
logLevel: "silent",
|
|
339
|
-
minify: shouldMinify(opts),
|
|
340
|
-
outfile,
|
|
341
|
-
platform: esbuildPlatform(opts.target),
|
|
342
|
-
stdin: { contents: source, loader: "js", resolveDir, sourcefile: "jx-entry.js" },
|
|
343
|
-
});
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Bun.build has no stdin input — write a transient entry in `resolveDir` so resolution matches
|
|
348
|
-
// The stdin/resolveDir behavior, and always remove it.
|
|
349
|
-
const entryPath = join(resolveDir, `${TRANSIENT_ENTRY_PREFIX}${process.pid}.mjs`);
|
|
350
|
-
writeFileSync(entryPath, source, "utf8");
|
|
351
|
-
try {
|
|
352
|
-
await bundleEntry({ entryPath, outfile }, opts);
|
|
353
|
-
} finally {
|
|
354
|
-
rmSync(entryPath, { force: true });
|
|
355
|
-
}
|
|
293
|
+
const code = await backend.build({ entry: { resolveDir, source }, ...backendOptions(opts) });
|
|
294
|
+
writeFileSync(outfile, code, "utf8");
|
|
356
295
|
}
|
|
357
296
|
|
|
358
297
|
/**
|
|
@@ -16,14 +16,13 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
19
|
-
import { createRequire } from "node:module";
|
|
20
19
|
import { dirname, join, relative, resolve } from "node:path";
|
|
21
20
|
import type { BundleResolver } from "./bundler.ts";
|
|
22
21
|
import {
|
|
23
22
|
bundleSource,
|
|
24
23
|
isTransientEntry,
|
|
24
|
+
resolveBareSpecifier,
|
|
25
25
|
resolveThroughBundler,
|
|
26
|
-
useNodeFallback,
|
|
27
26
|
} from "./bundler.ts";
|
|
28
27
|
import { DEFAULT_LIT_HTML_SRC, DEFAULT_REACTIVITY_SRC } from "../shared.ts";
|
|
29
28
|
import { isJsonObject, isNonNullObject, isString } from "@jxsuite/schema/guards";
|
|
@@ -230,18 +229,14 @@ export function renderImportMap(imports: Record<string, string>): string {
|
|
|
230
229
|
/**
|
|
231
230
|
* True when a bare specifier resolves from `resolveDir` under either backend.
|
|
232
231
|
*
|
|
233
|
-
* The backend
|
|
234
|
-
*
|
|
235
|
-
*
|
|
232
|
+
* The backend's own resolver answers, not a second inline choice: resolution and bundling must
|
|
233
|
+
* agree about which one they are on, or the import map promises a module the bundle then fails to
|
|
234
|
+
* produce.
|
|
236
235
|
*/
|
|
237
236
|
function canResolve(specifier: string, resolveDir: string): boolean {
|
|
238
237
|
try {
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
createRequire(join(resolveDir, "x.js")).resolve(specifier);
|
|
242
|
-
} else {
|
|
243
|
-
Bun.resolveSync(specifier, resolveDir);
|
|
244
|
-
}
|
|
238
|
+
// The resolver throws when the specifier does not resolve, and returns its path when it does.
|
|
239
|
+
resolveBareSpecifier(specifier, resolveDir);
|
|
245
240
|
return true;
|
|
246
241
|
} catch {
|
|
247
242
|
return false;
|