mirror of
https://github.com/oven-sh/bun
synced 2026-02-20 07:42:30 +00:00
Compare commits
4 Commits
ali/inspec
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aa7964f9a | ||
|
|
e99608620c | ||
|
|
9fbe6a5826 | ||
|
|
c0d97ebd88 |
@@ -1184,7 +1184,8 @@ Currently, the `--compile` flag can only accept a single entrypoint at a time an
|
||||
|
||||
- `--outdir` — use `outfile` instead (except when using with `--splitting`).
|
||||
- `--public-path`
|
||||
- `--target=node` or `--target=browser`
|
||||
- `--target=node`
|
||||
- `--target=browser` (without HTML entrypoints — see [Standalone HTML](/bundler/standalone-html) for `--compile --target=browser` with `.html` files)
|
||||
- `--no-bundle` - we always bundle everything into the executable.
|
||||
|
||||
---
|
||||
|
||||
@@ -481,6 +481,16 @@ All paths are resolved relative to your HTML file, making it easy to organize yo
|
||||
|
||||
This is a small wrapper around Bun's support for HTML imports in JavaScript.
|
||||
|
||||
## Standalone HTML
|
||||
|
||||
You can bundle your entire frontend into a **single self-contained `.html` file** with no external dependencies using `--compile --target=browser`. All JavaScript, CSS, and images are inlined directly into the HTML.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Learn more in the [Standalone HTML docs](/bundler/standalone-html).
|
||||
|
||||
## Adding a backend to your frontend
|
||||
|
||||
To add a backend to your frontend, you can use the "routes" option in `Bun.serve`.
|
||||
|
||||
314
docs/bundler/standalone-html.mdx
Normal file
314
docs/bundler/standalone-html.mdx
Normal file
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: Standalone HTML
|
||||
description: Bundle a single-page app into a single self-contained .html file with no external dependencies
|
||||
---
|
||||
|
||||
Bun can bundle your entire frontend into a **single `.html` file** with zero external dependencies. JavaScript, TypeScript, JSX, CSS, images, fonts, videos, WASM — everything gets inlined into one file.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
The output is a completely self-contained HTML document. No relative paths. No external files. No server required. Just one `.html` file that works anywhere a browser can open it.
|
||||
|
||||
## One file. Upload anywhere. It just works.
|
||||
|
||||
The output is a single `.html` file you can put anywhere:
|
||||
|
||||
- **Upload it to S3** or any static file host — no directory structure to maintain, just one file
|
||||
- **Double-click it from your desktop** — it opens in the browser and works offline, no localhost server needed
|
||||
- **Embed it in your webview** — No need to deal with relative files
|
||||
- **Insert it in an `<iframe>`** — embed interactive content in another page with a single file URL
|
||||
- **Serve it from anywhere** — any HTTP server, CDN, or file share. One file, zero configuration.
|
||||
|
||||
There's nothing to install, no `node_modules` to deploy, no build artifacts to coordinate, no relative paths to think about. The entire app — framework code, stylesheets, images, everything — lives in that one file.
|
||||
|
||||
## Truly one file
|
||||
|
||||
Normally, distributing a web page means managing a folder of assets — the HTML, the JavaScript bundles, the CSS files, the images. Move the HTML without the rest and everything breaks. Browsers have tried to solve this before: Safari's `.webarchive` and `.mhtml` are supposed to save a page as a single file, but in practice they unpack into a folder of loose files on your computer — defeating the purpose.
|
||||
|
||||
Standalone HTML is different. The output is a plain `.html` file. Not an archive. Not a folder. One file, with everything inside it. Every image, every font, every line of CSS and JavaScript is embedded directly in the HTML using standard `<style>` tags, `<script>` tags, and `data:` URIs. Any browser can open it. Any server can host it. Any file host can store it.
|
||||
|
||||
This makes it practical to distribute web pages the same way you'd distribute a PDF — as a single file you can move, copy, upload, or share without worrying about broken paths or missing assets.
|
||||
|
||||
## Quick start
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function App() {
|
||||
return <h1>Hello from a single HTML file!</h1>;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
```css styles.css icon="file-code"
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Open `dist/index.html` — the React app works with no server.
|
||||
|
||||
## Everything gets inlined
|
||||
|
||||
Bun inlines every local asset it finds in your HTML. If it has a relative path, it gets embedded into the output file. This isn't limited to images and stylesheets — it works with any file type.
|
||||
|
||||
### What gets inlined
|
||||
|
||||
| In your source | In the output |
|
||||
| ------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| `<script src="./app.tsx">` | `<script type="module">...bundled code...</script>` |
|
||||
| `<link rel="stylesheet" href="./styles.css">` | `<style>...bundled CSS...</style>` |
|
||||
| `<img src="./logo.png">` | `<img src="data:image/png;base64,...">` |
|
||||
| `<img src="./icon.svg">` | `<img src="data:image/svg+xml;base64,...">` |
|
||||
| `<video src="./demo.mp4">` | `<video src="data:video/mp4;base64,...">` |
|
||||
| `<audio src="./click.wav">` | `<audio src="data:audio/wav;base64,...">` |
|
||||
| `<source src="./clip.webm">` | `<source src="data:video/webm;base64,...">` |
|
||||
| `<video poster="./thumb.jpg">` | `<video poster="data:image/jpeg;base64,...">` |
|
||||
| `<link rel="icon" href="./favicon.ico">` | `<link rel="icon" href="data:image/x-icon;base64,...">` |
|
||||
| `<link rel="manifest" href="./app.webmanifest">` | `<link rel="manifest" href="data:application/manifest+json;base64,...">` |
|
||||
| CSS `url("./bg.png")` | CSS `url(data:image/png;base64,...)` |
|
||||
| CSS `@import "./reset.css"` | Flattened into the `<style>` tag |
|
||||
| CSS `url("./font.woff2")` | CSS `url(data:font/woff2;base64,...)` |
|
||||
| JS `import "./styles.css"` | Merged into the `<style>` tag |
|
||||
|
||||
Images, fonts, WASM binaries, videos, audio files, SVGs — any file referenced by a relative path gets base64-encoded into a `data:` URI and embedded directly in the HTML. The MIME type is automatically detected from the file extension.
|
||||
|
||||
External URLs (like CDN links or absolute URLs) are left untouched.
|
||||
|
||||
## Using with React
|
||||
|
||||
React apps work out of the box. Bun handles JSX transpilation and npm package resolution automatically.
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install react react-dom
|
||||
```
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>My App</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React, { useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Counter } from "./components/Counter.tsx";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Single-file React App</h1>
|
||||
<Counter />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
```tsx components/Counter.tsx icon="/icons/typescript.svg"
|
||||
import React, { useState } from "react";
|
||||
|
||||
export function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
All of React, your components, and your CSS are bundled into `dist/index.html`. Upload that one file anywhere and it works.
|
||||
|
||||
## Using with Tailwind CSS
|
||||
|
||||
Install the plugin and reference Tailwind in your HTML or CSS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun install --dev bun-plugin-tailwind
|
||||
```
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```html index.html icon="file-code"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="tailwindcss" />
|
||||
</head>
|
||||
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
|
||||
<div id="root"></div>
|
||||
<script src="./app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx app.tsx icon="/icons/typescript.svg"
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md">
|
||||
<h1 className="text-2xl font-bold text-gray-800">Hello Tailwind</h1>
|
||||
<p className="text-gray-600 mt-2">This is a single HTML file.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
Build with the plugin using the JavaScript API:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist",
|
||||
plugins: [require("bun-plugin-tailwind")],
|
||||
});
|
||||
```
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun run build.ts
|
||||
```
|
||||
|
||||
The generated Tailwind CSS is inlined directly into the HTML file as a `<style>` tag.
|
||||
|
||||
## How it works
|
||||
|
||||
When you pass `--compile --target=browser` with an HTML entrypoint, Bun:
|
||||
|
||||
1. Parses the HTML and discovers all `<script>`, `<link>`, `<img>`, `<video>`, `<audio>`, `<source>`, and other asset references
|
||||
2. Bundles all JavaScript/TypeScript/JSX into a single module
|
||||
3. Bundles all CSS (including `@import` chains and CSS imported from JS) into a single stylesheet
|
||||
4. Converts every relative asset reference into a base64 `data:` URI
|
||||
5. Inlines the bundled JS as `<script type="module">` before `</body>`
|
||||
6. Inlines the bundled CSS as `<style>` in `<head>`
|
||||
7. Outputs a single `.html` file with no external dependencies
|
||||
|
||||
## Minification
|
||||
|
||||
Add `--minify` to minify the JavaScript and CSS:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser --minify ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
Or via the API:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist",
|
||||
minify: true,
|
||||
});
|
||||
```
|
||||
|
||||
## JavaScript API
|
||||
|
||||
You can use `Bun.build()` to produce standalone HTML programmatically:
|
||||
|
||||
```ts build.ts icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
outdir: "./dist", // optional — omit to get output as BuildArtifact
|
||||
minify: true,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error("Build failed:");
|
||||
for (const log of result.logs) {
|
||||
console.error(log);
|
||||
}
|
||||
} else {
|
||||
console.log("Built:", result.outputs[0].path);
|
||||
}
|
||||
```
|
||||
|
||||
When `outdir` is omitted, the output is available as a `BuildArtifact` in `result.outputs`:
|
||||
|
||||
```ts icon="/icons/typescript.svg"
|
||||
const result = await Bun.build({
|
||||
entrypoints: ["./index.html"],
|
||||
compile: true,
|
||||
target: "browser",
|
||||
});
|
||||
|
||||
const html = await result.outputs[0].text();
|
||||
await Bun.write("output.html", html);
|
||||
```
|
||||
|
||||
## Multiple HTML files
|
||||
|
||||
You can pass multiple HTML files as entrypoints. Each produces its own standalone HTML file:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
bun build --compile --target=browser ./index.html ./about.html --outdir=dist
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
Use `--env` to inline environment variables into the bundled JavaScript:
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
API_URL=https://api.example.com bun build --compile --target=browser --env=inline ./index.html --outdir=dist
|
||||
```
|
||||
|
||||
References to `process.env.API_URL` in your JavaScript are replaced with the literal value at build time.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Code splitting** is not supported — `--splitting` cannot be used with `--compile --target=browser`
|
||||
- **Large assets** increase file size since they're base64-encoded (33% overhead vs the raw binary)
|
||||
- **External URLs** (CDN links, absolute URLs) are left as-is — only relative paths are inlined
|
||||
@@ -234,7 +234,7 @@
|
||||
{
|
||||
"group": "Asset Processing",
|
||||
"icon": "image",
|
||||
"pages": ["/bundler/html-static", "/bundler/css", "/bundler/loaders"]
|
||||
"pages": ["/bundler/html-static", "/bundler/standalone-html", "/bundler/css", "/bundler/loaders"]
|
||||
},
|
||||
{
|
||||
"group": "Single File Executable",
|
||||
|
||||
@@ -4244,6 +4244,132 @@ pub const Resolver = struct {
|
||||
// todo deinit these parent configs somehow?
|
||||
}
|
||||
info.tsconfig_json = merged_config;
|
||||
|
||||
// Handle "references" - load each referenced tsconfig and merge
|
||||
// their paths into this config. This supports the common pattern
|
||||
// where a root tsconfig.json uses "references" to delegate to
|
||||
// sub-configs (e.g. tsconfig.app.json, tsconfig.node.json).
|
||||
if (merged_config.references.len > 0) {
|
||||
const ts_dir_name = Dirname.dirname(merged_config.abs_path);
|
||||
for (merged_config.references) |ref_path| {
|
||||
// Per the TypeScript spec, if "path" points to a directory,
|
||||
// look for tsconfig.json inside it. If it points to a file,
|
||||
// use it directly.
|
||||
const abs_ref_path = brk2: {
|
||||
if (strings.endsWithComptime(ref_path, ".json")) {
|
||||
break :brk2 ResolvePath.joinAbsStringBuf(
|
||||
ts_dir_name,
|
||||
bufs(.tsconfig_path_abs),
|
||||
&[_]string{ ts_dir_name, ref_path },
|
||||
.auto,
|
||||
);
|
||||
} else {
|
||||
break :brk2 ResolvePath.joinAbsStringBuf(
|
||||
ts_dir_name,
|
||||
bufs(.tsconfig_path_abs),
|
||||
&[_]string{ ts_dir_name, ref_path, "tsconfig.json" },
|
||||
.auto,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const ref_config_maybe = r.parseTSConfig(abs_ref_path, bun.invalid_fd) catch |err| brk2: {
|
||||
r.log.addDebugFmt(null, logger.Loc.Empty, r.allocator, "{s} loading tsconfig.json reference {f}", .{
|
||||
@errorName(err),
|
||||
bun.fmt.QuotedFormatter{
|
||||
.text = abs_ref_path,
|
||||
},
|
||||
}) catch {};
|
||||
break :brk2 null;
|
||||
};
|
||||
if (ref_config_maybe) |ref_config| {
|
||||
// Also resolve extends chains for referenced configs
|
||||
var ref_parent_configs = try bun.BoundedArray(*TSConfigJSON, 64).init(0);
|
||||
try ref_parent_configs.append(ref_config);
|
||||
var ref_current = ref_config;
|
||||
while (ref_current.extends.len > 0) {
|
||||
const ref_ts_dir = Dirname.dirname(ref_current.abs_path);
|
||||
const ref_extends_abs = ResolvePath.joinAbsStringBuf(ref_ts_dir, bufs(.tsconfig_path_abs), &[_]string{ ref_ts_dir, ref_current.extends }, .auto);
|
||||
const ref_parent_maybe = r.parseTSConfig(ref_extends_abs, bun.invalid_fd) catch break;
|
||||
if (ref_parent_maybe) |ref_parent| {
|
||||
try ref_parent_configs.append(ref_parent);
|
||||
ref_current = ref_parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Merge the referenced config's extends chain
|
||||
// (same fields as the root extends merge)
|
||||
var ref_merged = ref_parent_configs.pop().?;
|
||||
while (ref_parent_configs.pop()) |ref_parent| {
|
||||
ref_merged.emit_decorator_metadata = ref_merged.emit_decorator_metadata or ref_parent.emit_decorator_metadata;
|
||||
if (ref_parent.base_url.len > 0) {
|
||||
ref_merged.base_url = ref_parent.base_url;
|
||||
ref_merged.base_url_for_paths = ref_parent.base_url_for_paths;
|
||||
}
|
||||
ref_merged.jsx = ref_parent.mergeJSX(ref_merged.jsx);
|
||||
ref_merged.jsx_flags.setUnion(ref_parent.jsx_flags);
|
||||
|
||||
if (ref_parent.preserve_imports_not_used_as_values) |value| {
|
||||
ref_merged.preserve_imports_not_used_as_values = value;
|
||||
}
|
||||
|
||||
var ref_iter = ref_parent.paths.iterator();
|
||||
while (ref_iter.next()) |c| {
|
||||
ref_merged.paths.put(c.key_ptr.*, c.value_ptr.*) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge referenced config's paths into the root config.
|
||||
// Path values need to be made absolute using the referenced
|
||||
// config's base_url_for_paths, since the root config may
|
||||
// have a different (or no) base URL.
|
||||
const ref_base = if (ref_merged.hasBaseURL()) ref_merged.base_url else ref_merged.base_url_for_paths;
|
||||
var ref_iter = ref_merged.paths.iterator();
|
||||
while (ref_iter.next()) |c| {
|
||||
const original_values = c.value_ptr.*;
|
||||
if (ref_base.len > 0 and (merged_config.base_url_for_paths.len == 0 or
|
||||
!strings.eql(ref_base, merged_config.base_url_for_paths)))
|
||||
{
|
||||
// Resolve each path value to absolute so it works
|
||||
// regardless of the root config's baseUrl
|
||||
var abs_values = bun.default_allocator.alloc(string, original_values.len) catch unreachable;
|
||||
for (original_values, 0..) |orig_path, i| {
|
||||
if (!std.fs.path.isAbsolute(orig_path)) {
|
||||
const join_parts = [_]string{ ref_base, orig_path };
|
||||
abs_values[i] = r.fs.dirname_store.append(
|
||||
string,
|
||||
r.fs.absBuf(&join_parts, bufs(.tsconfig_base_url)),
|
||||
) catch unreachable;
|
||||
} else {
|
||||
abs_values[i] = orig_path;
|
||||
}
|
||||
}
|
||||
merged_config.paths.put(c.key_ptr.*, abs_values) catch unreachable;
|
||||
} else {
|
||||
merged_config.paths.put(c.key_ptr.*, original_values) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// If the root config has no base_url_for_paths but the referenced
|
||||
// config has paths, we need to ensure base_url_for_paths is set
|
||||
if (merged_config.base_url_for_paths.len == 0 and ref_merged.paths.count() > 0) {
|
||||
merged_config.base_url_for_paths = ref_merged.base_url_for_paths;
|
||||
}
|
||||
|
||||
// Merge other settings from referenced configs
|
||||
merged_config.jsx = ref_merged.mergeJSX(merged_config.jsx);
|
||||
merged_config.jsx_flags.setUnion(ref_merged.jsx_flags);
|
||||
merged_config.emit_decorator_metadata = merged_config.emit_decorator_metadata or ref_merged.emit_decorator_metadata;
|
||||
|
||||
if (ref_merged.preserve_imports_not_used_as_values) |value| {
|
||||
if (merged_config.preserve_imports_not_used_as_values == null) {
|
||||
merged_config.preserve_imports_not_used_as_values = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info.enclosing_tsconfig_json = info.tsconfig_json;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ pub const TSConfigJSON = struct {
|
||||
emit_decorator_metadata: bool = false,
|
||||
experimental_decorators: bool = false,
|
||||
|
||||
// TypeScript project references. Each entry is the "path" value from the
|
||||
// "references" array in tsconfig.json. These are relative paths to other
|
||||
// tsconfig files (or directories containing tsconfig.json).
|
||||
// See: https://www.typescriptlang.org/docs/handbook/project-references.html
|
||||
references: []const string = &.{},
|
||||
|
||||
pub fn hasBaseURL(tsconfig: *const TSConfigJSON) bool {
|
||||
return tsconfig.base_url.len > 0;
|
||||
}
|
||||
@@ -158,6 +164,26 @@ pub const TSConfigJSON = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse "references"
|
||||
if (json.asProperty("references")) |references_value| {
|
||||
if (!source.path.isNodeModule()) {
|
||||
if (references_value.expr.asArray()) |ref_array_iter| {
|
||||
var ref_array = ref_array_iter;
|
||||
var refs = std.array_list.Managed(string).init(allocator);
|
||||
while (ref_array.next()) |element| {
|
||||
if (element.asProperty("path")) |path_prop| {
|
||||
if (path_prop.expr.asString(allocator)) |ref_path| {
|
||||
refs.append(ref_path) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (refs.items.len > 0) {
|
||||
result.references = refs.toOwnedSlice() catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var has_base_url = false;
|
||||
|
||||
// Parse "compilerOptions"
|
||||
|
||||
95
test/regression/issue/20172.test.ts
Normal file
95
test/regression/issue/20172.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunRun, tempDirWithFiles } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
test("tsconfig references resolves paths from referenced configs", () => {
|
||||
const dir = tempDirWithFiles("tsconfig-refs", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: "./tsconfig.app.json" }, { path: "./tsconfig.node.json" }],
|
||||
}),
|
||||
"tsconfig.app.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"@/*": ["./src/*"],
|
||||
},
|
||||
},
|
||||
include: ["src/**/*"],
|
||||
}),
|
||||
"tsconfig.node.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"@server/*": ["./server/*"],
|
||||
},
|
||||
},
|
||||
include: ["server/**/*"],
|
||||
}),
|
||||
"server/index.ts": `import { foo } from '@server/lib/foo';
|
||||
console.log(foo);`,
|
||||
"server/lib/foo.ts": `export const foo = 123;`,
|
||||
"src/main.ts": `import { bar } from '@/lib/bar';
|
||||
console.log(bar);`,
|
||||
"src/lib/bar.ts": `export const bar = 456;`,
|
||||
});
|
||||
|
||||
// Test @server/* paths from tsconfig.node.json
|
||||
const serverResult = bunRun(join(dir, "server/index.ts"));
|
||||
expect(serverResult.stdout).toBe("123");
|
||||
|
||||
// Test @/* paths from tsconfig.app.json
|
||||
const appResult = bunRun(join(dir, "src/main.ts"));
|
||||
expect(appResult.stdout).toBe("456");
|
||||
});
|
||||
|
||||
test("tsconfig references resolves directory references", () => {
|
||||
const dir = tempDirWithFiles("tsconfig-dir-refs", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: "./app" }],
|
||||
}),
|
||||
"app/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: "..",
|
||||
paths: {
|
||||
"#utils/*": ["./src/utils/*"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"src/index.ts": `import { helper } from '#utils/helper';
|
||||
console.log(helper);`,
|
||||
"src/utils/helper.ts": `export const helper = "works";`,
|
||||
});
|
||||
|
||||
const result = bunRun(join(dir, "src/index.ts"));
|
||||
expect(result.stdout).toBe("works");
|
||||
});
|
||||
|
||||
test("tsconfig references with extends in referenced config", () => {
|
||||
const dir = tempDirWithFiles("tsconfig-refs-extends", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: "./tsconfig.app.json" }],
|
||||
}),
|
||||
"tsconfig.app.json": JSON.stringify({
|
||||
extends: "./tsconfig.base.json",
|
||||
compilerOptions: {
|
||||
paths: {
|
||||
"@app/*": ["./src/*"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"tsconfig.base.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
},
|
||||
}),
|
||||
"src/index.ts": `import { val } from '@app/lib/val';
|
||||
console.log(val);`,
|
||||
"src/lib/val.ts": `export const val = "extended";`,
|
||||
});
|
||||
|
||||
const result = bunRun(join(dir, "src/index.ts"));
|
||||
expect(result.stdout).toBe("extended");
|
||||
});
|
||||
Reference in New Issue
Block a user