Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat(minor): add bud.spa single page application facade #2632

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions examples/typescript/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"extends": "@roots/bud/config/tsconfig.json",
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"paths": {
"@src": ["./src"]
},
Expand Down
43 changes: 23 additions & 20 deletions sources/@repo/docs/content/learn/config/files/env.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,29 @@ API_URL = 'https://api.example.com'

## Configurable environment variables

| Variable | Description | Related argument |
| ------------------------- | ----------------------------------------------------------------------------------- | ----------------------- |
| `BUD_BROWSERSLIST_UPDATE` | Set to `false` in order to disable automatic updating of the browserslist database. | `--browserslist-update` |
| `BUD_CACHE` | Enable or disable bud.js caching | `--cache` |
| `BUD_DEVTOOL` | The devtool to use for builds | `--devtool` |
| `BUD_ESM` | Enable or disable bud.js ESM output (experimental) | `--esm` |
| `BUD_HASH` | Enable or disable output file hashing | `--hash` |
| `BUD_HTML` | Enable HTML templating; set to a string to specify the path to a template | `--html` |
| `BUD_HOT` | Enable or disable bud.js hot reloading | `--hot` |
| `BUD_IMMUTABLE` | Enable or disable automated updates of dependencies when using remote modules | `--immutable` |
| `BUD_LAZY` | Enable or disable lazy compilation of modules in development | `--lazy` |
| `BUD_MINIMIZE` | Enable or disable code minimization | `--minimize` |
| `BUD_PATH_BASE` | The path to the root directory of the bud.js project | `--basedir` |
| `BUD_PATH_STORAGE` | The path to the directory where bud.js stores its data and caches | `--storage` |
| `BUD_PATH_INPUT` | The path to the directory where bud.js looks for source files | `--input` |
| `BUD_PATH_OUTPUT` | The path to the directory where bud.js outputs build files | `--output` |
| `BUD_PATH_PUBLIC` | Directory to prepend to asset URLs (if assets are not served from web root) | `--publicPath` |
| `BUD_PROXY_URL` | The URL of the proxy server to use | `--proxy` |
| `BUD_RUNTIME` | Enable or disable generation of a runtime module | `--runtime` |
| `BUD_SPLIT_CHUNKS` | Enable or disable splitting of modules into discrete chunks (`vendor`, `app`, etc.) | `--split-chunks` |
| Variable | Description | Related argument | Default value |
| ------------------------- | --------------------------------------------------------------------------------------- | ----------------------- | ----------------------- |
| `BUD_BROWSERSLIST_UPDATE` | Automatic updating of the browserslist database. | `--browserslist-update` | `true` |
| `BUD_CACHE` | Enable bud.js caching | `--cache` | `true` |
| `BUD_DEVTOOL` | Specify sourcemap option | `--devtool` | `true` |
| `BUD_ENTRYPOINTS` | Generate an entrypoints manifest | `--entrypoints` | `true` |
| `BUD_ENTRYPOINTS_HTML` | Generate an html file containing scripts and styles to be utilized in a server response | `--entrypoints.html` | `false` |
| `BUD_ESM` | Enable ESM output (experimental) | `--esm` | `false` |
| `BUD_HASH` | Enable output file hashing | `--hash` | `true` in production |
| `BUD_HTML` | Enable HTML templating; set to a string to specify the path to a template | `--html` | `false` |
| `BUD_HOT` | Enable hot reloading | `--hot` | `true` in development |
| `BUD_IMMUTABLE` | Enable automated updates of dependencies when using remote modules | `--immutable` | `false` |
| `BUD_LAZY` | Enable lazy compilation of modules in development | `--lazy` | `true` |
| `BUD_MINIMIZE` | Enable code minimization | `--minimize` | `true` in production |
| `BUD_PATH_BASE` | Project directory | `--basedir` | `process.cwd()` |
| `BUD_PATH_STORAGE` | Temporary storage and cache directory | `--storage` | OS cache directory |
| `BUD_PATH_INPUT` | Source directory | `--input` | `src` |
| `BUD_PATH_OUTPUT` | Output directory | `--output` | `dist` |
| `BUD_PATH_PUBLIC` | Directory to prepend to asset URLs (if assets are not served from web root) | `--publicPath` | `auto` |
| `BUD_PROXY_URL` | The URL of the proxy server to use | `--proxy` | `false` |
| `BUD_RUNTIME` | Enable generation of a runtime module | `--runtime` | `single` |
| `BUD_SPLIT_CHUNKS` | Enable splitting of modules into discrete chunks (`vendor`, `app`, etc.) | `--split-chunks` | `false` |
| `BUD_URL` | The URL of the development server | `--url` | `http://localhost:3000` |

## CLI

Expand Down
12 changes: 6 additions & 6 deletions sources/@repo/docs/content/learn/extending/decorators.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ There are decorators exported from `@roots/bud-framework/extension/decorators`.

Bind a class method.

```ts title="extension.ts"
```ts title=extension.ts
import {Extension} from '@roots/bud-framework/extension'
import {bind} from '@roots/bud-framework/extension/decorators'

Expand All @@ -22,7 +22,7 @@ class MyExtension extends Extension {

## @label

```ts title="extension.ts"
```ts title=extension.ts
import {Extension} from '@roots/bud-framework/extension'
import {label} from '@roots/bud-framework/extension/decorators'

Expand All @@ -32,7 +32,7 @@ class MyExtension extends Extension {}

## @dependsOn

```ts title="extension.ts"
```ts title=extension.ts
import {Extension} from '@roots/bud-framework/extension'
import {dependsOn, label} from '@roots/bud-framework/extension/decorators'

Expand All @@ -43,7 +43,7 @@ class MyExtension extends Extension {}

## @dependsOnOptional

```ts title="extension.ts"
```ts title=extension.ts
import {Extension} from '@roots/bud-framework/extension'
import {
dependsOnOptional,
Expand All @@ -57,7 +57,7 @@ class MyExtension extends Extension {}

## @options

```ts title="extension.ts"
```ts title=extension.ts
import {Extension} from '@roots/bud-framework/extension'
import {options, label} from '@roots/bud-framework/extension/decorators'

Expand All @@ -68,7 +68,7 @@ class MyExtension extends Extension {}

## @plugin

```ts title="extension.ts"
```ts title=extension.ts
import {Extension} from '@roots/bud-framework/extension'
import {options, label} from '@roots/bud-framework/extension/decorators'
import PluginConstructor from 'webpack-manifest-plugin'
Expand Down
23 changes: 3 additions & 20 deletions sources/@repo/docs/content/learn/extending/lifecycle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,7 @@ description: The life of a bud.js Extension
sidebar_label: Lifecycle
---

### init

Async callback. Called first. Useful to avoid needing to deal with `super` and the constructor.

```ts title=extension.ts
import {Bud} from '@roots/bud-framework'
import {Extension} from '@roots/bud-framework/extension'

export default class MyExtension extends Extension {
public label = 'bud-extension'

public async init(app, options) {
// do something
}
}
```

### register
## register

Async callback. Try to do things in this method, when possible.

Expand All @@ -38,7 +21,7 @@ export default class MyExtension extends Extension {
}
```

### boot
## boot

Async callback. Called after `register`. Good for business which requires another
extension to have already had `register` called on it.
Expand All @@ -56,7 +39,7 @@ export default class MyExtension extends Extension {
}
```

### configAfter
## configAfter

Async callback. Called after user configuration has been processed.

Expand Down
16 changes: 16 additions & 0 deletions sources/@repo/docs/content/reference/bud.spa/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: bud.spa
description: Configure the development server for single page applications
custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/spa'
tags:
- dev
---

import Overview from '@site/../../@roots/bud-api/docs/spa/overview.md'
import Usage from '@site/../../@roots/bud-api/docs/spa/usage.md'

<Overview />

## Usage

<Usage />
32 changes: 16 additions & 16 deletions sources/@repo/yarn-plugin-bud/bundles/@yarnpkg/plugin-bud.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions sources/@roots/bud-api/docs/spa/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: bud.spa
---

**bud.spa** configures the development server for single page applications using client routers (like React Router).
39 changes: 39 additions & 0 deletions sources/@roots/bud-api/docs/spa/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: Usage
---

Call **bud.spa** to enable configure bud.js for single page applications.

It is fine to call **bud.spa** with no parameters. bud.js will use a default port:

```ts title=bud.config.ts
export default async bud => {
bud.spa()
}
```

If you want to configure beyond the defaults you may do so using a string, number or URL.

Using a string:

```ts title=bud.config.ts
export default async bud => {
bud.spa(`http://localhost:3030`)
}
```

Using a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL):

```ts title=bud.config.ts
export default async bud => {
bud.spa(new URL(`http://localhost:3030`))
}
```

Using a number:

```ts title=bud.config.ts
export default async bud => {
bud.spa(3030)
}
```
8 changes: 8 additions & 0 deletions sources/@roots/bud-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type * as SetProxyUrl from '@roots/bud-api/methods/setProxyUrl'
import type * as SetPublicProxyUrl from '@roots/bud-api/methods/setPublicProxyUrl'
import type * as SetPublicUrl from '@roots/bud-api/methods/setPublicUrl'
import type * as SetUrl from '@roots/bud-api/methods/setUrl'
import type * as Spa from '@roots/bud-api/methods/spa'
import type * as SplitChunks from '@roots/bud-api/methods/splitChunks'
import type * as Use from '@roots/bud-api/methods/use'
import type * as Watch from '@roots/bud-api/methods/watch'
Expand Down Expand Up @@ -328,6 +329,13 @@ declare module '@roots/bud-framework' {
*/
setUrl(...params: SetUrl.Parameters): Bud

/**
* ## bud.spa
*
* {@link https://bud.js.org/docs/bud.spa 📕 Documentation}
*/
spa(...params: Spa.Parameters): Bud

/**
* ## bud.splitChunks
*
Expand Down
1 change: 1 addition & 0 deletions sources/@roots/bud-api/src/methods/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export {setProxyUrl} from '@roots/bud-api/methods/setProxyUrl'
export {setPublicProxyUrl} from '@roots/bud-api/methods/setPublicProxyUrl'
export {setPublicUrl} from '@roots/bud-api/methods/setPublicUrl'
export {setUrl} from '@roots/bud-api/methods/setUrl'
export {spa} from '@roots/bud-api/methods/spa'
export {splitChunks} from '@roots/bud-api/methods/splitChunks'
export {use} from '@roots/bud-api/methods/use'
export {watch} from '@roots/bud-api/methods/watch'
19 changes: 15 additions & 4 deletions sources/@roots/bud-api/src/methods/serve/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,23 @@ export const serve: serve = async function (this: Bud, input, options) {
if (isString(input)) normalizedUrl = new URL(input)

/**
* If input is a number or array of numbers,
* If input is an array
* try to resolve the requested port(s) and
* assign to the {@link URL}
*/
if (isArray(input) || isNumber(input)) {
if (isArray(input)) {
normalizedUrl.port = await requestPorts(
portOrPortsToNumbers(bud.context.port ?? input),
)
}

/**
* If the input is a number, assign it to the {@link URL}
*/
if (isNumber(input)) {
normalizedUrl.port = `${input}`
}

/**
* If input is an object, convert it to a {@link URL}
* and {@link ServerOptions}
Expand Down Expand Up @@ -83,8 +90,12 @@ const makeURLFromObject = async function (
}

if (options.host) url.hostname = options.host
if (options.port)
url.port = await requestPorts(portOrPortsToNumbers(options.port))
if (options.port) {
if (isArray(options.port))
url.port = isArray(options.port)
? await requestPorts(portOrPortsToNumbers(options.port))
: `${options.port}`
}

if (
url.protocol !== `https:` &&
Expand Down
21 changes: 21 additions & 0 deletions sources/@roots/bud-api/src/methods/spa/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Bud} from '@roots/bud-framework'

export type Parameters = [(number | string | URL)?]

export interface spa {
(...parameters: Parameters): Bud
}

export const spa: spa = function (this: Bud, devUrl) {
if (!this.isDevelopment) return this

const url = devUrl ?? 3000

this.api.logger.log(`bud.spa`, `url:`, url)

this.setUrl(url)
this.setProxyUrl(url)
this.hooks.on(`dev.middleware.proxy.options.ignorePath`, true)

return this
}
76 changes: 76 additions & 0 deletions sources/@roots/bud-api/test/spa.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {beforeEach, describe, expect, it, vi} from 'vitest'

import {spa as spaFn} from '../src/methods/spa'

describe(`@roots/bud-api/methods/spa`, () => {
let bud: any
let spa: spaFn

beforeEach(async () => {
bud = {
api: {
logger: {
log: vi.fn(() => null),
},
},
hooks: {
on: vi.fn(() => null),
},
isDevelopment: true,
server: {
url: `http://0.0.0.0:3000`,
},
setProxyUrl: vi.fn(() => null),
setUrl: vi.fn(() => null),
}

spa = spaFn.bind(bud)
})

it(`should call bud.setUrl`, () => {
spa(`https://example.com:3030`)
expect(bud.setUrl).toHaveBeenCalledWith(`https://example.com:3030`)

spa(3030)
expect(bud.setUrl).toHaveBeenCalledWith(3030)

const url = new URL(`https://example.com`)
spa(url)
expect(bud.setUrl).toHaveBeenCalledWith(url)

spa()
expect(bud.setUrl).toHaveBeenCalledWith(bud.server.url)

Check failure on line 42 in sources/@roots/bud-api/test/spa.test.ts

View workflow job for this annotation

GitHub Actions / unit test ([email protected]/ubuntu-latest)

AssertionError: expected "spy" to be called with arguments: [ 'http://0.0.0.0:3000' ] Received: 1st spy call: Array [ - "http://0.0.0.0:3000", + "https://example.com:3030", ] 2nd spy call: Array [ - "http://0.0.0.0:3000", + 3030, ] 3rd spy call: Array [ - "http://0.0.0.0:3000", + "https://example.com/", ] 4th spy call: Array [ - "http://0.0.0.0:3000", + 3000, ] Number of calls: 4

at sources/@roots/bud-api/test/spa.test.ts:42:24
})

it(`should call bud.setProxyUrl`, () => {
spa(`https://example.com:3030`)
expect(bud.setProxyUrl).toHaveBeenCalledWith(
`https://example.com:3030`,
)

spa(3030)
expect(bud.setProxyUrl).toHaveBeenCalledWith(3030)

const url = new URL(`https://example.com:3030`)
spa(url)
expect(bud.setProxyUrl).toHaveBeenCalledWith(url)
})

it(`should call bud.hooks.on`, () => {
spa()

expect(bud.hooks.on).toHaveBeenCalledWith(
`dev.middleware.proxy.options.ignorePath`,
true,
)
})

it(`should bail when bud.isDevelopment is false`, () => {
bud.isDevelopment = false
spa()

expect(bud.setUrl).not.toHaveBeenCalled()
expect(bud.setProxyUrl).not.toHaveBeenCalled()
expect(bud.hooks.on).not.toHaveBeenCalled()
})
})
Loading
Loading