mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
docs: re-apply many recent changes that somehow aren't present (#24719)
lots of recent changes aren't present, so this reapplies them
This commit is contained in:
@@ -37,6 +37,7 @@ await extractLinks("https://bun.com");
|
||||
|
||||
When scraping websites, you often want to convert relative URLs (like `/docs`) to absolute URLs. Here's how to handle URL resolution:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts extract-links.ts icon="/icons/typescript.svg"
|
||||
async function extractLinksFromURL(url: string) {
|
||||
const response = await fetch(url);
|
||||
@@ -47,13 +48,11 @@ async function extractLinksFromURL(url: string) {
|
||||
const href = el.getAttribute("href");
|
||||
if (href) {
|
||||
// Convert relative URLs to absolute // [!code ++]
|
||||
try {
|
||||
// [!code ++]
|
||||
try { // [!code ++]
|
||||
const absoluteURL = new URL(href, url).href; // [!code ++]
|
||||
links.add(absoluteURL); // [!code ++]
|
||||
} catch {
|
||||
// [!code ++]
|
||||
links.add(href);
|
||||
links.add(absoluteURL);
|
||||
} catch { // [!code ++]
|
||||
links.add(href); // [!code ++]
|
||||
} // [!code ++]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,6 +65,7 @@ First we use the [`.formData()`](https://developer.mozilla.org/en-US/docs/Web/AP
|
||||
|
||||
Finally, we write the `Blob` to disk using [`Bun.write()`](https://bun.com/docs/api/file-io#writing-files-bun-write).
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts index.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve({
|
||||
port: 4000,
|
||||
@@ -80,8 +81,7 @@ const server = Bun.serve({
|
||||
});
|
||||
|
||||
// parse formdata at /action // [!code ++]
|
||||
if (url.pathname === "/action") {
|
||||
// [!code ++]
|
||||
if (url.pathname === "/action") { // [!code ++]
|
||||
const formdata = await req.formData(); // [!code ++]
|
||||
const name = formdata.get("name"); // [!code ++]
|
||||
const profilePicture = formdata.get("profilePicture"); // [!code ++]
|
||||
|
||||
@@ -26,14 +26,14 @@ This will add the package to `peerDependencies` in `package.json`.
|
||||
|
||||
Running `bun install` will install peer dependencies by default, unless marked optional in `peerDependenciesMeta`.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```json package.json icon="file-json"
|
||||
{
|
||||
"peerDependencies": {
|
||||
"@types/bun": "^1.3.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/bun": {
|
||||
// [!code ++]
|
||||
"@types/bun": { // [!code ++]
|
||||
"optional": true // [!code ++]
|
||||
} // [!code ++]
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ jobs:
|
||||
steps:
|
||||
# ...
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2 // [!code ++]
|
||||
- uses: oven-sh/setup-bun@v2 # [!code ++]
|
||||
|
||||
# run any `bun` or `bunx` command
|
||||
- run: bun install // [!code ++]
|
||||
- run: bun index.ts // [!code ++]
|
||||
- run: bun run build // [!code ++]
|
||||
- run: bun install # [!code ++]
|
||||
- run: bun index.ts # [!code ++]
|
||||
- run: bun run build # [!code ++]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
steps:
|
||||
# ...
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with: // [!code ++]
|
||||
bun-version: 1.2.0 # or "latest", "canary", <sha> // [!code ++]
|
||||
with: # [!code ++]
|
||||
bun-version: 1.2.0 # or "latest", "canary", <sha> # [!code ++]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -27,9 +27,9 @@ if (process.env.NODE_ENV === "production") {
|
||||
|
||||
Before the code reaches the JavaScript engine, Bun replaces `process.env.NODE_ENV` with `"production"`.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
if ("production" === "production") {
|
||||
// [!code ++]
|
||||
if ("production" === "production") { // [!code ++]
|
||||
console.log("Production mode");
|
||||
} else {
|
||||
console.log("Development mode");
|
||||
@@ -42,9 +42,9 @@ It doesn't stop there. Bun's optimizing transpiler is smart enough to do some ba
|
||||
|
||||
Since `"production" === "production"` is always `true`, Bun replaces the entire expression with the `true` value.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
if (true) {
|
||||
// [!code ++]
|
||||
if (true) { // [!code ++]
|
||||
console.log("Production mode");
|
||||
} else {
|
||||
console.log("Development mode");
|
||||
|
||||
143
docs/guides/test/concurrent-test-glob.mdx
Normal file
143
docs/guides/test/concurrent-test-glob.mdx
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: Selectively run tests concurrently with glob patterns
|
||||
sidebarTitle: Concurrent test glob
|
||||
mode: center
|
||||
---
|
||||
|
||||
This guide demonstrates how to use the `concurrentTestGlob` option to selectively run tests concurrently based on file naming patterns.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```sh title="Project Structure" icon="folder-tree"
|
||||
my-project/
|
||||
├── bunfig.toml
|
||||
├── tests/
|
||||
│ ├── unit/
|
||||
│ │ ├── math.test.ts # Sequential
|
||||
│ │ └── utils.test.ts # Sequential
|
||||
│ └── integration/
|
||||
│ ├── concurrent-api.test.ts # Concurrent
|
||||
│ └── concurrent-database.test.ts # Concurrent
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure your `bunfig.toml` to run test files with "concurrent-" prefix concurrently:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
# Run all test files with "concurrent-" prefix concurrently
|
||||
concurrentTestGlob = "**/concurrent-*.test.ts"
|
||||
```
|
||||
|
||||
## Test Files
|
||||
|
||||
### Unit Test (Sequential)
|
||||
|
||||
Sequential tests are good for tests that share state or have specific ordering requirements:
|
||||
|
||||
```ts title="tests/unit/math.test.ts" icon="/icons/typescript.svg"
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
// These tests run sequentially by default
|
||||
let sharedState = 0;
|
||||
|
||||
test("addition", () => {
|
||||
sharedState = 5 + 3;
|
||||
expect(sharedState).toBe(8);
|
||||
});
|
||||
|
||||
test("uses previous state", () => {
|
||||
// This test depends on the previous test's state
|
||||
expect(sharedState).toBe(8);
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Test (Concurrent)
|
||||
|
||||
Tests in files matching the glob pattern automatically run concurrently:
|
||||
|
||||
```ts title="tests/integration/concurrent-api.test.ts" icon="/icons/typescript.svg"
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
// These tests automatically run concurrently due to filename matching the glob pattern.
|
||||
// Using test() is equivalent to test.concurrent() when the file matches concurrentTestGlob.
|
||||
// Each test is independent and can run in parallel.
|
||||
|
||||
test("fetch user data", async () => {
|
||||
const response = await fetch("/api/user/1");
|
||||
expect(response.ok).toBe(true);
|
||||
});
|
||||
|
||||
test("fetch posts", async () => {
|
||||
const response = await fetch("/api/posts");
|
||||
expect(response.ok).toBe(true);
|
||||
});
|
||||
|
||||
test("fetch comments", async () => {
|
||||
const response = await fetch("/api/comments");
|
||||
expect(response.ok).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
# Run all tests - concurrent-*.test.ts files will run concurrently
|
||||
bun test
|
||||
|
||||
# Override: Force ALL tests to run concurrently
|
||||
# Note: This overrides bunfig.toml and runs all tests concurrently, regardless of glob
|
||||
bun test --concurrent
|
||||
|
||||
# Run only unit tests (sequential)
|
||||
bun test tests/unit
|
||||
|
||||
# Run only integration tests (concurrent due to glob pattern)
|
||||
bun test tests/integration
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Gradual Migration**: Migrate to concurrent tests file by file by renaming them
|
||||
2. **Clear Organization**: File naming convention indicates execution mode
|
||||
3. **Performance**: Integration tests run faster in parallel
|
||||
4. **Safety**: Unit tests remain sequential where needed
|
||||
5. **Flexibility**: Easy to change execution mode by renaming files
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
To migrate existing tests to concurrent execution:
|
||||
|
||||
1. **Start with independent integration tests** - These typically don't share state
|
||||
2. **Rename files to match the glob pattern**: `mv api.test.ts concurrent-api.test.ts`
|
||||
3. **Verify tests still pass** - Run `bun test` to ensure no race conditions
|
||||
4. **Monitor for shared state issues** - Watch for flaky tests or unexpected failures
|
||||
5. **Continue migrating stable tests incrementally** - Don't rush the migration
|
||||
|
||||
## Tips
|
||||
|
||||
- **Use descriptive prefixes**: `concurrent-`, `parallel-`, `async-`
|
||||
- **Keep related sequential tests together** in the same directory
|
||||
- **Document why certain tests must remain sequential** with comments
|
||||
- **Use `test.concurrent()` for fine-grained control** in sequential files
|
||||
(Note: In files matched by `concurrentTestGlob`, plain `test()` already runs concurrently)
|
||||
|
||||
## Multiple Patterns
|
||||
|
||||
You can specify multiple patterns for different test categories:
|
||||
|
||||
```toml title="bunfig.toml" icon="settings"
|
||||
[test]
|
||||
concurrentTestGlob = [
|
||||
"**/integration/*.test.ts",
|
||||
"**/e2e/*.test.ts",
|
||||
"**/concurrent-*.test.ts"
|
||||
]
|
||||
```
|
||||
|
||||
This configuration will run tests concurrently if they match any of these patterns:
|
||||
|
||||
- All tests in `integration/` directories
|
||||
- All tests in `e2e/` directories
|
||||
- All tests with `concurrent-` prefix anywhere in the project
|
||||
@@ -23,6 +23,7 @@ const spy = spyOn(leo, "sayHi");
|
||||
|
||||
Once the spy is created, it can be used to write `expect` assertions relating to method calls.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
import { test, expect, spyOn } from "bun:test";
|
||||
|
||||
@@ -35,8 +36,7 @@ const leo = {
|
||||
|
||||
const spy = spyOn(leo, "sayHi");
|
||||
|
||||
test("turtles", () => {
|
||||
// [!code ++]
|
||||
test("turtles", () => { // [!code ++]
|
||||
expect(spy).toHaveBeenCalledTimes(0); // [!code ++]
|
||||
leo.sayHi("pizza"); // [!code ++]
|
||||
expect(spy).toHaveBeenCalledTimes(1); // [!code ++]
|
||||
|
||||
@@ -9,7 +9,7 @@ When building a WebSocket server, it's typically necessary to store some identif
|
||||
With [Bun.serve()](https://bun.com/docs/api/websockets contextual-data), this "contextual data" is set when the connection is initially upgraded by passing a `data` parameter in the `server.upgrade()` call.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
Bun.serve<{ socketId: number }>({
|
||||
Bun.serve({
|
||||
fetch(req, server) {
|
||||
const success = server.upgrade(req, {
|
||||
data: {
|
||||
@@ -22,6 +22,9 @@ Bun.serve<{ socketId: number }>({
|
||||
// ...
|
||||
},
|
||||
websocket: {
|
||||
// TypeScript: specify the type of ws.data like this
|
||||
data: {} as { socketId: number },
|
||||
|
||||
// define websocket handlers
|
||||
async message(ws, message) {
|
||||
// the contextual data is available as the `data` property
|
||||
@@ -43,8 +46,7 @@ type WebSocketData = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
// TypeScript: specify the type of `data`
|
||||
Bun.serve<WebSocketData>({
|
||||
Bun.serve({
|
||||
async fetch(req, server) {
|
||||
// use a library to parse cookies
|
||||
const cookies = parseCookies(req.headers.get("Cookie"));
|
||||
@@ -62,6 +64,9 @@ Bun.serve<WebSocketData>({
|
||||
if (upgraded) return undefined;
|
||||
},
|
||||
websocket: {
|
||||
// TypeScript: specify the type of ws.data like this
|
||||
data: {} as WebSocketData,
|
||||
|
||||
async message(ws, message) {
|
||||
// save the message to a database
|
||||
await saveMessageToDatabase({
|
||||
|
||||
@@ -9,7 +9,7 @@ Bun's server-side `WebSocket` API provides a native pub-sub API. Sockets can be
|
||||
This code snippet implements a simple single-channel chat server.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve<{ username: string }>({
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const cookies = req.headers.get("cookie");
|
||||
const username = getUsernameFromCookies(cookies);
|
||||
@@ -19,6 +19,9 @@ const server = Bun.serve<{ username: string }>({
|
||||
return new Response("Hello world");
|
||||
},
|
||||
websocket: {
|
||||
// TypeScript: specify the type of ws.data like this
|
||||
data: {} as { username: string },
|
||||
|
||||
open(ws) {
|
||||
const msg = `${ws.data.username} has entered the chat`;
|
||||
ws.subscribe("the-group-chat");
|
||||
|
||||
@@ -9,7 +9,7 @@ Start a simple WebSocket server using [`Bun.serve`](https://bun.com/docs/api/htt
|
||||
Inside `fetch`, we attempt to upgrade incoming `ws:` or `wss:` requests to WebSocket connections.
|
||||
|
||||
```ts server.ts icon="/icons/typescript.svg"
|
||||
const server = Bun.serve<{ authToken: string }>({
|
||||
const server = Bun.serve({
|
||||
fetch(req, server) {
|
||||
const success = server.upgrade(req);
|
||||
if (success) {
|
||||
@@ -22,6 +22,9 @@ const server = Bun.serve<{ authToken: string }>({
|
||||
return new Response("Hello world!");
|
||||
},
|
||||
websocket: {
|
||||
// TypeScript: specify the type of ws.data like this
|
||||
data: {} as { authToken: string },
|
||||
|
||||
// this is called when a message is received
|
||||
async message(ws, message) {
|
||||
console.log(`Received ${message}`);
|
||||
|
||||
Reference in New Issue
Block a user