diff --git a/examples/babel/bud.config.js b/examples/babel/bud.config.js index 288441c294..b2f7f398da 100644 --- a/examples/babel/bud.config.js +++ b/examples/babel/bud.config.js @@ -1,3 +1,4 @@ +// @ts-check /** * @param {import('@roots/bud').Bud} bud */ diff --git a/examples/config-yml/bud.config.yml b/examples/config-yml/bud.config.yml index de702e3e83..9fae17a149 100644 --- a/examples/config-yml/bud.config.yml +++ b/examples/config-yml/bud.config.yml @@ -14,12 +14,17 @@ runtime: true assets: - ['src/**/*.html'] -# Inner properties of bud are fair game +# You can access inner properties babel: setPluginOptions: - '@babel/plugin-transform-runtime' - {helpers: true} +# You can use dot notation +babel.setPluginOptions: + - '@babel/plugin-transform-runtime' + - {helpers: true} + # prefix bud properties with _ # to reference the value splitChunks: _bud.isProduction diff --git a/sources/@roots/bud-api/src/index.ts b/sources/@roots/bud-api/src/index.ts index aa834a4d4c..2bd814edaa 100644 --- a/sources/@roots/bud-api/src/index.ts +++ b/sources/@roots/bud-api/src/index.ts @@ -162,7 +162,39 @@ declare module '@roots/bud-framework' { /** * ## bud.hash * + * Hash output filenames + * * {@link https://bud.js.org/docs/bud.hash 📕 Documentation} + * + * @example + * Enable: + * ```js + * bud.hash() + * ``` + * + * @example + * Disable: + * ```js + * bud.hash(false) + * ``` + * + * @example + * Enable with custom format: + * ```js + * bud.hash('contenthash:8') + * ``` + * + * @example + * Enable with a bud.js callback: + * ```js + * bud.when(bud.isProduction, bud.hash) + * ``` + * + * @example + * Transform the existing value: + * ```js + * bud.hash((value) => !value) + * ``` */ hash(...params: Hash.Parameters): Bud diff --git a/sources/@roots/bud-api/src/methods/config/index.ts b/sources/@roots/bud-api/src/methods/config/index.ts index 91d9d9e65b..e05246f7ef 100644 --- a/sources/@roots/bud-api/src/methods/config/index.ts +++ b/sources/@roots/bud-api/src/methods/config/index.ts @@ -6,6 +6,7 @@ import isFunction from '@roots/bud-support/lodash/isFunction' export type Parameters = [ | ((config: Partial) => Partial) + | ((config: Partial) => Promise>) | Partial, ] @@ -27,7 +28,7 @@ export const config: config = function (this: Bud, input): Bud { if (!app) return app.build.config = isFunction(input) - ? input(app.build.config) + ? await input(app.build.config) : {...app.build.config, ...input} }) diff --git a/sources/@roots/bud-api/src/methods/devtool/index.ts b/sources/@roots/bud-api/src/methods/devtool/index.ts index 65c3e188f8..6999a6edf6 100644 --- a/sources/@roots/bud-api/src/methods/devtool/index.ts +++ b/sources/@roots/bud-api/src/methods/devtool/index.ts @@ -22,7 +22,7 @@ export const devtool: devtool = async function ( if (input instanceof Bud) { this.hooks.on(`build.devtool`, FALLBACK_SOURCEMAP) - this.api.logger.success(`bud.devtool`, `devtool set to`, input) + this.api.logger.success(`bud.devtool:`, `devtool set to`, input) return this } diff --git a/sources/@roots/bud-api/src/methods/hash/index.ts b/sources/@roots/bud-api/src/methods/hash/index.ts index 3c4249d324..d230d773ef 100644 --- a/sources/@roots/bud-api/src/methods/hash/index.ts +++ b/sources/@roots/bud-api/src/methods/hash/index.ts @@ -1,7 +1,9 @@ -import {Bud} from '@roots/bud-framework' +import type {Bud} from '@roots/bud-framework' + +import {InputError} from '@roots/bud-support/errors' export type Value = - | ((hash: boolean | undefined) => boolean) + | ((hash?: boolean) => boolean | string) | boolean | Bud | string @@ -12,34 +14,70 @@ export interface hash { (value: Value): Bud } -export const hash: hash = function (this: Bud, value) { - if (value instanceof Bud || value === undefined) { - this.context.hash = true +export const hash: hash = function (this: Bud, value = true) { + if (typeof value === `boolean`) { + return setHash(this, value) + } + + if (value instanceof this.constructor) { + return setHash(this, true) + } - this.api.logger.success(`bud.hash: hash set to`, this.context.hash) + if (typeof value === `function`) { + value = value(this.context.hash) - return this + if (typeof value !== `boolean` && typeof value !== `string`) + throw new InputError(`bud.hash: invalid input`, { + details: `callbacks supplied to bud.hash should return a boolean or a string value`, + docs: new URL(`https://bud.js.org/reference/bud.hash`), + thrownBy: `@roots/bud-api/methods/hash`, + }) + + if (typeof value === `string`) { + setHash(this, true) + setFormat(this, value) + return this + } + + return setHash(this, value) } if (typeof value === `string`) { - if (!value.startsWith(`[`)) value = `[${value}]` + setHash(this, true) + return setFormat(this, value) + } - this.context.hash = true - this.hooks.on(`value.hashFormat`, value) + throw new InputError(`bud.hash: invalid input`, { + details: `bud.hash accepts a boolean, string, or callback function as input.`, + docs: new URL(`https://bud.js.org/reference/bud.hash`), + thrownBy: `@roots/bud-api/methods/hash`, + }) +} - this.api.logger - .success(`bud.hash: hash set to`, this.context.hash) - .success( - `bud.hash: hash format set to`, - this.hooks.filter(`value.hashFormat`), - ) +const formatHashString = (value: string): string => { + if (!value.startsWith(`[`)) value = `[${value}` + if (!value.endsWith(`]`)) value = `${value}]` + return value +} - return this - } +const setFormat = (bud: Bud, value: string): Bud => { + value = formatHashString(value) + + bud.hooks + .on(`value.hashFormat`, value) + .api.logger.success(`bud.hash: hash format set to`, value) + + return bud +} - this.context.hash = this.maybeCall(value, this.context.hash) +const setHash = (bud: Bud, value: boolean): Bud => { + bud.context.hash = value - this.api.logger.success(`bud.hash: hash set to`, this.context.hash) + bud.api.logger.success( + `bud.hash:`, + `hash`, + value ? `enabled` : `disabled`, + ) - return this + return bud } diff --git a/sources/@roots/bud-api/src/methods/minimize/index.ts b/sources/@roots/bud-api/src/methods/minimize/index.ts index 550c095eaa..3983e1f1a0 100644 --- a/sources/@roots/bud-api/src/methods/minimize/index.ts +++ b/sources/@roots/bud-api/src/methods/minimize/index.ts @@ -13,30 +13,54 @@ export interface minimize { } export const minimize: minimize = function (this: Bud, value = true) { + /** + * Handle {@link Bud} instances (when used as a callback for something like bud.tap, bud.promise, etc) + */ if (value instanceof Bud) { - this.minimizers.enable(true) - this.minimizers.js.enable(true) - this.minimizers.css.enable(true) + ;[this.minimizers, this.minimizers.js, this.minimizers.css].map( + minimizer => minimizer.enable(true), + ) return this } + /** + * Handle true, false + */ if (typeof value == `boolean`) { - this.minimizers.enable(value) - this.minimizers.js.enable(value) - this.minimizers.css.enable(value) + ;[this.minimizers, this.minimizers.js, this.minimizers.css].map( + minimizer => minimizer.enable(value), + ) return this } + /** + * For everything else, enable minimization and reset any state by disabling all minimizers + */ + this.minimizers.enable(true) + this.minimizers.js.enable(false) + this.minimizers.css.enable(false) + + /** + * Handle string (`css`, `js`) + */ if (typeof value == `string`) { - this.minimizers.enable(true) + if (!(value in this.minimizers)) { + throwUndefinedMinimizer() + } + this.minimizers[value].enable(true) return this } + /** + * Handle array of strings ([`css`, `js`]) + */ if (Array.isArray(value)) { - this.minimizers.enable(true) - value.map(key => { - this.minimizers[key].enable(true) + if (value.some(prop => !(prop in this.minimizers))) { + throwUndefinedMinimizer() + } + value.map(prop => { + this.minimizers[prop].enable(true) }) return this } @@ -47,3 +71,11 @@ export const minimize: minimize = function (this: Bud, value = true) { thrownBy: `@roots/bud-api/methods/minimize`, }) } + +const throwUndefinedMinimizer = (): never => { + throw ConfigError.normalize(`Error in bud.minimize`, { + details: `Invalid argument passed to bud.minimize. Minimizer does not exist.`, + docs: new URL(`https://bud.js.org/reference/bud.minimize`), + thrownBy: `@roots/bud-api/methods/minimize`, + }) +} diff --git a/sources/@roots/bud-api/src/methods/persist/index.ts b/sources/@roots/bud-api/src/methods/persist/index.ts index 3096f074b0..52ea48c1d7 100644 --- a/sources/@roots/bud-api/src/methods/persist/index.ts +++ b/sources/@roots/bud-api/src/methods/persist/index.ts @@ -8,12 +8,6 @@ export interface persist { } export const persist: persist = function (this: Bud, type = `filesystem`) { - if (type instanceof Bud) { - this.cache.enabled = true - this.api.logger.success(`bud.cache:`, `set to`, type.cache.type) - return this - } - if (type === false) { this.cache.enabled = false this.api.logger.success(`bud.cache:`, `disabled`) diff --git a/sources/@roots/bud-api/src/methods/runtime/index.ts b/sources/@roots/bud-api/src/methods/runtime/index.ts index fbdc32e11e..0471eb78a6 100644 --- a/sources/@roots/bud-api/src/methods/runtime/index.ts +++ b/sources/@roots/bud-api/src/methods/runtime/index.ts @@ -1,12 +1,13 @@ -import type {Bud} from '@roots/bud-framework' import type {Optimization} from '@roots/bud-framework/config' +import {Bud} from '@roots/bud-framework' + export type Parameters = [ | (( runtime: Optimization.RuntimeChunk | undefined, ) => Optimization.RuntimeChunk) + | Bud | Optimization.RuntimeChunk - | undefined, ] export interface runtime { @@ -17,8 +18,14 @@ export const runtime: runtime = async function ( this: Bud, runtime = `single`, ) { - return this.hooks.on( + const value = runtime instanceof Bud || runtime === true ? `single` : runtime + + this.hooks.on( `build.optimization.runtimeChunk`, - runtime === true ? `single` : runtime, + value, ) + + this.api.logger.success(`bud.runtime:`, `set to`, value) + + return this } diff --git a/sources/@roots/bud-api/test/minimize.test.ts b/sources/@roots/bud-api/test/minimize.test.ts index 30d6c384b4..3ddbcccc1c 100644 --- a/sources/@roots/bud-api/test/minimize.test.ts +++ b/sources/@roots/bud-api/test/minimize.test.ts @@ -48,7 +48,7 @@ describe(`bud.minimize`, () => { minimize(value) expect(spies.pop()).toHaveBeenCalledWith(true) - expect(spies.pop()).not.toHaveBeenCalled() + expect(spies.pop()).toHaveBeenCalledWith(false) expect(spies.pop()).toHaveBeenCalledWith(true) }) @@ -64,7 +64,7 @@ describe(`bud.minimize`, () => { expect(spies.pop()).toHaveBeenCalledWith(true) expect(spies.pop()).toHaveBeenCalledWith(true) - expect(spies.pop()).not.toHaveBeenCalled() + expect(spies.pop()).toHaveBeenCalledWith(false) }) it(`should return bud`, () => { diff --git a/sources/@roots/bud-framework/src/bud/index.ts b/sources/@roots/bud-framework/src/bud/index.ts index a674a23505..12121a9d14 100644 --- a/sources/@roots/bud-framework/src/bud/index.ts +++ b/sources/@roots/bud-framework/src/bud/index.ts @@ -338,8 +338,9 @@ export class Bud { * Await all promised tasks */ @bind - public promise(promise: (bud: Bud) => Promise) { + public promise(promise: (bud: Bud) => Promise): Bud { this.promised.push(promise) + return this } @bind diff --git a/sources/@roots/bud-framework/src/configuration/index.ts b/sources/@roots/bud-framework/src/configuration/index.ts index 4be71c7a73..65bb3d7097 100644 --- a/sources/@roots/bud-framework/src/configuration/index.ts +++ b/sources/@roots/bud-framework/src/configuration/index.ts @@ -41,8 +41,6 @@ class Configuration { }) }) - this.bud.log(`config`, config) - if (!config) { throw ConfigError.normalize(`No configuration found`, { file: source, diff --git a/sources/@roots/bud-framework/src/methods/bindFacade.ts b/sources/@roots/bud-framework/src/methods/bindFacade.ts index ddb5b83b98..e2857626b5 100644 --- a/sources/@roots/bud-framework/src/methods/bindFacade.ts +++ b/sources/@roots/bud-framework/src/methods/bindFacade.ts @@ -1,5 +1,4 @@ -import type {Bud} from '@roots/bud-framework' - +import {Bud} from '@roots/bud-framework' import {BudError} from '@roots/bud-support/errors' import isFunction from '@roots/bud-support/lodash/isFunction' import logger from '@roots/bud-support/logger' @@ -23,16 +22,15 @@ export const bindFacade: bindFacade = function (key, fn, binding?) { if (`bind` in fn) fn = fn.bind(binding ?? this) this.set(key, (...args: Array) => { - logger - .scope(`bud.${key}`) - .log( - `called with args:`, - ...args.map(arg => this.fs.json.stringify(arg)), - ) + logger.enabled && + logger + .scope(`bud.${key}`) + .log(`called with args:`, args.map(parseArgs(this)).join(`, `)) this.promise(async () => { try { - await this.resolvePromises().then(async () => await fn(...args)) + await this.resolvePromises() + await fn(...args) } catch (error) { throw error } @@ -43,3 +41,10 @@ export const bindFacade: bindFacade = function (key, fn, binding?) { return this } + +const parseArgs = (bud: Bud) => (arg: any) => + arg instanceof Bud + ? `(bud)` + : typeof arg === `function` + ? `(function)` + : bud.fs.json.stringify(arg) diff --git a/sources/@roots/bud-framework/src/methods/sequence.ts b/sources/@roots/bud-framework/src/methods/sequence.ts index 2e6d6dccde..b729b29c51 100644 --- a/sources/@roots/bud-framework/src/methods/sequence.ts +++ b/sources/@roots/bud-framework/src/methods/sequence.ts @@ -3,6 +3,9 @@ import type {Bud} from '../index.js' export interface Callback { (value?: Bud): Promise } +export interface Callback { + (value?: Bud): unknown +} export interface sequence { (fns: Array): Promise diff --git a/sources/@roots/bud-minify/src/minify-css/index.ts b/sources/@roots/bud-minify/src/minify-css/index.ts index 1b4bdeea25..e8b283b8a9 100644 --- a/sources/@roots/bud-minify/src/minify-css/index.ts +++ b/sources/@roots/bud-minify/src/minify-css/index.ts @@ -21,13 +21,17 @@ class BudMinimizeCSS extends BudMinimizeCSSPublicApi { * {@link Extension.buildBefore} */ @bind - public override async buildBefore({extensions, hooks}: Bud) { + public override async buildBefore({extensions, hooks, module}: Bud) { const { default: Minimizer, esbuildMinify, lightningCssMinify, swcMinify, - } = await import(`css-minimizer-webpack-plugin`) + } = await module.import( + `css-minimizer-webpack-plugin`, + import.meta.url, + {raw: true}, + ) if (!this.minify && extensions.has(`@roots/bud-swc`)) this.setMinify(() => swcMinify) diff --git a/sources/@roots/bud-minify/src/minify-js/index.ts b/sources/@roots/bud-minify/src/minify-js/index.ts index 2881c9275d..0df3206fb1 100644 --- a/sources/@roots/bud-minify/src/minify-js/index.ts +++ b/sources/@roots/bud-minify/src/minify-js/index.ts @@ -20,13 +20,15 @@ class BudMinimizeJS extends BudMinimizeJSPublicApi { * {@link Extension.buildBefore} */ @bind - public override async buildBefore({extensions, hooks}: Bud) { + public override async buildBefore({extensions, hooks, module}: Bud) { const { default: Minimizer, esbuildMinify, swcMinify, terserMinify, - } = await import(`terser-webpack-plugin`) + } = await module.import(`terser-webpack-plugin`, import.meta.url, { + raw: true, + }) if (!this.minify && extensions.has(`@roots/bud-swc`)) this.setMinify(() => swcMinify) diff --git a/sources/@roots/bud/src/cli/commands/build/index.tsx b/sources/@roots/bud/src/cli/commands/build/index.tsx index 34dc61806a..85a812ca99 100644 --- a/sources/@roots/bud/src/cli/commands/build/index.tsx +++ b/sources/@roots/bud/src/cli/commands/build/index.tsx @@ -143,7 +143,7 @@ export default class BudBuildCommand extends BudCommand { this.hash, `BUD_HASH`, b => async value => b.hash(value), - ] satisfies Override, + ] satisfies Override, [ this.hot, `BUD_HOT`, @@ -180,7 +180,7 @@ export default class BudBuildCommand extends BudCommand { this.minimize, `BUD_MINIMIZE`, b => async v => b.minimize(v), - ] satisfies Override, + ] satisfies Override<`css` | `js` | boolean>, [ this.output, `BUD_PATH_OUTPUT`, diff --git a/sources/@roots/bud/src/cli/flags/hash.ts b/sources/@roots/bud/src/cli/flags/hash.ts index 189eb54a11..3b55ae09a7 100644 --- a/sources/@roots/bud/src/cli/flags/hash.ts +++ b/sources/@roots/bud/src/cli/flags/hash.ts @@ -1,5 +1,6 @@ import {Option} from '@roots/bud-support/clipanion' -export default Option.Boolean(`--hash`, undefined, { +export default Option.String(`--hash`, undefined, { description: `Hash compiled filenames`, + tolerateBoolean: true, }) diff --git a/sources/@roots/bud/src/cli/flags/minimize.ts b/sources/@roots/bud/src/cli/flags/minimize.ts index c4e682e519..3054315da0 100644 --- a/sources/@roots/bud/src/cli/flags/minimize.ts +++ b/sources/@roots/bud/src/cli/flags/minimize.ts @@ -1,5 +1,13 @@ import {Option} from '@roots/bud-support/clipanion' +import {isLiteral, isOneOf} from '@roots/bud-support/typanion' -export default Option.Boolean(`--minimize`, undefined, { +export default Option.String(`--minimize`, undefined, { description: `Minimize compiled assets`, + tolerateBoolean: true, + validator: isOneOf([ + isLiteral(`js`), + isLiteral(`css`), + isLiteral(true), + isLiteral(false), + ]), }) diff --git a/sources/@roots/bud/test/cli-flag-runtime/project/bud.config.ts b/sources/@roots/bud/test/cli-flag-runtime/project/bud.config.ts new file mode 100644 index 0000000000..f49d77b503 --- /dev/null +++ b/sources/@roots/bud/test/cli-flag-runtime/project/bud.config.ts @@ -0,0 +1,8 @@ +import {bud} from '@roots/bud' + +bud.after(async () => { + await bud.fs.write( + bud.path(`@storage`, `config.yml`), + bud.compiler?.config ?? {}, + ) +})