From c3b224fbf1ea50c91421c37687b81357b5fd7522 Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Tue, 28 Nov 2023 06:10:43 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(minor):=20add=20acorn=20opt-ou?= =?UTF-8?q?t=20(#2503)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add some opt-out methods to roots/sage extension - update documentation ## Type of change **PATCH: backwards compatible change** --- examples/babel/bud.config.js | 8 +- examples/babel/jsconfig.json | 5 + examples/config-json/bud.config.json | 12 +- examples/config-yml/bud.config.yml | 6 +- examples/multi-compiler/bud.config.ts | 18 +- examples/multi-compiler/test.html | 8 + examples/sage/bud.config.js | 8 +- examples/swc/src/styles.css | 18 +- examples/wordpress-theme/.eslintrc.js | 4 - examples/wordpress-theme/bud.config.yml | 19 +- examples/wordpress-theme/package.json | 4 +- examples/wordpress-theme/src/app.js | 2 + .../wordpress-theme/src/components/App.js | 2 - examples/wordpress-theme/theme.json | 38 +- .../docs/content/extensions/sage/index.mdx | 14 + .../content/reference/bud.addConfig/index.mdx | 14 + .../docs/content/reference/bud.after.mdx | 3 + .../docs/content/reference/bud.alias.mdx | 3 + .../content/reference/bud.assets/index.mdx | 2 +- .../docs/content/reference/bud.bundle.mdx | 3 + .../content/reference/bud.config/index.mdx | 5 +- .../content/reference/bud.copyDir/index.mdx | 2 +- .../content/reference/bud.copyFile/index.mdx | 2 +- .../content/reference/bud.define/index.mdx | 4 +- .../docs/content/reference/bud.devtool.mdx | 31 +- .../content/reference/bud.entry/index.mdx | 5 +- .../@repo/docs/content/reference/bud.env.mdx | 8 +- .../content/reference/bud.experiments.mdx | 5 +- .../docs/content/reference/bud.externals.mdx | 3 + .../docs/content/reference/bud.fs/index.mdx | 14 +- .../content/reference/bud.fs/s3/index.mdx | 6 - .../@repo/docs/content/reference/bud.get.mdx | 30 +- .../@repo/docs/content/reference/bud.glob.mdx | 5 +- .../docs/content/reference/bud.globSync.mdx | 5 +- .../@repo/docs/content/reference/bud.hash.mdx | 36 +- .../content/reference/bud.hooks/index.mdx | 20 +- .../@repo/docs/content/reference/bud.lazy.mdx | 17 +- .../@repo/docs/content/reference/bud.make.mdx | 58 +- .../content/reference/bud.minimize/index.mdx | 43 +- .../reference/bud.mode/bud.isDevelopment.mdx | 3 +- .../reference/bud.mode/bud.isProduction.mdx | 11 +- .../@repo/docs/content/reference/bud.path.mdx | 5 +- .../docs/content/reference/bud.persist.mdx | 45 ++ .../@repo/docs/content/reference/bud.pipe.mdx | 45 +- .../docs/content/reference/bud.publicPath.mdx | 12 +- .../docs/content/reference/bud.relPath.mdx | 18 + .../docs/content/reference/bud.runtime.mdx | 33 +- .../docs/content/reference/bud.sequence.mdx | 41 ++ .../docs/content/reference/bud.setPath.mdx | 7 + .../content/reference/bud.setPublicPath.mdx | 10 +- .../@repo/docs/content/reference/bud.sh.mdx | 2 + .../content/reference/bud.splitChunks.mdx | 28 +- .../@repo/docs/content/reference/bud.tap.mdx | 2 + .../docs/content/reference/bud.tapAsync.mdx | 6 +- .../@repo/docs/content/reference/bud.use.mdx | 1 + sources/@repo/docs/src/css/custom.css | 13 - sources/@roots/bud-api/docs/assets/usage.md | 15 +- .../@roots/bud-api/docs/compilePaths/usage.md | 8 +- sources/@roots/bud-api/docs/copyDir/usage.md | 8 +- sources/@roots/bud-api/docs/copyFile/usage.md | 14 +- sources/@roots/bud-api/docs/define/usage.md | 2 +- sources/@roots/bud-api/docs/entry/globbing.md | 1 - sources/@roots/bud-api/docs/entry/usage.md | 12 +- sources/@roots/bud-api/docs/html/usage.md | 34 +- .../bud-api/docs/serve/setting-options.md | 12 +- .../bud-api/docs/serve/setting-the-origin.md | 8 +- .../@roots/bud-api/docs/setProxyUrl/usage.md | 4 +- .../bud-api/docs/setPublicProxyUrl/usage.md | 4 +- .../@roots/bud-api/docs/setPublicUrl/usage.md | 4 +- sources/@roots/bud-api/docs/setUrl/usage.md | 4 +- sources/@roots/bud-api/src/index.ts | 54 +- .../bud-api/src/methods/bundle/index.ts | 4 +- .../bud-api/src/methods/copyDir/index.ts | 6 +- .../bud-api/src/methods/devtool/index.ts | 31 +- .../@roots/bud-api/src/methods/hash/index.ts | 43 +- .../bud-api/src/methods/html/helpers.ts | 23 - .../@roots/bud-api/src/methods/html/index.ts | 58 +- .../bud-api/src/methods/minimize/index.ts | 65 +- .../bud-api/src/methods/persist/index.ts | 20 +- .../bud-api/src/methods/splitChunks/index.ts | 5 +- sources/@roots/bud-api/src/service/index.ts | 13 +- sources/@roots/bud-api/test/bundle.test.ts | 11 +- sources/@roots/bud-api/test/devtool.test.ts | 43 +- sources/@roots/bud-api/test/minimize.test.ts | 24 +- sources/@roots/bud-api/test/persist.test.ts | 6 +- .../bud-build/src/config/dependencies.ts | 35 +- .../bud-build/src/config/output/index.ts | 6 +- sources/@roots/bud-build/src/item/index.ts | 3 +- .../@roots/bud-build/src/registry/index.ts | 6 +- sources/@roots/bud-build/src/service/index.ts | 12 +- sources/@roots/bud-cache/src/service.ts | 30 +- sources/@roots/bud-client/src/hot/client.ts | 12 +- .../indicator/indicator.component.ts | 15 +- .../indicator/indicator.controller.ts | 10 +- .../@roots/bud-client/src/types/index.d.ts | 2 +- .../@roots/bud-compiler/src/service/index.tsx | 52 +- .../bud-dashboard/src/components/error.tsx | 146 ++-- sources/@roots/bud-entrypoints/package.json | 12 +- .../@roots/bud-entrypoints/src/extension.ts | 4 +- sources/@roots/bud-entrypoints/src/index.ts | 14 +- sources/@roots/bud-entrypoints/src/types.ts | 12 - .../{src => test}/extension.test.ts | 3 +- sources/@roots/bud-esbuild/README.md | 2 +- sources/@roots/bud-esbuild/docs/02-api.md | 2 +- sources/@roots/bud-eslint/README.md | 14 +- .../bud-eslint/docs/01-configuration.md | 14 +- .../src/bud/commands/bud.eslint.command.tsx | 2 + .../@roots/bud-extensions/src/cdn/index.ts | 4 +- .../src/clean-webpack-plugin/index.ts | 6 +- .../src/html-webpack-plugin/index.ts | 47 +- sources/@roots/bud-extensions/src/index.ts | 5 +- .../interpolate-html-webpack-plugin/index.ts | 4 +- .../bud-extensions/src/service/index.ts | 101 +-- .../src/tsconfig-values/index.ts | 21 +- .../index.ts | 20 +- .../webpack-hot-module-replacement.test.ts | 14 +- .../service/__snapshots__/index.test.ts.snap | 28 +- .../bud-extensions/test/service/index.test.ts | 9 +- sources/@roots/bud-framework/package.json | 2 +- sources/@roots/bud-framework/src/bootstrap.ts | 31 +- .../src/{bud.ts => bud/index.ts} | 189 ++--- .../src/configuration/configuration.ts | 112 --- .../src/configuration/dynamic/index.ts | 23 + .../bud-framework/src/configuration/index.ts | 97 +-- .../src/configuration/static/index.ts | 147 ++++ .../src/extension/decorators/options.ts | 8 + .../bud-framework/src/extension/index.ts | 652 ++++++++++++------ sources/@roots/bud-framework/src/fs.ts | 2 +- sources/@roots/bud-framework/src/index.ts | 3 +- .../bud-framework/src/methods/addConfig.ts | 34 + .../@roots/bud-framework/src/methods/after.ts | 10 +- .../bud-framework/src/methods/bindFacade.ts | 21 +- .../@roots/bud-framework/src/methods/index.ts | 5 +- .../@roots/bud-framework/src/methods/path.ts | 3 +- .../src/methods/processConfigs.ts | 54 +- .../@roots/bud-framework/src/methods/run.ts | 5 +- .../bud-framework/src/methods/sequence.ts | 2 +- .../src/methods/setPublicPath.ts | 6 +- .../@roots/bud-framework/src/methods/tap.ts | 35 - .../bud-framework/src/methods/tapAsync.ts | 41 ++ .../@roots/bud-framework/src/methods/when.ts | 2 +- sources/@roots/bud-framework/src/module.ts | 2 +- sources/@roots/bud-framework/src/project.ts | 80 ++- .../bud-framework/src/registry/build.ts | 4 +- .../bud-framework/src/registry/events.ts | 1 + sources/@roots/bud-framework/src/service.ts | 12 +- .../bud-framework/src/services/cache/index.ts | 2 +- sources/@roots/bud-framework/test/bud.test.ts | 3 +- .../test/configuration/configuration.test.ts | 60 +- .../test/configuration/index.test.ts | 17 - sources/@roots/bud-hooks/src/base/base.ts | 15 +- sources/@roots/bud-hooks/src/event/event.ts | 23 +- sources/@roots/bud-imagemin/README.md | 8 +- .../docs/01-request-query-parameters.md | 4 +- .../docs/02-setting-encoder-options.md | 4 +- .../@roots/bud-minify/src/extension/index.ts | 18 +- sources/@roots/bud-minify/src/index.ts | 24 +- .../src/minify-css/extension.config.ts | 1 - .../src/minify-js/extension.config.ts | 52 +- .../@roots/bud-postcss/src/extension/index.ts | 7 +- .../bud-preset-wordpress/src/extension.ts | 2 + .../src/bud/commands/bud.prettier.command.tsx | 2 + .../bud-purgecss/src/extension/index.ts | 3 + .../bud-react/src/react-refresh/index.ts | 8 +- .../bud-sass/src/resolve-url/extension.ts | 3 +- sources/@roots/bud-server/src/server/base.ts | 9 +- .../__snapshots__/service.test.ts.snap | 17 - .../@roots/bud-server/src/service/index.ts | 19 +- .../bud-server/test/middleware-proxy.test.ts | 8 +- .../bud-support/src/decorators/deprecated.ts | 7 +- .../bud-support/src/decorators/index.ts | 1 + .../@roots/bud-support/src/decorators/log.ts | 17 + .../@roots/bud-support/src/errors/index.ts | 239 +++---- .../bud-support/src/filesystem/index.ts | 3 +- .../@roots/bud-support/src/utilities/files.ts | 6 +- .../@roots/bud-support/src/utilities/paths.ts | 2 +- sources/@roots/bud-support/src/value/index.ts | 1 + .../bud-support/src/which-pm/pmString.ts | 6 +- sources/@roots/bud-swc/README.md | 32 +- .../@roots/bud-swc/docs/01-configuration.md | 32 +- sources/@roots/bud-tailwindcss/README.md | 14 +- .../bud-tailwindcss/docs/01-configuration.md | 14 +- .../bud-tailwindcss/src/extension/options.ts | 5 + sources/@roots/bud-typescript/README.md | 8 +- .../@roots/bud-typescript/docs/01-config.mdx | 2 +- .../bud-typescript/docs/02-typechecking.mdx | 5 +- .../@roots/bud-typescript/docs/03-babel.mdx | 2 +- .../src/bud/commands/bud.ts.command.tsx | 2 + sources/@roots/bud/src/cli/app.tsx | 112 +-- .../bud/src/cli/commands/build/index.tsx | 2 +- .../bud/src/cli/commands/doctor/buildInfo.tsx | 3 +- .../bud/src/cli/commands/doctor/index.tsx | 17 +- .../bud/src/cli/commands/doctor/server.tsx | 23 +- .../bud/src/cli/commands/env/displayEnv.tsx | 4 +- sources/@roots/bud/src/cli/commands/index.tsx | 27 +- .../@roots/bud/src/cli/commands/repl/Repl.tsx | 16 +- .../bud/src/cli/commands/view/index.tsx | 3 +- .../bud/src/cli/components/LabelBox.tsx | 12 +- sources/@roots/bud/src/cli/finder.ts | 9 +- .../@roots/bud/src/cli/flags/publicPath.ts | 1 - sources/@roots/bud/src/cli/flags/use.ts | 2 + .../src/cli/helpers/browserslistUpdate.tsx | 6 +- .../@roots/bud/src/cli/helpers/override.ts | 12 +- .../src/html.emitter.ts | 69 +- .../entrypoints-webpack-plugin/src/plugin.ts | 26 +- .../test/plugin.test.ts | 2 +- sources/@roots/sage/README.md | 22 +- .../sage/docs/01-included-extensions.md | 12 +- sources/@roots/sage/docs/05-blade-assets.md | 10 + .../sage/src/acorn-v2-public-path/index.ts | 2 +- sources/@roots/sage/src/acorn/index.ts | 1 + .../@roots/sage/src/blade-loader/extension.ts | 7 +- sources/@roots/sage/src/index.ts | 53 ++ sources/@roots/sage/src/sage/index.ts | 43 +- .../@roots/sage/test/acorn/extension.test.ts | 4 +- .../@roots/sage/test/sage/extension.test.ts | 54 +- .../__snapshots__/sass.test.ts.snap | 11 - tests/integration/sass.test.ts | 10 +- tests/util/project/package.json | 12 +- yarn.lock | 2 + 220 files changed, 2970 insertions(+), 1884 deletions(-) create mode 100644 examples/babel/jsconfig.json create mode 100644 examples/multi-compiler/test.html delete mode 100644 examples/wordpress-theme/.eslintrc.js create mode 100644 sources/@repo/docs/content/reference/bud.addConfig/index.mdx create mode 100644 sources/@repo/docs/content/reference/bud.persist.mdx create mode 100644 sources/@repo/docs/content/reference/bud.relPath.mdx create mode 100644 sources/@repo/docs/content/reference/bud.sequence.mdx delete mode 100644 sources/@roots/bud-api/src/methods/html/helpers.ts delete mode 100644 sources/@roots/bud-entrypoints/src/types.ts rename sources/@roots/bud-entrypoints/{src => test}/extension.test.ts (79%) rename sources/@roots/bud-framework/src/{bud.ts => bud/index.ts} (71%) delete mode 100644 sources/@roots/bud-framework/src/configuration/configuration.ts create mode 100644 sources/@roots/bud-framework/src/configuration/dynamic/index.ts create mode 100644 sources/@roots/bud-framework/src/configuration/static/index.ts create mode 100644 sources/@roots/bud-framework/src/methods/addConfig.ts create mode 100644 sources/@roots/bud-framework/src/methods/tapAsync.ts delete mode 100644 sources/@roots/bud-framework/test/configuration/index.test.ts delete mode 100644 sources/@roots/bud-server/src/service/__snapshots__/service.test.ts.snap create mode 100644 sources/@roots/bud-support/src/decorators/log.ts delete mode 100644 tests/integration/__snapshots__/sass.test.ts.snap diff --git a/examples/babel/bud.config.js b/examples/babel/bud.config.js index e1310ded68..288441c294 100644 --- a/examples/babel/bud.config.js +++ b/examples/babel/bud.config.js @@ -1,6 +1,7 @@ +/** + * @param {import('@roots/bud').Bud} bud + */ export default async bud => { - bud.entry('app', ['app.js', 'app.css']) - bud.babel .setPresets({ '@babel/preset-env': '@babel/preset-env', @@ -11,4 +12,7 @@ export default async bud => { {helpers: false}, ], }) + .done() + + .entry('app', ['app.js']) } diff --git a/examples/babel/jsconfig.json b/examples/babel/jsconfig.json new file mode 100644 index 0000000000..5270013b0a --- /dev/null +++ b/examples/babel/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "types": ["@roots/bud", "@roots/bud-babel"] + } +} diff --git a/examples/config-json/bud.config.json b/examples/config-json/bud.config.json index 623ad6dee8..6f7419c5aa 100644 --- a/examples/config-json/bud.config.json +++ b/examples/config-json/bud.config.json @@ -1,9 +1,13 @@ { - "entry": ["app", ["app.js"]], - "babel": { - "setPluginOptions": [ + entry: ["app", ["app.js"]], + + /** + * Comments are okay + */ + babel: { + setPluginOptions: [ "@babel/plugin-transform-runtime", - {"helpers": false, "regenerator": false} + {helpers: false, regenerator: false} ] } } diff --git a/examples/config-yml/bud.config.yml b/examples/config-yml/bud.config.yml index af5ea26f78..de702e3e83 100644 --- a/examples/config-yml/bud.config.yml +++ b/examples/config-yml/bud.config.yml @@ -36,9 +36,9 @@ hash: > webpackConfig: > => config => ({...config, parallelism: 1}) -# or dynamic values +# logs: `[@examples/config-yml] › 2 + 2 equals 4` log: - - => 2 + 2 + - 2 + 2 - => `equals ${2 + 2}` # For anything that can't be accomplished @@ -56,7 +56,7 @@ tapAsync: > bud.log('hi!', '...eventually') } -# Bringin' it all together +# logs: `[@examples/config-yml] › cache enabled` when: - _bud.cache.enabled - => bud => bud.log('cache enabled') diff --git a/examples/multi-compiler/bud.config.ts b/examples/multi-compiler/bud.config.ts index fc1feb9fb8..4026e8dea2 100644 --- a/examples/multi-compiler/bud.config.ts +++ b/examples/multi-compiler/bud.config.ts @@ -5,20 +5,24 @@ import {bud} from '@roots/bud' * * Each can be uniquely configured. */ -await Promise.all([ +await bud.sequence([ /** * Make `theme` workspace in `./theme` and setup entrypoints * Files will be output to `./theme/dist` */ - bud.make({label: 'theme', basedir: bud.path('theme')}, async theme => - theme.entry('theme', ['theme.js', 'theme.css']), - ), + async bud => + await bud.make( + {label: 'theme', basedir: bud.path('theme')}, + async theme => theme.entry('theme', ['theme.js', 'theme.css']), + ), /** * Make plugin workspace in `./plugin` and setup entrypoints * Files will be output to `./plugin/dist` */ - bud.make({label: 'plugin', basedir: bud.path('plugin')}, async plugin => - plugin.entry('plugin', ['plugin.js', 'plugin.css']), - ), + async bud => + await bud.make( + {label: 'plugin', basedir: bud.path('plugin'), dependsOn: [`theme`]}, + async plugin => plugin.entry('plugin', ['plugin.js', 'plugin.css']), + ), ]) diff --git a/examples/multi-compiler/test.html b/examples/multi-compiler/test.html new file mode 100644 index 0000000000..f64b4c1543 --- /dev/null +++ b/examples/multi-compiler/test.html @@ -0,0 +1,8 @@ + + + Multi Compiler Test + + +

Multi Compiler Test

+ + diff --git a/examples/sage/bud.config.js b/examples/sage/bud.config.js index e6cbbd42b0..c2acce7da1 100644 --- a/examples/sage/bud.config.js +++ b/examples/sage/bud.config.js @@ -1,10 +1,10 @@ // @ts-check /** - * @param {import('@roots/bud').Bud} app + * @param {import('@roots/bud').Bud} bud */ -export default async (app) => { - app +export default async bud => { + bud .entry({ app: ['@scripts/app', '@styles/app'], editor: ['@scripts/editor', '@styles/editor'], @@ -14,7 +14,7 @@ export default async (app) => { .serve(3000) .proxy('http://example.test') - app.wpjson + bud.wpjson .useTailwindColors(true) .useTailwindFontFamily() .useTailwindFontSize() diff --git a/examples/swc/src/styles.css b/examples/swc/src/styles.css index cdf802a399..076377294d 100644 --- a/examples/swc/src/styles.css +++ b/examples/swc/src/styles.css @@ -1,3 +1,19 @@ +html, body { - background-color: white; + padding: 0; + margin: 0; +} + +#root { + align-items: center; + background: rgb(88, 19, 213); + color: white; + display: flex; + font-family: sans-serif; + height: 100vh; + justify-content: center; + letter-spacing: 0.2em; + text-align: center; + text-transform: uppercase; + width: 100vw; } diff --git a/examples/wordpress-theme/.eslintrc.js b/examples/wordpress-theme/.eslintrc.js deleted file mode 100644 index 83a354398a..0000000000 --- a/examples/wordpress-theme/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -export default { - root: true, - extends: ['@roots/bud-preset-wordpress/eslint-config'], -} diff --git a/examples/wordpress-theme/bud.config.yml b/examples/wordpress-theme/bud.config.yml index 014a825722..a3d08bc0a5 100644 --- a/examples/wordpress-theme/bud.config.yml +++ b/examples/wordpress-theme/bud.config.yml @@ -1,8 +1,23 @@ -setPublicPath: /app/themes/your-theme-name/dist/ - entry: app: [app.js, app.css] editor: [editor.js] +entrypoints.setEmitHtml: true + +eslint.extends: + - ['@roots/eslint-config'] + +setPublicPath: /app/themes/your-theme-name/ + +tailwind.extend: + colors: + blue: + 100: '#ebf8ff' + 200: '#bee3f8' + 300: '#90cdf4' + 400: '#63b3ed' + 500: '#4299e1' + wpjson: enable: true + useTailwindColors: true diff --git a/examples/wordpress-theme/package.json b/examples/wordpress-theme/package.json index 9ef278843c..379067147f 100644 --- a/examples/wordpress-theme/package.json +++ b/examples/wordpress-theme/package.json @@ -9,7 +9,9 @@ "devDependencies": { "@roots/bud": "workspace:*", "@roots/bud-eslint": "workspace:*", + "@roots/bud-postcss": "workspace:*", "@roots/bud-preset-wordpress": "workspace:*", - "@roots/bud-swc": "workspace:*" + "@roots/bud-swc": "workspace:*", + "@roots/bud-tailwindcss": "workspace:*" } } diff --git a/examples/wordpress-theme/src/app.js b/examples/wordpress-theme/src/app.js index 993a7a3a61..7c4ecd77bd 100644 --- a/examples/wordpress-theme/src/app.js +++ b/examples/wordpress-theme/src/app.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + import React from 'react' import {createRoot} from 'react-dom/client' import {App} from './components/App.js' diff --git a/examples/wordpress-theme/src/components/App.js b/examples/wordpress-theme/src/components/App.js index 420f63b915..301c794249 100644 --- a/examples/wordpress-theme/src/components/App.js +++ b/examples/wordpress-theme/src/components/App.js @@ -1,5 +1,3 @@ -import React from 'react' - export const App = () => (
+ +## Acorn compatibility + +[roots/sage](https://github.com/roots/sage) is built with the [roots/acorn](https://github.com/roots/acorn) framework. This preset guarantees compatibility with Acorn's API for enqueuing JS and CSS. + +If you do not wish to use Acorn to enqueue assets you can disable the Acorn extension: + +```ts title=bud.config.ts +export default async (bud: Bud) => { + bud.sage.acorn.enable(false) +} +``` + +This will restore the default behavior of including the public path (e.g. `/app/themes/sage/public`) in entrypoints manifest values. diff --git a/sources/@repo/docs/content/reference/bud.addConfig/index.mdx b/sources/@repo/docs/content/reference/bud.addConfig/index.mdx new file mode 100644 index 0000000000..168aa26a9c --- /dev/null +++ b/sources/@repo/docs/content/reference/bud.addConfig/index.mdx @@ -0,0 +1,14 @@ +--- +title: bud.addConfig +description: Parse and execute a configuration module +--- + +Parses and executes a configuration module. + +## Usage + +```js title=bud.config.js +export default async bud => { + await bud.addConfig(`./config/index.js`) +} +``` diff --git a/sources/@repo/docs/content/reference/bud.after.mdx b/sources/@repo/docs/content/reference/bud.after.mdx index 811b5d5ffa..32865a804d 100644 --- a/sources/@repo/docs/content/reference/bud.after.mdx +++ b/sources/@repo/docs/content/reference/bud.after.mdx @@ -1,6 +1,9 @@ --- title: bud.after description: Do something after compilation has completed +tags: + - facade + - lifecycle --- **bud.after** can be used to do tasks after a compilation has completed. diff --git a/sources/@repo/docs/content/reference/bud.alias.mdx b/sources/@repo/docs/content/reference/bud.alias.mdx index dd7597d5a3..a61c4c5c11 100644 --- a/sources/@repo/docs/content/reference/bud.alias.mdx +++ b/sources/@repo/docs/content/reference/bud.alias.mdx @@ -1,6 +1,9 @@ --- title: bud.alias description: Register shorthand for resolving modules +tags: + - configuration + - facade --- **bud.alias** is a helper function for creating aliases. Unlike paths, aliases may be used in your application scripts and stylesheets. diff --git a/sources/@repo/docs/content/reference/bud.assets/index.mdx b/sources/@repo/docs/content/reference/bud.assets/index.mdx index 02820dded2..c43bd732cd 100644 --- a/sources/@repo/docs/content/reference/bud.assets/index.mdx +++ b/sources/@repo/docs/content/reference/bud.assets/index.mdx @@ -4,8 +4,8 @@ description: Include static assets in your compilation even if they aren't refer custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/assets' tags: - assets + - configuration - filesystem - - config --- import Overview from '@site/../../@roots/bud-api/docs/assets/overview.md' diff --git a/sources/@repo/docs/content/reference/bud.bundle.mdx b/sources/@repo/docs/content/reference/bud.bundle.mdx index c53ef74a78..35feb6f990 100644 --- a/sources/@repo/docs/content/reference/bud.bundle.mdx +++ b/sources/@repo/docs/content/reference/bud.bundle.mdx @@ -1,6 +1,9 @@ --- title: bud.bundle description: Specify a set of assets to include in the compilation. +tags: + - configuration + - facade --- Split specified modules into a separate chunk diff --git a/sources/@repo/docs/content/reference/bud.config/index.mdx b/sources/@repo/docs/content/reference/bud.config/index.mdx index a2909c1465..0acb180256 100644 --- a/sources/@repo/docs/content/reference/bud.config/index.mdx +++ b/sources/@repo/docs/content/reference/bud.config/index.mdx @@ -1,10 +1,9 @@ --- title: bud.config -description: Modify the generated webpack config directly +description: Modify the generated webpack config directly prior to compilation custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/config' tags: - - assets - - config + - configuration --- import Overview from '@site/../../@roots/bud-api/docs/config/overview.md' diff --git a/sources/@repo/docs/content/reference/bud.copyDir/index.mdx b/sources/@repo/docs/content/reference/bud.copyDir/index.mdx index 842d47f7da..79025de400 100644 --- a/sources/@repo/docs/content/reference/bud.copyDir/index.mdx +++ b/sources/@repo/docs/content/reference/bud.copyDir/index.mdx @@ -4,7 +4,7 @@ description: Copy a directory to the distribution directory custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/copyDir' tags: - assets - - config + - configuration --- import Overview from '@site/../../@roots/bud-api/docs/copyDir/overview.md' diff --git a/sources/@repo/docs/content/reference/bud.copyFile/index.mdx b/sources/@repo/docs/content/reference/bud.copyFile/index.mdx index 489b9238dd..e028e8506b 100644 --- a/sources/@repo/docs/content/reference/bud.copyFile/index.mdx +++ b/sources/@repo/docs/content/reference/bud.copyFile/index.mdx @@ -4,7 +4,7 @@ description: Copy a file to the distribution directory custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/copyFile' tags: - assets - - config + - configuration --- import Overview from '@site/../../@roots/bud-api/docs/copyFile/overview.md' diff --git a/sources/@repo/docs/content/reference/bud.define/index.mdx b/sources/@repo/docs/content/reference/bud.define/index.mdx index fe97460849..fcdc9f0680 100644 --- a/sources/@repo/docs/content/reference/bud.define/index.mdx +++ b/sources/@repo/docs/content/reference/bud.define/index.mdx @@ -1,9 +1,9 @@ --- title: bud.define -description: Replace variables in your application code and templates at compile time. +description: Replace variables in your application code and templates at compile time custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/define' tags: - - config + - configuration --- import Overview from '@site/../../@roots/bud-api/docs/define/overview.md' diff --git a/sources/@repo/docs/content/reference/bud.devtool.mdx b/sources/@repo/docs/content/reference/bud.devtool.mdx index 3a9b7f17a4..3e77e0544e 100644 --- a/sources/@repo/docs/content/reference/bud.devtool.mdx +++ b/sources/@repo/docs/content/reference/bud.devtool.mdx @@ -1,36 +1,41 @@ --- title: bud.devtool description: Configure sourcemaps +tags: + - configuration + - facade --- -Enable or disable sourcemaps. - -You may pass your own configuration to the function, although this is optional. - -For more information on sourcemap options see [webpack's documentation](https://webpack.js.org/configuration/devtool/). +Configure sourcemaps. ## Usage -Enable sourcemaps +Enable sourcemaps with defaults: + - `eval` in [development](/reference/bud.mode/bud.isDevelopment) + - `source-map` in [production](/reference/bud.mode/bud.isProduction) -```js title=bud.config.js +```ts title=bud.config.ts bud.devtool() ``` Disable sourcemaps -```js title=bud.config.js +```ts title=bud.config.ts bud.devtool(false) ``` Override the default configuration -```js title=bud.config.js -bud.devtool('inline-cheap-module-source-map') +```ts title=bud.config.ts +bud.devtool(`eval-source-map`) ``` -Enable sourcemaps only in development (see [bud.when](bud.when) for extra clarity) +**bud.devtool** can be used as a bud.js callback. When used this way it is the same as enabling sourcemaps with the default configuration. + +```ts title=bud.config.ts +bud.when(bud.isDevelopment, bud.devtool) +``` -```js title=bud.config.js -bud.when(bud.isDevelopment, bud => bud.devtool()) +```ts title=bud.config.ts +bud.tap(bud.devtool) ``` diff --git a/sources/@repo/docs/content/reference/bud.entry/index.mdx b/sources/@repo/docs/content/reference/bud.entry/index.mdx index 98e41139e0..44a129caf8 100644 --- a/sources/@repo/docs/content/reference/bud.entry/index.mdx +++ b/sources/@repo/docs/content/reference/bud.entry/index.mdx @@ -1,14 +1,13 @@ --- title: bud.entry -description: Specify a set of assets to include in the compilation. +description: Define an application entrypoint custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/entry' tags: - - config + - configuration --- import Overview from '@site/../../@roots/bud-api/docs/entry/overview.md' import Usage from '@site/../../@roots/bud-api/docs/entry/usage.md' -import Globbing from '@site/../../@roots/bud-api/docs/entry/globbing.md' diff --git a/sources/@repo/docs/content/reference/bud.env.mdx b/sources/@repo/docs/content/reference/bud.env.mdx index ee450700c3..97cd20c3ee 100644 --- a/sources/@repo/docs/content/reference/bud.env.mdx +++ b/sources/@repo/docs/content/reference/bud.env.mdx @@ -1,6 +1,8 @@ --- title: bud.env description: bud.js includes support for utilizing dotenv values (in your config, templates and the client). +tags: + - env --- **bud.js** includes support for utilizing dotenv values in your configuration, html templates and application code. @@ -25,7 +27,7 @@ bud.env.is('APP_ENV', 'production') ## Accessing env values from within a template -Values defined in the application `.env` file are available within HTML templates ([see **bud.html** for more information on HTML templating](/reference/bud.html)). +Values defined in the application `.env` file are available within HTML templates. See [**bud.html** usage details](/reference/bud.html). ## Accessing env values from within the application @@ -40,3 +42,7 @@ The `PUBLIC_APP_NAME` envvar will be available as `APP_NAME` in application sour ```js title=bud.config.js console.log(APP_NAME) ``` + +## Related + +- [bud.html](/reference/bud.html) diff --git a/sources/@repo/docs/content/reference/bud.experiments.mdx b/sources/@repo/docs/content/reference/bud.experiments.mdx index 7452bc1473..449477b6f9 100644 --- a/sources/@repo/docs/content/reference/bud.experiments.mdx +++ b/sources/@repo/docs/content/reference/bud.experiments.mdx @@ -1,11 +1,14 @@ --- title: bud.experiments description: Enable experimental webpack settings +tags: + - configuration + - facade --- :::warning -These settings are flagged as experimental by Webpack. If you're using `bud.experiments` and you bump into a problem please ensure that +These settings are flagged as experimental by Webpack. If you're using **bud.experiments** and you bump into a problem please ensure that **bud.js** is doing something wrong before opening an issue in the roots/bud repository. Consider accompanying your issue with a PR that implements the change you would like to see. diff --git a/sources/@repo/docs/content/reference/bud.externals.mdx b/sources/@repo/docs/content/reference/bud.externals.mdx index f24c963c3e..e6fa8ec6f7 100644 --- a/sources/@repo/docs/content/reference/bud.externals.mdx +++ b/sources/@repo/docs/content/reference/bud.externals.mdx @@ -1,6 +1,9 @@ --- title: bud.externals description: Specify a non-standard resolution strategy for modules with a matching name. +tags: + - configuration + - facade --- Specify a non-standard resolution strategy for modules with a matching name. diff --git a/sources/@repo/docs/content/reference/bud.fs/index.mdx b/sources/@repo/docs/content/reference/bud.fs/index.mdx index ce0774173c..436434ebfc 100644 --- a/sources/@repo/docs/content/reference/bud.fs/index.mdx +++ b/sources/@repo/docs/content/reference/bud.fs/index.mdx @@ -3,13 +3,13 @@ title: bud.fs description: Work with the filesystem sidebar_label: bud.fs tags: - - config + - configuration - filesystem --- -`bud.fs` is a collection of utilities to help with common filesystem based tasks. +**bud.fs** is a collection of utilities to help with common filesystem based tasks. -All `bud.fs` functions take paths relative to the project's root directory. You can also create new instances with +All **bud.fs** functions take paths relative to the project's root directory. You can also create new instances with a different context using `bud.fs.make`. This is useful for code readability if you need to do several tasks on files in a shared, non-root directory: ```typescript @@ -28,13 +28,7 @@ Utilities for reading and writing yml. See [bud.fs.yml documentation](/reference ## bud.fs.s3 -`bud.fs` includes built-in support for working with remote files stored in S3. It is compatible with Digital Ocean Spaces. - -In order to use this feature you must first install the `@aws-sdk/client-s3` package: - -```npm2yarn -yarn add @aws-sdk/client-s3 -``` +**bud.fs** includes built-in support for working with remote files stored in S3. Once installed, you can set your credentials and bucket name in your bud configuration and do uploads: diff --git a/sources/@repo/docs/content/reference/bud.fs/s3/index.mdx b/sources/@repo/docs/content/reference/bud.fs/s3/index.mdx index 5efb2b6b08..637be43f54 100644 --- a/sources/@repo/docs/content/reference/bud.fs/s3/index.mdx +++ b/sources/@repo/docs/content/reference/bud.fs/s3/index.mdx @@ -7,12 +7,6 @@ sidebar_position: 1 `bud.fs.s3` is a collection of utilities for working with the S3 cloud storage API. -You must install `@aws-sdk/client-s3` to use these features. - -```npm2yarn -yarn add @aws-sdk/client-s3 --dev -``` - ## Setup Digital Ocean users will want to specify the endpoint: diff --git a/sources/@repo/docs/content/reference/bud.get.mdx b/sources/@repo/docs/content/reference/bud.get.mdx index 29834b7b1e..48dda92dbb 100644 --- a/sources/@repo/docs/content/reference/bud.get.mdx +++ b/sources/@repo/docs/content/reference/bud.get.mdx @@ -1,19 +1,21 @@ --- title: bud.get -description: Multi-compiler management utility. Allows you to reference a named compiler. +description: Reference a named bud.js instance. +tags: + - multi-instance --- -A utility for multi-compiler builds. **bud.get** allows you to reference a named compiler. +A utility for multi-instance builds. **bud.get** allows you to reference a named bud.js instance. ## Usage -Get a compiler: +Get an instance: ```js title=bud.config.js export default async bud => bud.get('compiler-name') ``` -Once you have a compiler instance you can continue chaining off it. +Once you have an instance you can continue chaining off it. ```js title=bud.config.js export default async bud => @@ -22,26 +24,26 @@ export default async bud => }) ``` -The parent compiler is accessible using `global` or `parent`. +The parent instance is accessible using `bud.root`. ```js title=bud.config.js -export default async bud => +export default async bud => { + /** + * Parent instance + */ bud /** - * Configuring child compiler + * Uses bud.get to move to child instance */ - .get('child-compiler') + .get('child-instance') .entry({ child: ['index.js'], }) - /** - * Configuring parent compiler + * Uses bud.root to move back to parent instance */ - .get('global') - .entry({ - 'parent-main': ['parent.js'], - }) + .root.setUrl(`http://localhost:3030`) +} ``` ## Related diff --git a/sources/@repo/docs/content/reference/bud.glob.mdx b/sources/@repo/docs/content/reference/bud.glob.mdx index 855988232b..51a370f768 100644 --- a/sources/@repo/docs/content/reference/bud.glob.mdx +++ b/sources/@repo/docs/content/reference/bud.glob.mdx @@ -1,9 +1,12 @@ --- title: bud.glob description: Globbing function +tags: + - filesystem + - helpers --- -Glob for matching files. This function is asynchronous but there is a synchronous version provided by [bud.globSync](/reference/bud.globSync) +Search for matching files. This function is asynchronous but there is a synchronous version provided by [bud.globSync](/reference/bud.globSync) ## Usage diff --git a/sources/@repo/docs/content/reference/bud.globSync.mdx b/sources/@repo/docs/content/reference/bud.globSync.mdx index e4053126ca..d4974c0016 100644 --- a/sources/@repo/docs/content/reference/bud.globSync.mdx +++ b/sources/@repo/docs/content/reference/bud.globSync.mdx @@ -1,9 +1,12 @@ --- title: bud.globSync description: Globbing function +tags: + - filesystem + - helpers --- -Glob for matching files. This function is synchronous but there is an asynchronous version provided by [bud.glob](/reference/bud.glob). The asynchronous version should be preferred. +Search for matching files. This function is synchronous but there is an asynchronous version provided by [bud.glob](/reference/bud.glob). The asynchronous version should be preferred. ## Usage diff --git a/sources/@repo/docs/content/reference/bud.hash.mdx b/sources/@repo/docs/content/reference/bud.hash.mdx index b442cbd842..d4df2cd349 100644 --- a/sources/@repo/docs/content/reference/bud.hash.mdx +++ b/sources/@repo/docs/content/reference/bud.hash.mdx @@ -1,32 +1,44 @@ --- title: bud.hash -description: Enable or disable filename hashing of built assets. +description: Configure filename hashing +tags: + - configuration + - facade + - optimization --- -Enable or disable filename hashing of built assets. +Configure filename hashing. ## Usage -Enable hashing: +Enable hashing -```js +```ts title=bud.config.ts bud.hash() ``` -You may also explicitly pass a boolean **true**: - -```js +```ts title=bud.config.ts bud.hash(true) ``` -Disable hashing: +Disable hashing -```js +```ts title=bud.config.ts bud.hash(false) ``` -## Customizing the hash format +Enable hashing with a custom format: + +```ts title=bud.config.ts +bud.hash(`contenthash:12`) +``` + +Use as a bud.js callback. This is the same as enabling hashing with the default configuration. + +```ts title=bud.config.ts +bud.when(bud.isProduction, bud.hash) +``` -```js -bud.store.set('hashFormat', '[name].[contenthash:6]') +```ts title=bud.config.ts +bud.tap(bud.hash) ``` diff --git a/sources/@repo/docs/content/reference/bud.hooks/index.mdx b/sources/@repo/docs/content/reference/bud.hooks/index.mdx index 9bfc9e8360..1d601e0730 100644 --- a/sources/@repo/docs/content/reference/bud.hooks/index.mdx +++ b/sources/@repo/docs/content/reference/bud.hooks/index.mdx @@ -16,7 +16,7 @@ Callbacks and raw values are registered with `bud.hooks.on` ### Example defined with a raw value -```js title=bud.config.js +```ts title=bud.config.ts bud.hooks.on('build.externals', { $: 'jquery', }) @@ -26,7 +26,7 @@ bud.hooks.on('build.externals', { You should always assume the value passed to the callback may be **undefined** and handle it appropriately. -```js title=bud.config.js +```ts title=bud.config.ts bud.hooks.on('build.externals', (externals = {}) => ({ ...externals, $: 'jquery', @@ -37,7 +37,7 @@ bud.hooks.on('build.externals', (externals = {}) => ({ Filters are registered with `bud.hooks.filter`. -```js +```ts title=bud.config.ts const initialValue = `foo` const result = bud.hooks.filter('my.filter.key', initialValue) ``` @@ -51,7 +51,7 @@ const result = bud.hooks.filter('my.filter.key', initialValue) Some hooks are asynchronous. An example of this are values for `build.resolve.modules`: -```js title=bud.config.js +```ts title=bud.config.ts bud.hooks.async('build.resolve.modules', async (paths = []) => [ ...paths, bud.path(`modules`), @@ -65,7 +65,7 @@ Callbacks for asynchronous hooks should be registered as asynchronous functions. Actions are registered with `bud.hooks.action`. Every action is passed the **bud** object and it will never be **undefined**. You don't need to return anything. -```js +```ts title=bud.config.ts bud.hooks.action('my.action.key', async bud => { bud.log('action log') // do something }) @@ -73,7 +73,7 @@ bud.hooks.action('my.action.key', async bud => { Multiple actions can be defined for the same key in one call: -```js +```ts title=bud.config.ts bud.hooks.action( `my.action.key`, async bud => bud.log(`action log 1`), @@ -89,13 +89,13 @@ specifying a single action or multiple actions. Actions are fired with `bud.hooks.fire`. -```js +```ts title=bud.config.ts await bud.hooks.fire('my.action.key') ``` ## Putting it together -```js title=bud.config.js +```ts title=bud.config.ts bud.hooks.filter(`my.hook`, 1) // => 1 @@ -120,7 +120,7 @@ await bud.hooks.fire(`my.action`) Consider this example, where we want to get a function that has been registered to a hook. We only want to run the last registered function and we only want to call it one time: -```js +```ts title=bud.config.ts const doThing = () => { console.log(`thing`) } @@ -143,7 +143,7 @@ the filter runs, and the functions we defined don't return anything. So the end The way around it is to wrap our functions in a callback: -```js +```ts title=bud.config.ts const doThing = () => { console.log(`thing`) } diff --git a/sources/@repo/docs/content/reference/bud.lazy.mdx b/sources/@repo/docs/content/reference/bud.lazy.mdx index 2e5b12a769..a8d8d94145 100644 --- a/sources/@repo/docs/content/reference/bud.lazy.mdx +++ b/sources/@repo/docs/content/reference/bud.lazy.mdx @@ -1,15 +1,18 @@ --- title: bud.lazy -description: Configure sourcemaps +description: Enable or disable lazy compilation. +tags: + - configuration + - facade --- -Configure lazy compilation. By default, bud.js only compiles imports (not entrypoints) lazily, and only in development. +Configure lazy compilation settings. -For more information on the `lazyCompilation` option see [webpack's documentation](https://webpack.js.org/configuration/experiments/#experimentslazycompilation). +In development bud.js compiles imports (not entrypoints) lazily. ## Usage -Enable lazy compilation (default): +Enable lazy compilation. ```ts title=bud.config.ts bud.lazy() @@ -19,7 +22,7 @@ bud.lazy() bud.lazy(true) ``` -Disable lazy compilation: +Disable lazy compilation. ```ts title=bud.config.ts bud.lazy(false) @@ -27,7 +30,7 @@ bud.lazy(false) ## Custom configuration - +For more information see [webpack's documentation on `experiments.lazyCompilation`](https://webpack.js.org/configuration/experiments/#experimentslazycompilation). ```ts title=bud.config.ts bud.lazy({ @@ -38,6 +41,6 @@ bud.lazy({ entries: false, // do not lazily compile moduleB - test: (module) => !/moduleB/.test(module.nameForCondition()), + test: module => !/moduleB/.test(module.nameForCondition()), }) ``` diff --git a/sources/@repo/docs/content/reference/bud.make.mdx b/sources/@repo/docs/content/reference/bud.make.mdx index ef6f122caf..7d0d46fcdf 100644 --- a/sources/@repo/docs/content/reference/bud.make.mdx +++ b/sources/@repo/docs/content/reference/bud.make.mdx @@ -3,11 +3,9 @@ title: bud.make description: Create a new, configurable instance of bud.js. tags: - configuration + - multi-instance --- -import Tabs from '@theme/Tabs' -import TabItem from '@theme/TabItem' - Create a new, configurable instance of bud.js. For more context on how this might be useful check out [the guide on multi-instance configurations](/learn/general-use/multi-instance). @@ -21,15 +19,15 @@ For more context on how this might be useful check out [the guide on multi-insta Example specified with a **label**: -```js +```ts title=bud.config.ts export default async bud => { - await bud.make('compiler-a', async child => child.entry('app', 'app.js')) + await bud.make(`compiler-a`, async child => child.entry('app', 'app.js')) } ``` Example specified with **context** overrides: -```ts +```ts title=bud.config.ts export default async bud => { await bud.make( { @@ -45,43 +43,33 @@ export default async bud => { ## Preventing runtime conflicts -When loading output from multiple instances on a single page you need to be aware of the potential for runtime conflicts. +When loading entrypoints from multiple instances within a single request you need to be aware of the potential for runtime conflicts. If you are using the [bud.runtime](/reference/bud.runtime) function, you likely want to use the value `single` (the default value). -Beyond that, it may be helpful to know that each instance of bud.js declares all previous instances as _dependencies_. So, in the above examples, -`compiler-a` would be a dependency of `compiler-b`. If you added another instance, `compiler-c`, it would be dependent on both `compiler-a` and `compiler-b`. +If you are using a different value than `single` you may need to declare cross-instance dependencies. -You can modify this behavior by declaring your own dependencies using the `dependsOn` context prop. +For example, let's say we have an instance, `compiler-a`, that adds a value to the `window` object. -```ts -export default async bud => { - await bud.make({ - label: 'compiler-a', - basedir: bud.path('./compiler-a'), - }) - - await bud.make({ - label: 'compiler-b', - basedir: bud.path('./compiler-b'), - }) - - /** - * By default `compiler-c` would depend on `compiler-a` and `compiler-b`. - * By specifying a value `dependsOn` we can override this behavior. - */ - await bud.make({ - label: 'compiler-c', - basedir: bud.path('./compiler-c'), - dependsOn: ['compiler-a'], - }) -} +```ts title=bud.config.ts +await bud.make({ + label: `compiler-a`, + basedir: bud.path(`./compiler-a`), +}) ``` -You can also modify this behavior with the `build.dependencies` hook: +And now we have a second instance, `compiler-b`, which depends on the value added by `compiler-a`. + +To avoid type errors and variable collisions will want to mark `compiler-b` as depending on `compiler-a`. + +You can do that by including the `dependsOn` property in the instance context: -```ts -bud.get('compiler-c').hooks.on('build.dependencies', ['compiler-a']) +```ts title=bud.config.ts +await bud.make({ + label: `compiler-b`, + basedir: bud.path(`./compiler-b`), + dependsOn: [`compiler-a`], +}) ``` Related: diff --git a/sources/@repo/docs/content/reference/bud.minimize/index.mdx b/sources/@repo/docs/content/reference/bud.minimize/index.mdx index 437474dcfa..4a2e71551d 100644 --- a/sources/@repo/docs/content/reference/bud.minimize/index.mdx +++ b/sources/@repo/docs/content/reference/bud.minimize/index.mdx @@ -1,16 +1,43 @@ --- title: bud.minimize -description: Enables or disable minification optimizations. -custom_edit_url: 'https://github.com/roots/bud/tree/main/sources/@roots/bud-api/docs/minimize' +description: Enables or disable minimization optimizations. tags: - - config + - configuration + - optimization --- -import Overview from '@site/../../@roots/bud-api/docs/minimize/overview.md' -import Usage from '@site/../../@roots/bud-api/docs/minimize/usage.md' - - +Configure code optimization options. ## Usage - +Enable optimization: + +```js +bud.minimize() +``` + +Disable optimization: + +```js +bud.minimize(false) +``` + +Use a string or array of strings to enable or disable particular handlers. Possible values: `css`, `js`. + +```js +bud.minimize(`css`) +``` + +```js +bud.minimize([`css`, `js`]) +``` + +Use as a bud.js callback. This is the same as enabling minimization with the default configuration. + +```ts title=bud.config.ts +bud.when(bud.isProduction, bud.minimize) +``` + +```ts title=bud.config.ts +bud.tap(bud.minimize) +``` diff --git a/sources/@repo/docs/content/reference/bud.mode/bud.isDevelopment.mdx b/sources/@repo/docs/content/reference/bud.mode/bud.isDevelopment.mdx index 529496b155..e5056620ec 100644 --- a/sources/@repo/docs/content/reference/bud.mode/bud.isDevelopment.mdx +++ b/sources/@repo/docs/content/reference/bud.mode/bud.isDevelopment.mdx @@ -19,10 +19,11 @@ Pairs well with [bud.when](/reference/bud.when): ```js {2} title=bud.config.js export default async bud => - bud.when(bud.isDevelopment, bud => bud.devtool()) + bud.when(bud.isDevelopment, bud.devtool) ``` ## See also - [bud.mode](/reference/bud.mode/) - [bud.isProduction](/reference/bud.mode/bud.isProduction) +- [bud.when](/reference/bud.when) diff --git a/sources/@repo/docs/content/reference/bud.mode/bud.isProduction.mdx b/sources/@repo/docs/content/reference/bud.mode/bud.isProduction.mdx index 8a98ba07c6..79a0970aa9 100644 --- a/sources/@repo/docs/content/reference/bud.mode/bud.isProduction.mdx +++ b/sources/@repo/docs/content/reference/bud.mode/bud.isProduction.mdx @@ -11,17 +11,20 @@ description: Property that is `true` when bud.mode is `production` Very useful in conditionals: -```js title=bud.config.js -export default async bud => bud.isProduction && bud.devtool('source-map') +```ts title=bud.config.ts +export default async bud => { + bud.isProduction && bud.devtool(false) +} ``` Pairs well with [bud.when](/reference/bud.when): -```js {2} title=bud.config.js -bud.when(bud.isProduction, bud => bud.minify()) +```ts title=bud.config.ts +bud.when(bud.isProduction, bud.minimize) ``` ## See also - [bud.mode](/reference/bud.mode/) - [bud.isDevelopment](/reference/bud.mode/bud.isDevelopment) +- [bud.when](/reference/bud.when) diff --git a/sources/@repo/docs/content/reference/bud.path.mdx b/sources/@repo/docs/content/reference/bud.path.mdx index 7a9402db91..4284db428c 100644 --- a/sources/@repo/docs/content/reference/bud.path.mdx +++ b/sources/@repo/docs/content/reference/bud.path.mdx @@ -1,6 +1,9 @@ --- title: bud.path description: Returns an absolute path to a directory or file. +tags: + - helpers + - filesystem --- You can use **bud.path** to reference a file or directory's absolute path. @@ -31,7 +34,7 @@ The following is a table containing `string` values which fulfill a special role :::note The `@storage` path is used to store cache and artifact files. It should not be set with `bud.setPath`. There is a lot of logic -that depends on this path being set, and much of it executes before any user configuration files are loaded. +that depends on this path being set and much of it executes before any user configuration files are loaded. If you want to customize the storage path, you should use the **--storage** flag instead. diff --git a/sources/@repo/docs/content/reference/bud.persist.mdx b/sources/@repo/docs/content/reference/bud.persist.mdx new file mode 100644 index 0000000000..e440c319bb --- /dev/null +++ b/sources/@repo/docs/content/reference/bud.persist.mdx @@ -0,0 +1,45 @@ +--- +title: bud.persist +description: Configure compiler caching +tags: + - cache + - configuration +--- + +Configure compiler caching. + +By default bud.js uses a filesystem cache. + +## Usage + +Enable filesystem caching: + +```ts title=bud.config.ts +bud.persist() +``` + +```ts title=bud.config.ts +bud.persist(`filesystem`) +``` + +Enable memory caching: + +```ts title=bud.config.ts +bud.persist(`memory`) +``` + +Disable caching: + +```ts title=bud.config.ts +bud.persist(false) +``` + +Use as a bud.js callback. This is the same as enabling caching with the default configuration (`filesystem`). + +```ts title=bud.config.ts +bud.when(bud.isDevelopment, bud.persist) +``` + +```ts title=bud.config.ts +bud.tap(bud.persist) +``` diff --git a/sources/@repo/docs/content/reference/bud.pipe.mdx b/sources/@repo/docs/content/reference/bud.pipe.mdx index c156f600b1..2ee1b899a0 100644 --- a/sources/@repo/docs/content/reference/bud.pipe.mdx +++ b/sources/@repo/docs/content/reference/bud.pipe.mdx @@ -2,40 +2,39 @@ title: bud.pipe description: Pipe a value through an array of functions tags: - - utility + - helpers --- -Pipe a value through an array of functions. The return value of each callback is used as input for the next. +Pipe a value through an array of functions. + +Unlike [bud.sequence](/reference/bud.sequence) the output of each function is used as input for the next. ## Usage Pass an array of functions to be executed in sequence. Execution order is guaranteed even if the functions are async. -The output of each function will be used as input for the next. +The output of each function will be used as input for the next arrayed function. The intial function will receive the bud.js instance as input. -bud.config.mjs -export default async bud => { +```ts title=bud.config.ts await bud.pipe([ -async bud => bud.log(`function 1`), -async bud => bud.log(`function 2`), + async bud => bud.minimize(), + async bud => await bud.fs.read(`./src/index.js`), ]) -} - -You can pass an additional second parameter which will be used as an initial value. +``` -If this parameter is not supplied the initial value will be the bud instance. +You can pass a second parameter to be used as an initial value. ```ts title=bud.config.ts -import type {Bud} from '@roots/bud' - -export default async (bud: Bud) => { - await bud.pipe( - [ - async v => `${v}!`, // `this in the initial value!` - async v => `${v}!`, // `this is the initial value!!` - async v => `${v}!`, // `this is the initial value!!!` - ], - `this is the initial value`, - ) -} +await bud.pipe( + [ + async v => `${v}!`, // `this in the initial value!` + async v => `${v}!`, // `this is the initial value!!` + async v => `${v}!`, // `this is the initial value!!!` + ], + `this is the initial value`, +) ``` + +## Related + +- [bud.sequence](/reference/bud.sequence) diff --git a/sources/@repo/docs/content/reference/bud.publicPath.mdx b/sources/@repo/docs/content/reference/bud.publicPath.mdx index 879291ee84..22865c3ea9 100644 --- a/sources/@repo/docs/content/reference/bud.publicPath.mdx +++ b/sources/@repo/docs/content/reference/bud.publicPath.mdx @@ -1,16 +1,22 @@ --- title: bud.publicPath -description: Get the application public path. +description: Get the application public path +tags: + - filesystem --- Get the application public path. -To set the path itself you may use [bud.setPublicPath](/reference/bud.setPublicPath). +To set the path use [bud.setPublicPath](/reference/bud.setPublicPath). ## Usage Get the public path: -```js +```ts title=bud.config.ts bud.publicPath() ``` + +## Related + +- [bud.setPublicPath](/reference/bud.setPublicPath) diff --git a/sources/@repo/docs/content/reference/bud.relPath.mdx b/sources/@repo/docs/content/reference/bud.relPath.mdx new file mode 100644 index 0000000000..48c2cb676d --- /dev/null +++ b/sources/@repo/docs/content/reference/bud.relPath.mdx @@ -0,0 +1,18 @@ +--- +title: bud.relPath +description: Returns an absolute path to a directory or file. +tags: + - helpers + - filesystem +--- + +You can use **bud.relPath** to get a path relative to the project's base directory. + +## Usage + +Given an absolute path to a directory or file, **bud.relPath** returns a path relative to the project's base directory. + +```ts title=bud.config.ts +bud.relPath(`/code/my-project/src`) +// => `src` +``` diff --git a/sources/@repo/docs/content/reference/bud.runtime.mdx b/sources/@repo/docs/content/reference/bud.runtime.mdx index 6c172a8fbe..cbb6b168ad 100644 --- a/sources/@repo/docs/content/reference/bud.runtime.mdx +++ b/sources/@repo/docs/content/reference/bud.runtime.mdx @@ -1,21 +1,40 @@ --- title: bud.runtime description: Get the application public path +tags: + - configuration + - optimization + - runtime --- -Generate a runtime chunk intended to be inlined on the page. Used for code splitting. +Generate an optimized runtime intended to be inlined on the page. ## Usage -```js +Enable a runtime shared amongst all entrypoints + +```ts title=bud.config.ts bud.runtime() ``` -By default `bud` generates a runtime per chunk. You may want to generate a single runtime for your application. -You can override the `runtimeChunk` setting using this function, in that case. +```ts title=bud.config.ts +bud.runtime(`single`) +``` + +Generate multiple runtimes (for multiple entrypoints) + +```ts title=bud.config.ts +bud.runtime(`multiple`) +``` + +Generate a runtime using the default compiler options -```js -bud.runtime('single') +```ts title=bud.config.ts +bud.runtime(true) ``` -The function will accept anything that webpack would. +Disable runtime generation + +```ts title=bud.config.ts +bud.runtime(false) +``` diff --git a/sources/@repo/docs/content/reference/bud.sequence.mdx b/sources/@repo/docs/content/reference/bud.sequence.mdx new file mode 100644 index 0000000000..4eaa88e2f9 --- /dev/null +++ b/sources/@repo/docs/content/reference/bud.sequence.mdx @@ -0,0 +1,41 @@ +--- +title: bud.sequence +description: Pipe a value through an array of functions +tags: + - helpers +--- + +Pipe a value through an array of functions. + +Unlike [bud.pipe](/reference/bud.pipe), the output of each function is discarded. This is useful when you want to execute a sequence of functions in order, +but don't need or want the output of each function to be passed to the next. + +## Usage + +Pass an array of functions to be executed in sequence. Execution order is guaranteed even if the functions are async. + +The output of each function is discarded. Each function will receive the bud.js instance as input. + +```ts title=bud.config.ts +await bud.sequence([ + async bud => console.log(`function 1`), + async bud => console.log(`function 2`), +]) +``` + +You can pass a second parameter which will be used as the input value (instead of the bud.js instance). + +```ts title=bud.config.ts +await bud.sequence( + [ + async v => console.log(`function 1: ${v}`), + async v => console.log(`function 2: ${v}`), + async v => console.log(`function 3: ${v}`), + ], + `this is the initial value`, +) +``` + +## Related + +- [bud.pipe](/reference/bud.pipe) diff --git a/sources/@repo/docs/content/reference/bud.setPath.mdx b/sources/@repo/docs/content/reference/bud.setPath.mdx index 417ac03f15..d73e8c5624 100644 --- a/sources/@repo/docs/content/reference/bud.setPath.mdx +++ b/sources/@repo/docs/content/reference/bud.setPath.mdx @@ -1,6 +1,9 @@ --- title: bud.setPath description: Set application paths +tags: + - configuration + - filesystem --- You can use **bud.setPath** to set a [bud.path](/reference/bud.path) handle. This documentation probably makes more sense if you've read the [bud.path](/reference/bud.path) documentation. @@ -42,3 +45,7 @@ bud.setPath({ ``` Note that if we were to change `@src` again that `@components` path would still reference the old value. This is something to be aware of. + +## Related + +- [bud.path](/reference/bud.path) diff --git a/sources/@repo/docs/content/reference/bud.setPublicPath.mdx b/sources/@repo/docs/content/reference/bud.setPublicPath.mdx index 2f753403b4..790f634ff3 100644 --- a/sources/@repo/docs/content/reference/bud.setPublicPath.mdx +++ b/sources/@repo/docs/content/reference/bud.setPublicPath.mdx @@ -1,6 +1,10 @@ --- title: bud.setPublicPath -description: Set the public path. +description: Set the public path +tags: + - configuration + - facade + - filesystem --- Set the public path. By default the public path will be `''` in production and `/` in development. @@ -30,3 +34,7 @@ You may set the public path using the CLI using the `--publicPath` flag: ```sh bud build --publicPath /assets/ ``` + +## Related + +- [bud.publicPath](/reference/bud.publicPath) diff --git a/sources/@repo/docs/content/reference/bud.sh.mdx b/sources/@repo/docs/content/reference/bud.sh.mdx index 4009603ebf..e9a5eb5b5d 100644 --- a/sources/@repo/docs/content/reference/bud.sh.mdx +++ b/sources/@repo/docs/content/reference/bud.sh.mdx @@ -1,6 +1,8 @@ --- title: bud.sh description: Execute arbitrary shell commands +tags: + - helpers --- **bud.sh** is used to execute arbitrary shell commands. It is a wrapper around the [execa](https://github.com/sindresorhus/execa) package. diff --git a/sources/@repo/docs/content/reference/bud.splitChunks.mdx b/sources/@repo/docs/content/reference/bud.splitChunks.mdx index 928f1de984..a7fa56d294 100644 --- a/sources/@repo/docs/content/reference/bud.splitChunks.mdx +++ b/sources/@repo/docs/content/reference/bud.splitChunks.mdx @@ -1,16 +1,40 @@ --- title: bud.splitChunks description: Separate vendor code from application code +tags: + - configuration + - facade + - optimization --- Separate vendor code from application code. ## Usage +Enable code splitting: + ```js title=bud.config.js bud.splitChunks() ``` -## Options +Explicitly enable code splitting: + +```js title=bud.config.js +bud.splitChunks(true) +``` + +Disable code splitting: + +```js title=bud.config.js +bud.splitChunks(false) +``` + +Use as a bud.js callback. This is the same as enabling code splitting with the default configuration. -[bud.splitChunks](/reference/bud.splitChunks) takes any parameter Webpack does. +```ts title=bud.config.ts +bud.when(bud.isProduction, bud.splitChunks) +``` + +```ts title=bud.config.ts +bud.tap(bud.splitChunks) +``` diff --git a/sources/@repo/docs/content/reference/bud.tap.mdx b/sources/@repo/docs/content/reference/bud.tap.mdx index 0c2a1c322b..ba1e7949fa 100644 --- a/sources/@repo/docs/content/reference/bud.tap.mdx +++ b/sources/@repo/docs/content/reference/bud.tap.mdx @@ -1,6 +1,8 @@ --- title: bud.tap description: Access the bud.js object through a callback +tags: + - helpers --- Access the bud object through a callback. Useful to maintain a function chain. diff --git a/sources/@repo/docs/content/reference/bud.tapAsync.mdx b/sources/@repo/docs/content/reference/bud.tapAsync.mdx index f35188135a..f1b0eb79fb 100644 --- a/sources/@repo/docs/content/reference/bud.tapAsync.mdx +++ b/sources/@repo/docs/content/reference/bud.tapAsync.mdx @@ -1,9 +1,11 @@ --- title: bud.tapAsync description: Access the bud.js object through an async callback +tags: + - helpers --- -Access the bud.js object through an async callback. Useful to maintain a function chain. +Access the bud.js object through an async callback. ## Usage @@ -15,4 +17,4 @@ export default async bud => { } ``` -It may be helpful to consider that the `bud.config.js` export is esssentially just a **bud.tapAsync** callback. +It may be helpful to consider that the bud config default export is esssentially just a **bud.tapAsync** callback. diff --git a/sources/@repo/docs/content/reference/bud.use.mdx b/sources/@repo/docs/content/reference/bud.use.mdx index a916baa81b..530291aa8b 100644 --- a/sources/@repo/docs/content/reference/bud.use.mdx +++ b/sources/@repo/docs/content/reference/bud.use.mdx @@ -2,6 +2,7 @@ title: bud.use description: Function to register an extension or array of extensions tags: + - configuration - extensions --- diff --git a/sources/@repo/docs/src/css/custom.css b/sources/@repo/docs/src/css/custom.css index 5414ca123a..740d994fc3 100644 --- a/sources/@repo/docs/src/css/custom.css +++ b/sources/@repo/docs/src/css/custom.css @@ -10,9 +10,6 @@ --ifm-color-primary-lighter: rgba(82, 93, 220, 0.5); --ifm-color-primary-lightest: rgba(82, 93, 220, 0.1); - --ifm-h1-font-size: 2rem; - --ifm-h2-font-size: 1.5rem; - --ifm-font-family-base: 'Public Sans', system-ui, sans-serif; --ifm-font-family-monospace: 'dm', monospace; } @@ -25,10 +22,6 @@ article[itemprop='blogPost'] header > h1[itemprop='headline'] { font-size: 2.5em !important; } -.markdown > h2 { - --ifm-h2-font-size: 1.8rem; -} - .theme-doc-sidebar-container { border-right: none !important; } @@ -63,9 +56,6 @@ article[itemprop='blogPost'] header > h1[itemprop='headline'] { :root { --ifm-font-size-base: 16px; } - article header h1 { - font-size: 1.5rem !important; - } .hero .hero__title { font-size: 2.5rem; @@ -76,9 +66,6 @@ article[itemprop='blogPost'] header > h1[itemprop='headline'] { :root { --ifm-font-size-base: 16px; } - article header h1 { - font-size: 2.2rem !important; - } } .navbar__link { diff --git a/sources/@roots/bud-api/docs/assets/usage.md b/sources/@roots/bud-api/docs/assets/usage.md index 3dc2b8cb6d..65dfa9238e 100644 --- a/sources/@roots/bud-api/docs/assets/usage.md +++ b/sources/@roots/bud-api/docs/assets/usage.md @@ -8,7 +8,7 @@ All paths are `@src` relative. Copy the entire `@src/images` directory: -```js title=bud.config.js +```ts title=bud.config.ts bud.assets('images') ``` @@ -16,7 +16,7 @@ bud.assets('images') Copy a single file: -```js title=bud.config.js +```ts title=bud.config.ts bud.assets('images/image.png') ``` @@ -24,13 +24,13 @@ bud.assets('images/image.png') You may add array items to specify additional tasks. -```ts title='bud.config.mjs' +```ts title=bud.config.ts bud.assets([`images`, `fonts`]) ``` Or, call **bud.assets** more than once: -```ts title=bud.config.js +```ts title=bud.config.ts bud .assets(`images`) .assets(`fonts`) @@ -42,7 +42,7 @@ For more granular control, you may specify [`CopyPlugin.ObjectPattern`](https:// As an example, to copy all the images from `vendor/images` and preserve the directory structure: -```js title=bud.config.js +```ts title=bud.config.ts bud.assets({ from: `vendor/images/**/*`, context: bud.path(), @@ -51,9 +51,6 @@ bud.assets({ ## Additional information -You don't need to import assets which are utilized by your bundled code. For instance, -if you are referencing a font file from your stylesheet, the font will already be included -in your distribution. You don't need to manually require it with **bud.assets**, although -there is probably no real harm in doing so. +You don't need to import assets which are utilized by your bundled code. For instance, if you are referencing a font file from your stylesheet, the font will already be included in your distribution. You don't need to manually require it with **bud.assets**, although there is probably no real harm in doing so. **bud.assets** is specifically for compiling files which are not already included elsewhere. diff --git a/sources/@roots/bud-api/docs/compilePaths/usage.md b/sources/@roots/bud-api/docs/compilePaths/usage.md index 8acdb03318..853f3b86f4 100644 --- a/sources/@roots/bud-api/docs/compilePaths/usage.md +++ b/sources/@roots/bud-api/docs/compilePaths/usage.md @@ -5,7 +5,7 @@ title: Usage The most basic, broad and intensive application of this function is passing the project's base directory. This will treat _every module_ as a source module. -```js title='bud.config.js' +```ts title=bud.config.ts export default async bud => { bud.compilePaths(bud.path()) } @@ -13,7 +13,7 @@ export default async bud => { It pays to be more restrictive here. So while you could do the above it would be better to narrow it down: -```js title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.compilePaths([bud.path(`@src`), bud.path(`@modules`)]) } @@ -21,7 +21,7 @@ export default async bud => { Even better to only target the modules which actually need it: -```js title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.compilePaths([bud.path(`@src`), bud.path(`@modules/swiper`)]) } @@ -29,7 +29,7 @@ export default async bud => { The best possible thing would be to only treat the directories as sources _for specific filetypes_. This is supported with a second parameter: -```js title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.compilePaths( /** diff --git a/sources/@roots/bud-api/docs/copyDir/usage.md b/sources/@roots/bud-api/docs/copyDir/usage.md index bb7214ed7c..3323b5b87e 100644 --- a/sources/@roots/bud-api/docs/copyDir/usage.md +++ b/sources/@roots/bud-api/docs/copyDir/usage.md @@ -8,7 +8,7 @@ All paths are relative to `@src`. This can be changed with [the `context` parame Copy `@src/images` to `@dist/images`: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyDir(`images`) ``` @@ -16,7 +16,7 @@ bud.copyDir(`images`) Copy `@src/images` to `@dist/example`. -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyDir([`images`, `example`]) ``` @@ -28,7 +28,7 @@ You can pass a second parameter to change the context. Copy `vendor/images` to `@dist/images`: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyDir(`images`, `vendor`) ``` @@ -38,7 +38,7 @@ Any of the underlying options can be dialed in with an optional third parameter. Copy from `@src/fonts` to `@dist/vendor/fonts` and include dotfiles: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyDir(`fonts`, `vendor/fonts`, { globOptions: {dot: true}, }) diff --git a/sources/@roots/bud-api/docs/copyFile/usage.md b/sources/@roots/bud-api/docs/copyFile/usage.md index 5a8cca1326..bdfbb77207 100644 --- a/sources/@roots/bud-api/docs/copyFile/usage.md +++ b/sources/@roots/bud-api/docs/copyFile/usage.md @@ -8,7 +8,7 @@ All paths are relative to `@src`. This can be changed with [the `context` parame Copy `@src/images/image.jpeg` to `@dist/images/image.jpeg`: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile(`images/image.jpeg`) ``` @@ -16,7 +16,7 @@ bud.copyFile(`images/image.jpeg`) Copy `@src/images/image.jpeg` to `@dist/example/image.jpeg`. -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile([`images/image.jpeg`, `example/image.jpeg`]) ``` @@ -24,7 +24,7 @@ bud.copyFile([`images/image.jpeg`, `example/image.jpeg`]) The standard way would be to replace the filename with `@file`: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile([`images/image.jpeg`, `images/@file`]) ``` @@ -34,19 +34,19 @@ You can pass a second parameter to specify base directory of the task (the `cont Copy `vendor/images/image.jpeg` to `@dist/images/image.jpeg`: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile(`images/image.jpeg`, `vendor`) ``` Copy `vendor/images/image.jpeg` to `@dist/example/image.jpeg` -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile([`images/image.jpeg`, `example/image.jpeg`], `vendor`) ``` Copying from `node_modules/@roots/bud/README.md` to `@dist/README.md`: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile( `README.md` await bud.module.getDirectory(`@roots/bud`) @@ -59,7 +59,7 @@ Any of the underlying options can be dialed in with an optional third parameter. Copy from `@src/fonts` to `@dist/vendor/fonts` and include dotfiles: -```typescript title=bud.config.js +```ts title=bud.config.ts bud.copyFile(`fonts/killa.otf`, `@src`, { globOptions: {dot: true}, }) diff --git a/sources/@roots/bud-api/docs/define/usage.md b/sources/@roots/bud-api/docs/define/usage.md index 469c8ff5a5..bfdd1e563f 100644 --- a/sources/@roots/bud-api/docs/define/usage.md +++ b/sources/@roots/bud-api/docs/define/usage.md @@ -4,7 +4,7 @@ title: Usage Call **bud.define** and pass your definitions. -```ts title=bud.config.js +```ts title=bud.config.ts bud.define({ APP_NAME: 'My Application', }) diff --git a/sources/@roots/bud-api/docs/entry/globbing.md b/sources/@roots/bud-api/docs/entry/globbing.md index 5ed5935585..801f6b7a16 100644 --- a/sources/@roots/bud-api/docs/entry/globbing.md +++ b/sources/@roots/bud-api/docs/entry/globbing.md @@ -4,7 +4,6 @@ title: Globbing **bud.entry** can be used with [bud.glob](/reference/bud.glob) to find matching files. -```js title='bud.config.mjs' export default async bud => { bud.entry({ app: await bud.glob('./src/*.{css,js}'), diff --git a/sources/@roots/bud-api/docs/entry/usage.md b/sources/@roots/bud-api/docs/entry/usage.md index 5ea811deac..92edf603d0 100644 --- a/sources/@roots/bud-api/docs/entry/usage.md +++ b/sources/@roots/bud-api/docs/entry/usage.md @@ -4,7 +4,7 @@ title: Usage The simplest usage is a string reference to your application's entrypoint. -```js title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.entry('app') } @@ -12,7 +12,7 @@ export default async bud => { For more control over naming, you may pass two parameters. The first will be used as the name, and the second as the asset signifier. -```js title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.entry('app', 'app.js') } @@ -20,7 +20,7 @@ export default async bud => { It is also possible to pass an array of assets (with or without an entrypoint name). Assets do not have to be the same filetype to be grouped together as a single entrypoint. -```js title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.entry('app', ['app.js', 'app.css']) } @@ -28,7 +28,7 @@ export default async bud => { You may also specify multiple entrypoints in one call using object syntax: -```js title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.entry({ app: ['app.js', 'app.css'], @@ -41,7 +41,7 @@ The entire [EntryObject API](https://webpack.js.org/concepts/entry-points/#objec As an example, you might use [`publicPath`](https://webpack.js.org/configuration/output/#outputpublicpath) to specify a CDN for your a particular entry. -```ts title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.entry({ react: ['react', 'react-dom'], @@ -58,7 +58,7 @@ export default async bud => { **bud.entry** can be used with [bud.glob](/reference/bud.glob) to find matching files. -```js title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.entry({ app: await bud.glob('./src/*.{css,js}'), diff --git a/sources/@roots/bud-api/docs/html/usage.md b/sources/@roots/bud-api/docs/html/usage.md index 50a7efe748..787d205eaa 100644 --- a/sources/@roots/bud-api/docs/html/usage.md +++ b/sources/@roots/bud-api/docs/html/usage.md @@ -2,9 +2,9 @@ title: Usage --- -**bud.html** can be called without passing any options. +**bud.html** can be called to generate an HTML skeleton for your web application. -```typescript title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.html() } @@ -12,7 +12,7 @@ export default async bud => { The default template will source a couple variables from `.env`; you'll want to make sure they are set. -```env title='.env' +```env title=.env PUBLIC_APP_TITLE='My App' PUBLIC_APP_DESCRIPTION='My App Description' ``` @@ -21,7 +21,7 @@ PUBLIC_APP_DESCRIPTION='My App Description' You can customize the generated HTML using an options object. It [accepts everything `HTMLWebpackPlugin` does](https://github.com/jantimon/html-webpack-plugin#options). -```typescript title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.html({ title: 'My App', @@ -37,9 +37,17 @@ export default async bud => { ### Using a custom template -The path to this file will be resolved relative to the project root: +You can use a custom HTML template by passing the path to **bud.html**. If the given path is relative it will be resolved against the project base directory. -```typescript title='bud.config.mjs' +```ts title=bud.config.ts +export default async bud => { + bud.html(`index.html`) +} +``` + +Alternatively, you can use the `template` option as part of an options object: + +```ts title=bud.config.ts export default async bud => { bud.html({ template: 'index.html', @@ -47,12 +55,12 @@ export default async bud => { } ``` -Define your template as an absolute path if this doesn't work for you: +Define your template as an absolute path if it exists outside the project: -```typescript title='bud.config.mjs' +```ts title=bud.config.ts export default async bud => { bud.html({ - template: bud.path(`public/index.html`), + template: `/code/shared/template.html`, }) } ``` @@ -61,18 +69,20 @@ export default async bud => { Add template variables using `replace`. -```typescript {2-6} title='bud.config.js' +```ts {2-6} title=bud.config.ts export default async bud => { bud.html({ template: bud.path(`public/index.html`), - replace: {VARIABLE: `value`}, + replace: { + VARIABLE: `value` + }, }) } ``` You may use any of these variables in the template by surrounding the variable name with `%` characters. -```html title='public/index.html' +```html title=public/index.html %VARIABLE% diff --git a/sources/@roots/bud-api/docs/serve/setting-options.md b/sources/@roots/bud-api/docs/serve/setting-options.md index 8da9545f1c..fa1147b1d9 100644 --- a/sources/@roots/bud-api/docs/serve/setting-options.md +++ b/sources/@roots/bud-api/docs/serve/setting-options.md @@ -4,7 +4,7 @@ title: Options **bud.serve** also supports an options object for more specific configurations: -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve({ host: `dev.example.test`, }) @@ -12,7 +12,7 @@ bud.serve({ You can also pass `options` as a second parameter to `bud.serve`. This can be convenient when you want to specify _some_ options but want to keep the simple configuration syntax for the rest: -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve(`https://dev.example.test`, { cert: bud.path('example.test.crt'), key: bud.path('example.test.key'), @@ -35,7 +35,7 @@ bud.serve(`https://dev.example.test`, { You can use `host` and `port` to set the server origin: -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve({ host: 'dev.example.test', port: 3000, @@ -44,7 +44,7 @@ bud.serve({ Alternatively, you can set the origin with a using the `url` property: -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve({ url: new URL('http://dev.example.test:3000'), }) @@ -54,7 +54,7 @@ bud.serve({ Use the `ssl` option to indicate that the server should be `https` enabled. This requires that `cert` and `key` options to be set as well. -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve({ ssl: true, host: 'dev.example.test', @@ -71,7 +71,7 @@ You don't need to include `ssl` if you are using the `url` property. It will be If you want to use [the node API](https://nodejs.org/api/https.html) more directly you can use the `options` property: -```ts title='bud.config.js' +```ts title=bud.config.ts const cert = await bud.fs.read('example.test.crt') const key = await bud.fs.read('example.test.key') diff --git a/sources/@roots/bud-api/docs/serve/setting-the-origin.md b/sources/@roots/bud-api/docs/serve/setting-the-origin.md index e48564b555..93cfd625a7 100644 --- a/sources/@roots/bud-api/docs/serve/setting-the-origin.md +++ b/sources/@roots/bud-api/docs/serve/setting-the-origin.md @@ -6,13 +6,13 @@ title: Setting the server origin Use a number to change the port. If the port is not available a new port will be dynamically selected. -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve(3010) ``` Use an array of numbers to specify a range of ports to try. The first available port will be used. -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve([3000, 3010]) ``` @@ -20,12 +20,12 @@ bud.serve([3000, 3010]) If you need to change hostname or protocol a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) is preferred: -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve(new URL('http://dev.example.test:3000')) ``` But, a string can be used (it will be converted to a URL): -```ts title='bud.config.js' +```ts title=bud.config.ts bud.serve('http://dev.example.test:3000') ``` diff --git a/sources/@roots/bud-api/docs/setProxyUrl/usage.md b/sources/@roots/bud-api/docs/setProxyUrl/usage.md index 30785e63d3..0de41da17e 100644 --- a/sources/@roots/bud-api/docs/setProxyUrl/usage.md +++ b/sources/@roots/bud-api/docs/setProxyUrl/usage.md @@ -4,7 +4,7 @@ title: Usage Using a string: -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setProxyUrl(`http://example.test`) } @@ -12,7 +12,7 @@ export default async bud => { Using a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL): -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setProxyUrl(new URL(`http://example.test`)) } diff --git a/sources/@roots/bud-api/docs/setPublicProxyUrl/usage.md b/sources/@roots/bud-api/docs/setPublicProxyUrl/usage.md index 9f298a6589..f7c34f3375 100644 --- a/sources/@roots/bud-api/docs/setPublicProxyUrl/usage.md +++ b/sources/@roots/bud-api/docs/setPublicProxyUrl/usage.md @@ -4,7 +4,7 @@ title: Usage Using a string: -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setPublicProxyUrl(`http://example.test`) } @@ -12,7 +12,7 @@ export default async bud => { Using a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL): -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setPublicProxyUrl(new URL(`http://example.test`)) } diff --git a/sources/@roots/bud-api/docs/setPublicUrl/usage.md b/sources/@roots/bud-api/docs/setPublicUrl/usage.md index 171a29eb6a..41e06f87e4 100644 --- a/sources/@roots/bud-api/docs/setPublicUrl/usage.md +++ b/sources/@roots/bud-api/docs/setPublicUrl/usage.md @@ -4,7 +4,7 @@ title: Usage Using a string: -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setPublicUrl(`http://example.test`) } @@ -12,7 +12,7 @@ export default async bud => { Using a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL): -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setPublicUrl(new URL(`http://example.test`)) } diff --git a/sources/@roots/bud-api/docs/setUrl/usage.md b/sources/@roots/bud-api/docs/setUrl/usage.md index f12bfa6b72..6e17a6e212 100644 --- a/sources/@roots/bud-api/docs/setUrl/usage.md +++ b/sources/@roots/bud-api/docs/setUrl/usage.md @@ -4,7 +4,7 @@ title: Usage Using a string: -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setUrl(`http://example.test`) } @@ -12,7 +12,7 @@ export default async bud => { Using a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL): -```typescript title=bud.config.js +```ts title=bud.config.ts export default async bud => { bud.setUrl(new URL(`http://example.test`)) } diff --git a/sources/@roots/bud-api/src/index.ts b/sources/@roots/bud-api/src/index.ts index 8feb878f83..aa834a4d4c 100644 --- a/sources/@roots/bud-api/src/index.ts +++ b/sources/@roots/bud-api/src/index.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. /** - * Extends bud.js with high-level facades to simplify common configuration tasks + * @roots/bud-api * * @see https://bud.js.org * @see https://github.com/roots/bud @@ -178,17 +178,51 @@ declare module '@roots/bud-framework' { */ lazy(...params: Lazy.Parameters): Bud - /** - * @roots/bud-minify - */ - minify: import('@roots/bud-minify').default - /** * ## bud.minimize * + * Minimize compiled code. + * + * Enabled by default when {@link Bud.mode} is `production` + * * {@link https://bud.js.org/docs/bud.minimize 📕 Documentation} + * + * @example + * Enable: + * ```js + * bud.minimize() + * ``` + * + * @example + * Disable: + * ```js + * bud.minimize(false) + * ``` + * + * @example + * Enable only for js: + * ```js + * bud.minimize('js') + * ``` + * + * @example + * Enable multiple minimizers: + * ```js + * bud.minimize(['js', 'css']) + * ``` + * + * @example + * Enable with a bud callback: + * ```js + * bud.when(bud.isProduction, bud.minimize) + * ``` */ - minimize(...params: Minimize.Parameters): Bud + minimize: Minimize.minimize + + /** + * ## bud.minimizers + */ + minimizers: import('@roots/bud-minify').default /** * ## bud.override @@ -300,17 +334,15 @@ declare module '@roots/bud-framework' { watch(...params: Watch.Parameters): Bud /** - * ## bud.config + * ## bud.webpackConfig * * Alias for {@link Config.config bud.config} * * {@link https://bud.js.org/docs/bud.config 📕 Documentation} */ - webpackConfig(...params: Config.Parameters): Bud + webpackConfig(...params: Config.Parameters): Bud } - type Api = Service - interface Services { api: Service extensions: import('@roots/bud-extensions').default diff --git a/sources/@roots/bud-api/src/methods/bundle/index.ts b/sources/@roots/bud-api/src/methods/bundle/index.ts index d86d487d5e..5b734bb584 100644 --- a/sources/@roots/bud-api/src/methods/bundle/index.ts +++ b/sources/@roots/bud-api/src/methods/bundle/index.ts @@ -72,8 +72,8 @@ const normalize = (matcher: Array | RegExp | string): RegExp => { return isRegExp(matcher) ? matcher : isString(matcher) - ? getTestRegExp([matcher]) - : getTestRegExp(matcher) + ? getTestRegExp([matcher]) + : getTestRegExp(matcher) } const getTestRegExp = (matcher: Array): RegExp => diff --git a/sources/@roots/bud-api/src/methods/copyDir/index.ts b/sources/@roots/bud-api/src/methods/copyDir/index.ts index e525b286ab..0422a8c846 100644 --- a/sources/@roots/bud-api/src/methods/copyDir/index.ts +++ b/sources/@roots/bud-api/src/methods/copyDir/index.ts @@ -37,7 +37,9 @@ export const copyDir: copyDir = async function copyDir( .get(`@roots/bud-extensions/copy-webpack-plugin`) .setPatterns((patterns = []) => [...patterns, result]) - this.api.logger.success(`bud.copyDir: asset pattern added`) + this.api.logger + .success(`bud.copyDir`, `asset pattern added`) + .info(result) return this } @@ -51,6 +53,7 @@ export const fromStringFactory = context, from: app.relPath(from), globOptions: {dot: false}, + noErrorOnMissing: true, to: app.relPath(from, `@file`), ...overrides, }) @@ -68,6 +71,7 @@ export const fromTupleFactory = context, from: app.relPath(from), globOptions: {dot: false}, + noErrorOnMissing: true, to: app.relPath(to, `@file`), ...overrides, }) diff --git a/sources/@roots/bud-api/src/methods/devtool/index.ts b/sources/@roots/bud-api/src/methods/devtool/index.ts index c12e2f527f..65c3e188f8 100644 --- a/sources/@roots/bud-api/src/methods/devtool/index.ts +++ b/sources/@roots/bud-api/src/methods/devtool/index.ts @@ -1,7 +1,9 @@ -import type {Bud} from '@roots/bud-framework' import type {Configuration} from '@roots/bud-framework/config' -export type Parameters = [Configuration['devtool']?] +import {Bud} from '@roots/bud-framework' +import isUndefined from '@roots/bud-support/lodash/isUndefined' + +export type Parameters = [(Bud | Configuration['devtool'])?] export interface devtool { (...devtool: Parameters): Promise @@ -13,9 +15,28 @@ export interface facade { export const devtool: devtool = async function ( this: Bud, - input = `cheap-module-source-map`, + input?: Parameters[0], ) { - this.hooks.on(`build.devtool`, input) - this.api.logger.success(`bud.devtool: devtool set to`, input) + const FALLBACK_SOURCEMAP = this.isDevelopment ? `eval` : `source-map` + + if (input instanceof Bud) { + this.hooks.on(`build.devtool`, FALLBACK_SOURCEMAP) + + this.api.logger.success(`bud.devtool`, `devtool set to`, input) + + return this + } + + this.hooks.on( + `build.devtool`, + !isUndefined(input) ? input : FALLBACK_SOURCEMAP, + ) + + this.api.logger.success( + `bud.devtool`, + `devtool set to`, + input ?? FALLBACK_SOURCEMAP, + ) + 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 bbde11d0ba..3c4249d324 100644 --- a/sources/@roots/bud-api/src/methods/hash/index.ts +++ b/sources/@roots/bud-api/src/methods/hash/index.ts @@ -1,16 +1,45 @@ -import type {Bud} from '@roots/bud-framework' +import {Bud} from '@roots/bud-framework' -export type Parameters = [ - (((hash: boolean | undefined) => boolean) | boolean | undefined)?, -] +export type Value = + | ((hash: boolean | undefined) => boolean) + | boolean + | Bud + | string + +export type Parameters = [Value?] export interface hash { - (...value: Parameters): Bud + (value: Value): Bud } -export const hash: hash = function (this: Bud, value = true) { +export const hash: hash = function (this: Bud, value) { + if (value instanceof Bud || value === undefined) { + this.context.hash = true + + this.api.logger.success(`bud.hash: hash set to`, this.context.hash) + + return this + } + + if (typeof value === `string`) { + if (!value.startsWith(`[`)) value = `[${value}]` + + this.context.hash = true + this.hooks.on(`value.hashFormat`, value) + + this.api.logger + .success(`bud.hash: hash set to`, this.context.hash) + .success( + `bud.hash: hash format set to`, + this.hooks.filter(`value.hashFormat`), + ) + + return this + } + this.context.hash = this.maybeCall(value, this.context.hash) - this.api.logger.success(`bud.hash: hash set to ${this.context.hash}`) + + this.api.logger.success(`bud.hash: hash set to`, this.context.hash) return this } diff --git a/sources/@roots/bud-api/src/methods/html/helpers.ts b/sources/@roots/bud-api/src/methods/html/helpers.ts deleted file mode 100644 index 468b140465..0000000000 --- a/sources/@roots/bud-api/src/methods/html/helpers.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type {Options} from '@roots/bud-extensions/html-webpack-plugin' -import type {Bud} from '@roots/bud-framework' - -import isObject from '@roots/bud-support/lodash/isObject' -import isUndefined from '@roots/bud-support/lodash/isUndefined' -import omit from '@roots/bud-support/lodash/omit' - -import type {Parameters} from './index.js' - -export const defaultHtmlPluginOptions = {} - -export const getHtmlPluginOptions = ( - bud: Bud, - options: Parameters[0], -): Omit => { - if (isUndefined(options) || !isObject(options)) - return defaultHtmlPluginOptions - - if (!isUndefined(options.template)) - options.template = bud.path(options.template) - - return {...omit(options, `replace`)} -} diff --git a/sources/@roots/bud-api/src/methods/html/index.ts b/sources/@roots/bud-api/src/methods/html/index.ts index 8ff6459271..f342c670b8 100644 --- a/sources/@roots/bud-api/src/methods/html/index.ts +++ b/sources/@roots/bud-api/src/methods/html/index.ts @@ -2,13 +2,22 @@ import type * as HTMLExtension from '@roots/bud-extensions/html-webpack-plugin' import type * as InterpolateHTMLExtension from '@roots/bud-extensions/interpolate-html-webpack-plugin' import type {Bud} from '@roots/bud-framework' +import {isAbsolute} from 'node:path' + +import isBoolean from '@roots/bud-support/lodash/isBoolean' +import isFunction from '@roots/bud-support/lodash/isFunction' import isObject from '@roots/bud-support/lodash/isObject' +import isString from '@roots/bud-support/lodash/isString' +import isUndefined from '@roots/bud-support/lodash/isUndefined' +import omit from '@roots/bud-support/lodash/omit' type Options = HTMLExtension.Options & { replace?: InterpolateHTMLExtension.Options } -export type Parameters = [(boolean | Options)?] +export type Parameters = [ + (((options?: Options) => Options) | boolean | Options | string)?, +] export interface html { (...options: Parameters): Promise @@ -17,31 +26,44 @@ export interface html { /** * Set HTML template */ -export const html: html = async function (this: Bud, options) { - const {getHtmlPluginOptions} = await import(`./helpers.js`) - - const enabled = options !== false +export const html: html = async function (this: Bud, options = true) { + const isEnabled = options !== false - const htmlExtension = this.extensions.get( + const html = this.extensions.get( `@roots/bud-extensions/html-webpack-plugin`, ) - htmlExtension.enable(enabled) + const interpolate = this.extensions.get( + `@roots/bud-extensions/interpolate-html-webpack-plugin`, + ) + + html.enable(isEnabled) + interpolate.enable(isEnabled) + + if (isBoolean(options)) return this - const htmlOptions = getHtmlPluginOptions(this, options) - if (isObject(htmlOptions)) { - Object.entries(htmlOptions).forEach(v => htmlExtension.set(...v)) + if (isFunction(options)) { + html.setOptions(options(html.options ?? {})) + return this } - const interpolateVariablesExtension = this.extensions.get( - `@roots/bud-extensions/interpolate-html-webpack-plugin`, - ) - interpolateVariablesExtension.enable(enabled) + if (isString(options)) { + html.set(`template`, this.path(options)) + return this + } - if (isObject(options) && isObject(options.replace)) { - Object.entries(options.replace).forEach( - (v: [string, RegExp | string]) => - interpolateVariablesExtension.set(...v), + if (isObject(options)) { + if (!isUndefined(options.template) && !isAbsolute(options.template)) + options.template = this.path(options.template) + + Object.entries(omit(options, `replace`)).forEach(([k, v]) => + html.set(k, v), ) + + if (isObject(options.replace)) { + Object.entries(options.replace).forEach( + (v) => interpolate.set(...v), + ) + } } return this diff --git a/sources/@roots/bud-api/src/methods/minimize/index.ts b/sources/@roots/bud-api/src/methods/minimize/index.ts index 164928cb35..550c095eaa 100644 --- a/sources/@roots/bud-api/src/methods/minimize/index.ts +++ b/sources/@roots/bud-api/src/methods/minimize/index.ts @@ -1,6 +1,9 @@ -import type {Bud} from '@roots/bud-framework' +import {Bud} from '@roots/bud-framework' +import {ConfigError} from '@roots/bud-support/errors' -export type Parameters = [(`css` | `js` | Array<`css` | `js`> | boolean)?] +export type Parameters = [ + (`css` | `js` | Array<`css` | `js`> | boolean | Bud)?, +] /** * Minimize function interface @@ -9,48 +12,38 @@ export interface minimize { (...parameters: Parameters): Bud } -/** - * Enables minification of built assets. - * - * @example - * Enable: - * - * ```js - * bud.minimize() - * ``` - * - * @example - * Explicitly disable: - * - * ```js - * bud.minimize(false) - * ``` - * - * @example - * Explicitly enable: - * - * ```js - * bud.minimize(true) - * ``` - */ export const minimize: minimize = function (this: Bud, value = true) { - if (typeof value == `boolean`) { - this.minify.enable(value) - this.minify.js.enable(value) - this.minify.css.enable(value) + if (value instanceof Bud) { + this.minimizers.enable(true) + this.minimizers.js.enable(true) + this.minimizers.css.enable(true) return this } - this.minify.enable(true) + if (typeof value == `boolean`) { + this.minimizers.enable(value) + this.minimizers.js.enable(value) + this.minimizers.css.enable(value) + return this + } if (typeof value == `string`) { - this.minify[value].enable(true) + this.minimizers.enable(true) + this.minimizers[value].enable(true) return this } - value.map(key => { - this.minify[key].enable(true) - }) + if (Array.isArray(value)) { + this.minimizers.enable(true) + value.map(key => { + this.minimizers[key].enable(true) + }) + return this + } - return this + throw ConfigError.normalize(`Error in bud.minimize`, { + details: `Invalid argument passed to bud.minimize. Value must be a boolean, string, or array of strings.`, + 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 a49a248906..3096f074b0 100644 --- a/sources/@roots/bud-api/src/methods/persist/index.ts +++ b/sources/@roots/bud-api/src/methods/persist/index.ts @@ -1,24 +1,32 @@ -import type {Bud} from '@roots/bud-framework' - +import {Bud} from '@roots/bud-framework' import isString from '@roots/bud-support/lodash/isString' -export type Parameters = [(`filesystem` | `memory` | boolean)?] +export type Parameters = [(`filesystem` | `memory` | boolean | Bud)?] export interface persist { (...parameters: Parameters): Bud } 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(`cache disabled`) + this.api.logger.success(`bud.cache:`, `disabled`) return this } this.cache.enabled = true - this.cache.type = isString(type) ? type : `filesystem` + this.api.logger.success(`bud.cache:`, `enabled`) - this.api.logger.success(`cache enabled`) + if (isString(type)) { + this.cache.type = type + this.api.logger.success(`bud.cache:`, `set to`, type) + } return this } diff --git a/sources/@roots/bud-api/src/methods/splitChunks/index.ts b/sources/@roots/bud-api/src/methods/splitChunks/index.ts index 4813b5b160..8434d69258 100644 --- a/sources/@roots/bud-api/src/methods/splitChunks/index.ts +++ b/sources/@roots/bud-api/src/methods/splitChunks/index.ts @@ -1,8 +1,8 @@ -import type {Bud} from '@roots/bud-framework' import type {Optimization} from '@roots/bud-framework/config' import {join, sep} from 'node:path' +import {Bud} from '@roots/bud-framework' import isUndefined from '@roots/bud-support/lodash/isUndefined' export type Parameters = [ @@ -11,6 +11,7 @@ export type Parameters = [ splitChunks: false | Optimization.SplitChunks | undefined, ) => false | Optimization.SplitChunks) | boolean + | Bud | Optimization.SplitChunks )?, ] @@ -48,7 +49,7 @@ export const splitChunks: splitChunks = async function (this: Bud, value) { * For true and undefined options the default * cache groups are added to the build */ - if (isUndefined(value) || value === true) { + if (isUndefined(value) || value === true || value instanceof Bud) { this.hooks.on(`build.optimization.splitChunks`, (options = {}) => { if (options === false) options = {} diff --git a/sources/@roots/bud-api/src/service/index.ts b/sources/@roots/bud-api/src/service/index.ts index 7c60da7cb4..dfccb69da8 100644 --- a/sources/@roots/bud-api/src/service/index.ts +++ b/sources/@roots/bud-api/src/service/index.ts @@ -2,6 +2,7 @@ import type {Api as BudApi} from '@roots/bud-framework' import type {Bud} from '@roots/bud-framework' import {ServiceContainer} from '@roots/bud-framework/service' +import {bind} from '@roots/bud-support/decorators/bind' import * as methods from '../methods/index.js' @@ -9,18 +10,14 @@ import * as methods from '../methods/index.js' * Bud.API {@link ServiceContainer} */ class Api extends ServiceContainer implements BudApi { - /** - * {@link BudApi.label} - */ - public override label: BudApi[`label`] = `api` - /** * {@link ServiceContainer.bootstrap} */ + @bind public override async bootstrap?(bud: Bud) { - Object.entries(methods).map(([k, v]: [any, any]) => - bud.bindFacade(k, v, bud), - ) + Object.entries(methods).map(([k, v]: Array) => { + bud.bindFacade(k, v) + }) } } diff --git a/sources/@roots/bud-api/test/bundle.test.ts b/sources/@roots/bud-api/test/bundle.test.ts index 701defcb8a..420bc9569f 100644 --- a/sources/@roots/bud-api/test/bundle.test.ts +++ b/sources/@roots/bud-api/test/bundle.test.ts @@ -1,10 +1,11 @@ import {factory} from '@repo/test-kit' import '@roots/bud-api' import {bundle} from '@roots/bud-api/methods/bundle' +import {type Bud} from '@roots/bud-framework' import {beforeEach, describe, expect, it} from 'vitest' describe(`bud.bundle`, () => { - let bud + let bud: Bud let instance: typeof bundle beforeEach(async () => { @@ -13,7 +14,7 @@ describe(`bud.bundle`, () => { }) it(`should set the bundle using a string`, async () => { - await instance(`react`).promise() + await instance(`react`).resolvePromises() expect( bud.hooks.filter(`build.optimization.splitChunks`), @@ -21,7 +22,7 @@ describe(`bud.bundle`, () => { }) it(`should set the bundle using a string name and a string test`, async () => { - await instance(`react`, `react`).promise() + await instance(`react`, `react`).resolvePromises() expect( bud.hooks.filter(`build.optimization.splitChunks`), @@ -29,7 +30,7 @@ describe(`bud.bundle`, () => { }) it(`should set the bundle using a string name and regular expression test`, async () => { - await instance(`react`, /react/).promise() + await instance(`react`, /react/).resolvePromises() expect( bud.hooks.filter(`build.optimization.splitChunks`), @@ -37,7 +38,7 @@ describe(`bud.bundle`, () => { }) it(`should set the bundle using a string name and array of strings test`, async () => { - await instance(`react`, [`react`, `react-dom`]).promise() + await instance(`react`, [`react`, `react-dom`]).resolvePromises() expect( bud.hooks.filter(`build.optimization.splitChunks`), diff --git a/sources/@roots/bud-api/test/devtool.test.ts b/sources/@roots/bud-api/test/devtool.test.ts index fa3a4d3a29..9262a468b8 100644 --- a/sources/@roots/bud-api/test/devtool.test.ts +++ b/sources/@roots/bud-api/test/devtool.test.ts @@ -15,33 +15,54 @@ describe(`bud.devtool`, function () { vi.clearAllMocks() }) - it(`is a function`, () => { + it(`should be a function`, () => { expect(method).toBeInstanceOf(Function) }) - it(`returns bud`, async () => { - const ret = await method() - expect(ret).toBe(bud) + it(`should return bud`, async () => { + const value = await method() + expect(value).toBe(bud) }) - it(`calls bud.hooks.on`, async () => { + it(`should call bud.hooks.on`, async () => { const onSpy = vi.spyOn(bud.hooks, `on`) await method() expect(onSpy).toHaveBeenCalledTimes(1) }) - it(`calls bud.hooks.on`, async () => { + + it (`should set source-map in production`, async () => { + const onSpy = vi.spyOn(bud.hooks, `on`) + await method() + expect(onSpy).toHaveBeenCalledWith(`build.devtool`, `source-map`) + }) + + it(`should set eval in development`, async () => { + bud = await factory({mode: `development`}) + bud.hooks.on = callback + method = devtool.bind(bud) + const onSpy = vi.spyOn(bud.hooks, `on`) await method() - expect(onSpy).toHaveBeenCalledWith( - `build.devtool`, - `cheap-module-source-map`, - ) + expect(onSpy).toHaveBeenCalledWith(`build.devtool`, `eval`) }) - it(`calls bud.hooks.on with expected arguments`, async () => { + it (`should accept a string value`, async () => { + const onSpy = vi.spyOn(bud.hooks, `on`) + await method(`cheap-module-source-map`) + expect(onSpy).toHaveBeenCalledWith(`build.devtool`, `cheap-module-source-map`) + }) + + it(`should accept a callback function`, async () => { const onSpy = vi.spyOn(bud.hooks, `on`) await method(callback) expect(onSpy).toHaveBeenCalledWith(`build.devtool`, callback) }) + + it (`should accept false`, async () => { + const onSpy = vi.spyOn(bud.hooks, `on`) + await method(false) + expect(onSpy).toHaveBeenCalledWith(`build.devtool`, false) + }) + }) diff --git a/sources/@roots/bud-api/test/minimize.test.ts b/sources/@roots/bud-api/test/minimize.test.ts index f1d3d529f2..30d6c384b4 100644 --- a/sources/@roots/bud-api/test/minimize.test.ts +++ b/sources/@roots/bud-api/test/minimize.test.ts @@ -13,9 +13,9 @@ describe(`bud.minimize`, () => { it(`should enable minimizers when called with truthy value`, () => { const spies = [ - vi.spyOn(bud.minify.css, `enable`), - vi.spyOn(bud.minify.js, `enable`), - vi.spyOn(bud.minify, `enable`), + vi.spyOn(bud.minimizers.css, `enable`), + vi.spyOn(bud.minimizers.js, `enable`), + vi.spyOn(bud.minimizers, `enable`), ] const value = true @@ -26,9 +26,9 @@ describe(`bud.minimize`, () => { it(`should disable minimizers when called with falsy value`, () => { const spies = [ - vi.spyOn(bud.minify.css, `enable`), - vi.spyOn(bud.minify.js, `enable`), - vi.spyOn(bud.minify, `enable`), + vi.spyOn(bud.minimizers.css, `enable`), + vi.spyOn(bud.minimizers.js, `enable`), + vi.spyOn(bud.minimizers, `enable`), ] const value = false @@ -39,9 +39,9 @@ describe(`bud.minimize`, () => { it(`should enable a specific minimizer when called with a key`, () => { const spies = [ - vi.spyOn(bud.minify.css, `enable`), - vi.spyOn(bud.minify.js, `enable`), - vi.spyOn(bud.minify, `enable`), + vi.spyOn(bud.minimizers.css, `enable`), + vi.spyOn(bud.minimizers.js, `enable`), + vi.spyOn(bud.minimizers, `enable`), ] const value = `css` @@ -54,9 +54,9 @@ describe(`bud.minimize`, () => { it(`should enable a specific minimizer when called with an array of keys`, () => { const spies = [ - vi.spyOn(bud.minify.css, `enable`), - vi.spyOn(bud.minify.js, `enable`), - vi.spyOn(bud.minify, `enable`), + vi.spyOn(bud.minimizers.css, `enable`), + vi.spyOn(bud.minimizers.js, `enable`), + vi.spyOn(bud.minimizers, `enable`), ] const value: [`js`] = [`js`] diff --git a/sources/@roots/bud-api/test/persist.test.ts b/sources/@roots/bud-api/test/persist.test.ts index 179c30a61d..7daf25186b 100644 --- a/sources/@roots/bud-api/test/persist.test.ts +++ b/sources/@roots/bud-api/test/persist.test.ts @@ -39,19 +39,19 @@ describe(`bud.persist`, () => { it(`should call bud.success to log param`, () => { const successSpy = vi.spyOn(bud.api.logger, `success`) subject() - expect(successSpy).toHaveBeenCalledWith(`cache enabled`) + expect(successSpy).toHaveBeenCalledWith(`bud.cache:`, `enabled`) }) it(`should call bud.success to log param`, () => { const successSpy = vi.spyOn(bud.api.logger, `success`) subject(true) - expect(successSpy).toHaveBeenCalledWith(`cache enabled`) + expect(successSpy).toHaveBeenCalledWith(`bud.cache:`, `enabled`) }) it(`should call bud.success to log param`, () => { const successSpy = vi.spyOn(bud.api.logger, `success`) subject(false) - expect(successSpy).toHaveBeenCalledWith(`cache disabled`) + expect(successSpy).toHaveBeenCalledWith(`bud.cache:`, `disabled`) }) it(`should return bud`, () => { diff --git a/sources/@roots/bud-build/src/config/dependencies.ts b/sources/@roots/bud-build/src/config/dependencies.ts index 7459d9918b..16cc5397df 100644 --- a/sources/@roots/bud-build/src/config/dependencies.ts +++ b/sources/@roots/bud-build/src/config/dependencies.ts @@ -1,11 +1,40 @@ import type {Factory} from '@roots/bud-build/config' +import {ConfigError} from '@roots/bud-support/errors' import isUndefined from '@roots/bud-support/lodash/isUndefined' export const dependencies: Factory<`dependencies`> = async ({ hooks, + label, root, -}) => - hooks +}) => { + const dependencies = hooks .filter(`build.dependencies`, []) - .filter(label => !isUndefined(root.children[label])) + ?.filter(dependency => { + const defined = !isUndefined(root.children?.[dependency]) + + if (!defined) { + throw ConfigError.normalize( + `${dependency} is not a registered instance of bud.js.`, + { + details: root.children + ? `Available instances are: ${Object.values(root.children) + .map(child => child.label) + .join(`, `)}` + : `We would tell you what the available instances are, but there are none registered.`, + docs: new URL( + `https://bud.js.org/learn/general-use/multi-instance`, + ), + instance: label, + thrownBy: `@roots/bud-build/config/dependencies`, + }, + ) + } + + return true + }) + + if (!dependencies || dependencies?.length < 1) return undefined + + return dependencies +} diff --git a/sources/@roots/bud-build/src/config/output/index.ts b/sources/@roots/bud-build/src/config/output/index.ts index 7a976e3c3b..1b0cea79db 100644 --- a/sources/@roots/bud-build/src/config/output/index.ts +++ b/sources/@roots/bud-build/src/config/output/index.ts @@ -30,7 +30,11 @@ export const output: Factory<`output`> = async ({ * @see {@link https://medium.com/@kenneth_chau/speeding-up-webpack-typescript-incremental-builds-by-7x-3912ba4c1d15} */ pathinfo: filter(`build.output.pathinfo`, false), - publicPath: filter(`build.output.publicPath`, `auto`), + publicPath: (() => { + const value = filter(`build.output.publicPath`, `auto`) + if ([``, `auto`].includes(value)) return value + return value.endsWith(`/`) ? value : `${value}/` + })(), scriptType: filter(`build.output.scriptType`, undefined), uniqueName: filter(`build.output.uniqueName`, `@roots/bud`), }) diff --git a/sources/@roots/bud-build/src/item/index.ts b/sources/@roots/bud-build/src/item/index.ts index 529e4d8e12..eb3c0971a4 100644 --- a/sources/@roots/bud-build/src/item/index.ts +++ b/sources/@roots/bud-build/src/item/index.ts @@ -7,6 +7,7 @@ import Registrable from '@roots/bud-build/helpers/registrable' import Loader from '@roots/bud-build/loader' import {bind} from '@roots/bud-support/decorators/bind' import isString from '@roots/bud-support/lodash/isString' +import logger from '@roots/bud-support/logger' export type ConstructorOptions = Build.Item.ConstructorOptions @@ -139,7 +140,7 @@ class Item extends Registrable implements Build.Item { } if (!output.loader) { - this.app.error(`error in ${this.ident}`, `no loader registered`) + logger.error(`error in ${this.ident}`, `no loader registered`) } return Object.entries(output).reduce( diff --git a/sources/@roots/bud-build/src/registry/index.ts b/sources/@roots/bud-build/src/registry/index.ts index 4a991442ac..878c341b5a 100644 --- a/sources/@roots/bud-build/src/registry/index.ts +++ b/sources/@roots/bud-build/src/registry/index.ts @@ -36,7 +36,11 @@ async function register(bud: Bud) { ? bud.build.items.minicss : bud.build.items.style - Object.entries(rules).map(makeRegister(bud, bud.build.setRule)) + await Promise.all( + Object.entries(rules).map(makeRegister(bud, bud.build.setRule)), + ).catch(e => { + throw e + }) } /** diff --git a/sources/@roots/bud-build/src/service/index.ts b/sources/@roots/bud-build/src/service/index.ts index 64510c856c..1d0b2e5ac9 100644 --- a/sources/@roots/bud-build/src/service/index.ts +++ b/sources/@roots/bud-build/src/service/index.ts @@ -43,6 +43,7 @@ class Build extends Service implements BudBuild { /** * {@link Service.register} */ + @bind public override async bootstrap?(app: Bud) { this.items = {} as Items this.loaders = {} as Loaders @@ -160,6 +161,7 @@ class Build extends Service implements BudBuild { ident: K, definition?: ((item: Items[K]) => Items[K]) | Items[K], ): this { + this.logger.log(`build.setItem`, ident) const maybeOptionsCallback = isUndefined(definition) ? {ident, loader: ident} : definition @@ -182,11 +184,12 @@ class Build extends Service implements BudBuild { name: K, definition?: any, ): this { + this.logger.log(`build.setLoader`, name) const loader = isUndefined(definition) ? this.makeLoader(name) : definition instanceof Loader - ? definition - : this.makeLoader(definition) + ? definition + : this.makeLoader(definition) this.loaders[name] = loader this.logger.info(loader) @@ -202,12 +205,13 @@ class Build extends Service implements BudBuild { name: K, definition?: Rule | RuleOptions, ): this { + this.logger.log(`build.setRule`, name) const rule = definition instanceof Rule ? definition : isFunction(definition) - ? definition(this.makeRule()) - : this.makeRule(definition as any) + ? definition(this.makeRule()) + : this.makeRule(definition as any) this.rules[name] = rule this.logger.info(rule) diff --git a/sources/@roots/bud-cache/src/service.ts b/sources/@roots/bud-cache/src/service.ts index 7d31e9f342..b146571bdc 100644 --- a/sources/@roots/bud-cache/src/service.ts +++ b/sources/@roots/bud-cache/src/service.ts @@ -13,7 +13,6 @@ import {join} from 'node:path' import {isBuildDependency} from '@roots/bud-cache/helpers' import {Service} from '@roots/bud-framework/service' import {bind} from '@roots/bud-support/decorators/bind' -import isString from '@roots/bud-support/lodash/isString' /** * {@link Bud.cache} @@ -53,9 +52,7 @@ export default class Cache extends Service implements BudCache { .map(({path}) => path), ].filter(Boolean), ) - const records = { - bud: [...dependencies], - } + const records = {bud: [...dependencies]} return ( this.app.hooks.filter(`build.cache.buildDependencies`, records) ?? @@ -106,14 +103,13 @@ export default class Cache extends Service implements BudCache { /** * {@link BudCache.type} */ - public get type(): 'filesystem' | 'memory' { - const fallback = isString(this.app.context.cache) - ? this.app.context.cache - : `filesystem` - - return this.app.hooks.filter(`build.cache.type`) ?? fallback + public get type(): `filesystem` | `memory` { + return ( + this.app.hooks.filter(`build.cache.type`, `filesystem`) ?? + `filesystem` + ) } - public set type(type: Callback) { + public set type(type: Callback<`filesystem` | `memory`>) { this.app.hooks.on(`build.cache.type`, type) } @@ -130,6 +126,7 @@ export default class Cache extends Service implements BudCache { /** * {@link Service.boot} */ + @bind public override async boot?(bud: Bud) { if (bud.context.force === true) { await this.flush() @@ -143,7 +140,12 @@ export default class Cache extends Service implements BudCache { */ public get configuration(): Configuration[`cache`] { if (this.enabled !== true) return false - if (this.type === `memory`) return true + if (this.type === `memory`) + return { + cacheUnaffected: true, + maxGenerations: Infinity, + type: `memory`, + } return { allowCollectingMemory: this.allowCollectingMemory, @@ -154,6 +156,7 @@ export default class Cache extends Service implements BudCache { idleTimeout: 100, idleTimeoutForInitialStore: 0, managedPaths: [this.cacheDirectory, this.app.path(`@modules`)], + maxMemoryGenerations: Infinity, name: this.name, profile: this.app.context.debug === true, store: `pack`, @@ -197,6 +200,7 @@ export default class Cache extends Service implements BudCache { /** * {@link BudCache.register} */ + @bind public override async register?(bud: Bud) { this.enabled = bud.context.cache !== false this.version = bud.context.bud.version @@ -238,7 +242,7 @@ export default class Cache extends Service implements BudCache { /** * Set {@link BudCache.type} */ - public setType(type: Callback) { + public setType(type: `filesystem` | `memory`) { this.type = type return this } diff --git a/sources/@roots/bud-client/src/hot/client.ts b/sources/@roots/bud-client/src/hot/client.ts index 159592560b..191739e2ad 100644 --- a/sources/@roots/bud-client/src/hot/client.ts +++ b/sources/@roots/bud-client/src/hot/client.ts @@ -83,15 +83,18 @@ export const client = async ( options.reload && window.location.reload() } + const onAccepted = (info: __WebpackModuleApi.HotNotifierInfo) => { + window.bud.controllers.map( + controller => controller?.update({action: `sync`, errors: []}), + ) + } + /** * Webpack HMR error handler */ const onErrored = (error: any) => { window.bud.controllers.map( - controller => - controller?.update({ - errors: [error], - }), + controller => controller?.update({errors: [error]}), ) } @@ -104,6 +107,7 @@ export const client = async ( ignoreDeclined: true, ignoreErrored: true, ignoreUnaccepted: true, + onAccepted, onDeclined: onUnacceptedOrDeclined, onErrored, onUnaccepted: onUnacceptedOrDeclined, diff --git a/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts b/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts index 41a43ec020..b936eb9d4f 100644 --- a/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts +++ b/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts @@ -50,6 +50,8 @@ export class Component extends HTMLElement { .querySelector(this.selector) .classList.remove(`warning`, `success`, `pending`) this.shadowRoot.querySelector(this.selector).classList.add(`error`) + + this.hide() } /** * Status is pending @@ -90,23 +92,20 @@ export class Component extends HTMLElement { .classList.remove(`error`, `success`, `pending`) this.shadowRoot.querySelector(this.selector).classList.add(`warning`) + + this.hide() } public attributeChangedCallback() { if (this.hasAttribute(`has-errors`)) return this.onError() if (this.hasAttribute(`has-warnings`)) return this.onWarning() - if ( - !this.hasAttribute(`has-errors`) && - !this.hasAttribute(`has-warnings`) && - this.getAttribute(`action`) === `built` - ) - return this.onSuccess() - if ( this.getAttribute(`action`) == `building` || this.getAttribute(`action`) == `sync` ) - return this.onPending() + return this.onSuccess() + + this.onPending() } /** diff --git a/sources/@roots/bud-client/src/hot/components/indicator/indicator.controller.ts b/sources/@roots/bud-client/src/hot/components/indicator/indicator.controller.ts index dcd568ce00..a2695f485e 100644 --- a/sources/@roots/bud-client/src/hot/components/indicator/indicator.controller.ts +++ b/sources/@roots/bud-client/src/hot/components/indicator/indicator.controller.ts @@ -49,15 +49,9 @@ export class Controller { * Update activity indicator */ public update(payload: Payload) { - this.node.toggleAttribute( - `has-errors`, - payload.errors?.length ? true : false, - ) + this.node.toggleAttribute(`has-errors`, payload.errors?.length > 0) - this.node.toggleAttribute( - `has-warnings`, - payload.warnings?.length ? true : false, - ) + this.node.toggleAttribute(`has-warnings`, payload.warnings?.length > 0) this.node.setAttribute(`action`, payload.action) diff --git a/sources/@roots/bud-client/src/types/index.d.ts b/sources/@roots/bud-client/src/types/index.d.ts index 6f6e794bb9..78508afea5 100644 --- a/sources/@roots/bud-client/src/types/index.d.ts +++ b/sources/@roots/bud-client/src/types/index.d.ts @@ -16,7 +16,7 @@ declare interface Events { declare interface Payload { name: string type: `middleware` | __WebpackModuleApi.HotNotifierInfo[`type`] - action: 'reload' | 'sync' | 'building' | 'built' + action?: 'reload' | 'sync' | 'building' | 'built' hash?: string time?: number errors?: Array> diff --git a/sources/@roots/bud-compiler/src/service/index.tsx b/sources/@roots/bud-compiler/src/service/index.tsx index 6db984b83b..51cc11364e 100644 --- a/sources/@roots/bud-compiler/src/service/index.tsx +++ b/sources/@roots/bud-compiler/src/service/index.tsx @@ -17,10 +17,10 @@ import {cpus} from 'node:os' import process from 'node:process' import {pathToFileURL} from 'node:url' -import {Error as DisplayError} from '@roots/bud-dashboard/components/error' +import {Display as DisplayError} from '@roots/bud-dashboard/components/error' import {Service} from '@roots/bud-framework/service' import {bind} from '@roots/bud-support/decorators/bind' -import {BudError} from '@roots/bud-support/errors' +import {BudError, CompilerError} from '@roots/bud-support/errors' import isNull from '@roots/bud-support/lodash/isNull' import isNumber from '@roots/bud-support/lodash/isNumber' import isString from '@roots/bud-support/lodash/isString' @@ -54,26 +54,27 @@ class Compiler extends Service implements BudCompiler { * {@link BudCompiler.onError} */ @bind - public onError(error: Error) { + public onError(error: Error | undefined) { process.exitCode = 1 if (!error) return this.app.server?.appliedMiddleware?.hot?.publish({error}) + const normalized = CompilerError.normalize(error, { + thrownBy: import.meta.url, + }) + + normalized.details = undefined + this.app.notifier?.notify({ group: this.app.label, - message: error.message, - subtitle: error.name, + message: normalized.message, + subtitle: normalized.name, }) - if (`isBudError` in error) { - this.app.context.render() - } else { - this.app.context.render( - , - ) - } + this.app.context.render() } + /** * {@link BudCompiler.onStats} */ @@ -152,17 +153,20 @@ class Compiler extends Service implements BudCompiler { const config = !bud.hasChildren ? [await bud.build.make()] : await Promise.all( - Object.values(bud.children).map(async (child: Bud) => - child.build.make().catch(error => { - throw error - }), + Object.values(bud.children).map( + async (child: Bud) => + await child.build.make().catch(error => { + throw error + }), ), ) this.config = config?.filter(Boolean) - this.config.parallelism = Math.max(cpus().length - 1, 1) - this.logger.info(`parallel compilations: ${this.config.parallelism}`) + if (this.config.length > 1) { + this.config.parallelism = Math.max(cpus().length - 1, 1) + this.logger.info(`parallel compilations: ${this.config.parallelism}`) + } await bud.hooks.fire(`compiler.before`, bud).catch(error => { throw error @@ -170,17 +174,13 @@ class Compiler extends Service implements BudCompiler { this.logger.timeEnd(`initialize`) - try { - this.instance = this.implementation(this.config) - } catch (error: unknown) { - const normalError = - error instanceof Error ? error : BudError.normalize(error) - this.onError(normalError) - } + this.instance = this.implementation(this.config) this.instance.hooks.done.tap(bud.label, (stats: any) => { this.onStats(stats) - bud.hooks.fire(`compiler.done`, bud, this.stats).catch(this.onError) + bud.hooks + .fire(`compiler.done`, bud, this.stats) + .catch(this.app.catch) }) return this.instance diff --git a/sources/@roots/bud-dashboard/src/components/error.tsx b/sources/@roots/bud-dashboard/src/components/error.tsx index 44d0c9a5da..65be1b2cb9 100644 --- a/sources/@roots/bud-dashboard/src/components/error.tsx +++ b/sources/@roots/bud-dashboard/src/components/error.tsx @@ -20,9 +20,7 @@ const cleanErrorObject = (error: RawError): BudError => { export const Error = ({error: input}: {error: RawError}): ReactNode => { return ( - {(_, key) => ( - - )} + {(_, key) => } ) } @@ -31,10 +29,9 @@ export const Display = ({error: input}: {error: RawError}) => { const error = cleanErrorObject(input) return ( - + {error.name && ( - {figures.cross} {error.name} @@ -42,100 +39,96 @@ export const Display = ({error: input}: {error: RawError}) => { )} {error.message && ( - - {error.message} + + + {figures.cross} + {error.message} + )} {error.details && !error.details.startsWith(`resolve`) && ( - - - - {figures.ellipsis} - {` `}Details{` `} - + + + {figures.ellipsis} + Details + + {error.details} + + )} - {error.details} - + {error.origin && ( + + + {error.origin.message && {error.origin.message}} + + + )} + + {error.instance && ( + + + {figures.ellipsis} + Instance + {error.instance} + )} {error.thrownBy && ( - - - {figures.ellipsis} - {` `}Thrown by{` `} - - {error.thrownBy} + + + {figures.ellipsis} + Thrown by + {error.thrownBy.toString()} + )} {error.docs && ( - - - - {figures.arrowRight} - {` `}Documentation{` `} - + + + {figures.ellipsis} + Documentation {error.docs.href} - + )} - {error.issues && ( - + {error.issue?.href && ( + - - {figures.arrowRight} - {` `} - Issues - - {` `} - {error.issues.href} + {figures.ellipsis} + Issue + {error.issue.href} )} - {error.file && ( - - - {figures.info} - {` `}See file{` `} - + {error.file?.path && ( + + {figures.info} + See file {error.file.path} )} - {error.origin && - !(error.origin instanceof BudError) && - error.stack && ( - - - {figures.home} - {` `}Stack trace{` `} - - - - {error.stack} - + {error.stack && ( + + + {figures.info} + Stack trace - )} - - {error.origin && error.origin instanceof BudError && error.stack && ( - - - {figures.home} - {` `}Originating error{` `} - - { borderRight={false} borderStyle="single" borderTop={false} - flexDirection="column" paddingLeft={1} > - - {error.origin.message} - {`\n`} - - {error.origin.stack && ( - {error.origin.stack} - )} + {error.stack} )} diff --git a/sources/@roots/bud-entrypoints/package.json b/sources/@roots/bud-entrypoints/package.json index dee314b5db..60c8bc826a 100644 --- a/sources/@roots/bud-entrypoints/package.json +++ b/sources/@roots/bud-entrypoints/package.json @@ -48,22 +48,12 @@ ], "type": "module", "exports": { - ".": { - "import": "./lib/index.js", - "default": "./lib/index.js" - }, - "./types": { - "import": "./lib/types.js", - "default": "./lib/types.js" - } + ".": "./lib/index.js" }, "typesVersions": { "*": { ".": [ "./lib/index.d.ts" - ], - "types": [ - "./lib/types.d.ts" ] } }, diff --git a/sources/@roots/bud-entrypoints/src/extension.ts b/sources/@roots/bud-entrypoints/src/extension.ts index 3b509fb0e7..0f2f432cb0 100644 --- a/sources/@roots/bud-entrypoints/src/extension.ts +++ b/sources/@roots/bud-entrypoints/src/extension.ts @@ -18,7 +18,9 @@ import {EntrypointsWebpackPlugin} from '@roots/entrypoints-webpack-plugin' type: `object`, }) @plugin(EntrypointsWebpackPlugin) -export class BudEntrypoints extends Extension< +class BudEntrypoints extends Extension< Options, EntrypointsWebpackPlugin > {} + +export default BudEntrypoints diff --git a/sources/@roots/bud-entrypoints/src/index.ts b/sources/@roots/bud-entrypoints/src/index.ts index 9865ee3fab..180ef2fe67 100644 --- a/sources/@roots/bud-entrypoints/src/index.ts +++ b/sources/@roots/bud-entrypoints/src/index.ts @@ -8,7 +8,17 @@ * @see https://github.com/roots/bud */ -import {BudEntrypoints} from './extension.js' -import './types.js' +import type {PublicExtensionApi} from '@roots/bud-framework/extension' + +import BudEntrypoints from './extension.js' + +declare module '@roots/bud-framework' { + interface Bud { + entrypoints: PublicExtensionApi + } + interface Modules { + '@roots/bud-entrypoints': BudEntrypoints + } +} export default BudEntrypoints diff --git a/sources/@roots/bud-entrypoints/src/types.ts b/sources/@roots/bud-entrypoints/src/types.ts deleted file mode 100644 index 74541ff8a2..0000000000 --- a/sources/@roots/bud-entrypoints/src/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {PublicExtensionApi} from '@roots/bud-framework/extension' - -import type {BudEntrypoints} from './extension.js' - -declare module '@roots/bud-framework' { - interface Bud { - entrypoints: PublicExtensionApi - } - interface Modules { - '@roots/bud-entrypoints': BudEntrypoints - } -} diff --git a/sources/@roots/bud-entrypoints/src/extension.test.ts b/sources/@roots/bud-entrypoints/test/extension.test.ts similarity index 79% rename from sources/@roots/bud-entrypoints/src/extension.test.ts rename to sources/@roots/bud-entrypoints/test/extension.test.ts index 247ec11722..74bfbdd69c 100644 --- a/sources/@roots/bud-entrypoints/src/extension.test.ts +++ b/sources/@roots/bud-entrypoints/test/extension.test.ts @@ -1,7 +1,6 @@ +import Extension from '@roots/bud-entrypoints' import {describe, expect, it} from 'vitest' -import Extension from './index.js' - describe(`@roots/bud-entrypoints`, () => { it(`should be constructable`, () => { expect(Extension).toBeInstanceOf(Function) diff --git a/sources/@roots/bud-esbuild/README.md b/sources/@roots/bud-esbuild/README.md index 4dd5d11419..098c23d0e2 100644 --- a/sources/@roots/bud-esbuild/README.md +++ b/sources/@roots/bud-esbuild/README.md @@ -43,7 +43,7 @@ If you have a `tsconfig.json` in your project root it will automatically be regi If you have one or more compilers installed alongside the esbuild extension you will want to call **bud.esbuild.use** in your config to ensure esbuild is used to compile your code. -```ts title=bud.config.js +```ts title=bud.config.ts export default async (bud) => { bud.esbuild.use(); // ...config diff --git a/sources/@roots/bud-esbuild/docs/02-api.md b/sources/@roots/bud-esbuild/docs/02-api.md index d9e64f08bd..c98d9b215b 100644 --- a/sources/@roots/bud-esbuild/docs/02-api.md +++ b/sources/@roots/bud-esbuild/docs/02-api.md @@ -7,7 +7,7 @@ title: API If you have one or more compilers installed alongside the esbuild extension you will want to call **bud.esbuild.use** in your config to ensure esbuild is used to compile your code. -```ts title=bud.config.js +```ts title=bud.config.ts export default async (bud) => { bud.esbuild.use() // ...config diff --git a/sources/@roots/bud-eslint/README.md b/sources/@roots/bud-eslint/README.md index 6835eb4322..96df614b7f 100644 --- a/sources/@roots/bud-eslint/README.md +++ b/sources/@roots/bud-eslint/README.md @@ -54,9 +54,9 @@ export default { ### Configuring eslint with `bud.eslint` -You can configure eslint directly in `bud.config.js` using the `bud.eslint` API. +You can configure eslint directly in your bud.js config using the `bud.eslint` API. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint .extends([`@roots/eslint-config`]) .setRules({ "no-console": `error` }) @@ -69,7 +69,7 @@ bud.eslint You can extend a eslint config by passing an array of eslint config files to `bud.eslint.extends`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.extends([`@roots/bud-eslint/config`]); ``` @@ -77,7 +77,7 @@ bud.eslint.extends([`@roots/bud-eslint/config`]); You can set eslint rules by passing an object to `bud.eslint.setRules`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setRules({ "no-descending-specificity": null }); ``` @@ -86,7 +86,7 @@ bud.eslint.setRules({ "no-descending-specificity": null }); By default, eslint will fail on error in production mode. You can change this behavior by setting `bud.eslint.failOnError` to `false`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setFailOnError(false); ``` @@ -95,7 +95,7 @@ bud.eslint.setFailOnError(false); By default, eslint will not fail on warning. You can change this behavior by setting `bud.eslint.failOnWarning` to `true`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setFailOnWarning(true); ``` @@ -104,7 +104,7 @@ bud.eslint.setFailOnWarning(true); By default, eslint will not fix errors. You can change this behavior by setting `bud.eslint.fix` to `true`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setFix(true); ``` diff --git a/sources/@roots/bud-eslint/docs/01-configuration.md b/sources/@roots/bud-eslint/docs/01-configuration.md index b716ef4125..eb904ea99f 100644 --- a/sources/@roots/bud-eslint/docs/01-configuration.md +++ b/sources/@roots/bud-eslint/docs/01-configuration.md @@ -24,9 +24,9 @@ export default { ### Configuring eslint with `bud.eslint` -You can configure eslint directly in `bud.config.js` using the `bud.eslint` API. +You can configure eslint directly in your bud.js config using the `bud.eslint` API. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint .extends([`@roots/eslint-config`]) .setRules({'no-console': `error`}) @@ -39,7 +39,7 @@ bud.eslint You can extend a eslint config by passing an array of eslint config files to `bud.eslint.extends`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.extends([`@roots/bud-eslint/config`]) ``` @@ -47,7 +47,7 @@ bud.eslint.extends([`@roots/bud-eslint/config`]) You can set eslint rules by passing an object to `bud.eslint.setRules`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setRules({'no-descending-specificity': null}) ``` @@ -56,7 +56,7 @@ bud.eslint.setRules({'no-descending-specificity': null}) By default, eslint will fail on error in production mode. You can change this behavior by setting `bud.eslint.failOnError` to `false`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setFailOnError(false) ``` @@ -65,7 +65,7 @@ bud.eslint.setFailOnError(false) By default, eslint will not fail on warning. You can change this behavior by setting `bud.eslint.failOnWarning` to `true`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setFailOnWarning(true) ``` @@ -74,7 +74,7 @@ bud.eslint.setFailOnWarning(true) By default, eslint will not fix errors. You can change this behavior by setting `bud.eslint.fix` to `true`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.eslint.setFix(true) ``` diff --git a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx index 7838525365..6954a50867 100644 --- a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx +++ b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx @@ -1,5 +1,6 @@ import BudCommand from '@roots/bud/cli/commands' import {Option} from '@roots/bud-support/clipanion' +import {bind} from '@roots/bud-support/decorators/bind' /** * {@link BudCommand} @@ -24,6 +25,7 @@ export class BudEslintCommand extends BudCommand { /** * {@link BudCommand.execute} */ + @bind public override async execute() { await this.makeBud() await this.bud.run() diff --git a/sources/@roots/bud-extensions/src/cdn/index.ts b/sources/@roots/bud-extensions/src/cdn/index.ts index c577750ba6..10f7a00288 100644 --- a/sources/@roots/bud-extensions/src/cdn/index.ts +++ b/sources/@roots/bud-extensions/src/cdn/index.ts @@ -112,8 +112,8 @@ export default class Cdn extends Extension implements Api { ...(!rule.include ? [bud.path()] : Array.isArray(rule.include) - ? rule.include - : []), + ? rule.include + : []), ...Array.from(this.allowedUris ?? []), ]) }) diff --git a/sources/@roots/bud-extensions/src/clean-webpack-plugin/index.ts b/sources/@roots/bud-extensions/src/clean-webpack-plugin/index.ts index 42c2da7ddf..cdbfce1480 100644 --- a/sources/@roots/bud-extensions/src/clean-webpack-plugin/index.ts +++ b/sources/@roots/bud-extensions/src/clean-webpack-plugin/index.ts @@ -3,16 +3,19 @@ import type {Options} from '@roots/bud-support/clean-webpack-plugin' import {Extension} from '@roots/bud-framework/extension' import { + expose, label, options, plugin, } from '@roots/bud-framework/extension/decorators' import {Plugin} from '@roots/bud-support/clean-webpack-plugin' +import isUndefined from '@roots/bud-support/lodash/isUndefined' /** * Clean webpack plugin configuration */ @label(`@roots/bud-extensions/clean-webpack-plugin`) +@expose(`clean`) @plugin(Plugin) @options({ /** @@ -36,8 +39,7 @@ export default class BudClean extends Extension { */ public override when(bud: Bud) { if (this.enabled === false) return false - if (bud.context.clean === true) return true - if (bud.context.clean === false) return false + if (!isUndefined(bud.context.clean)) return bud.context.clean return bud.isProduction } } diff --git a/sources/@roots/bud-extensions/src/html-webpack-plugin/index.ts b/sources/@roots/bud-extensions/src/html-webpack-plugin/index.ts index bf4c4d5caa..0125bb33f7 100644 --- a/sources/@roots/bud-extensions/src/html-webpack-plugin/index.ts +++ b/sources/@roots/bud-extensions/src/html-webpack-plugin/index.ts @@ -23,45 +23,20 @@ import { filename: `index.html`, inject: true, publicPath: DynamicOption.make(app => app.publicPath()), - template: undefined, + template: resolve( + dirname(fileURLToPath(import.meta.url)), + `..`, + `..`, + `vendor`, + `template.html`, + ), }) @disabled class BudHtmlWebpackPlugin extends Extension { - /** - * {@link Extension.configAfter} - */ - public override async configAfter(bud: Bud) { - if (bud.context.html) { - this.enabled = true - bud.extensions - .get(`@roots/bud-extensions/interpolate-html-webpack-plugin`) - .enable() - } - - if (bud.context.html === false) { - this.enabled = false - bud.extensions - .get(`@roots/bud-extensions/interpolate-html-webpack-plugin`) - .enable(false) - return bud - } - - if (typeof bud.context.html === `string`) { - this.set(`template`, bud.path(bud.context.html)) - } - - if (!this.options.template) { - this.set( - `template`, - resolve( - dirname(fileURLToPath(import.meta.url)), - `..`, - `..`, - `vendor`, - `template.html`, - ), - ) - } + public get interpolatePlugin() { + return this.app.extensions.get( + `@roots/bud-extensions/interpolate-html-webpack-plugin`, + ) } /** diff --git a/sources/@roots/bud-extensions/src/index.ts b/sources/@roots/bud-extensions/src/index.ts index ce9b035b52..db6766e63b 100644 --- a/sources/@roots/bud-extensions/src/index.ts +++ b/sources/@roots/bud-extensions/src/index.ts @@ -9,7 +9,7 @@ */ import type BudCDN from '@roots/bud-extensions/cdn' -import type CleanWebpackPlugin from '@roots/bud-extensions/clean-webpack-plugin' +import type BudClean from '@roots/bud-extensions/clean-webpack-plugin' import type CopyWebpackPlugin from '@roots/bud-extensions/copy-webpack-plugin' import type BudESM from '@roots/bud-extensions/esm' import type BudFixStyleOnlyEntrypoints from '@roots/bud-extensions/fix-style-only-entrypoints' @@ -29,6 +29,7 @@ import {default as Service} from '@roots/bud-extensions/service' declare module '@roots/bud-framework' { interface Bud { cdn: BudCDN + clean: BudClean esm: BudESM manifest: WebpackManifestPlugin tsconfig: BudTsConfigValues @@ -36,7 +37,7 @@ declare module '@roots/bud-framework' { interface Modules { '@roots/bud-extensions/cdn': BudCDN - '@roots/bud-extensions/clean-webpack-plugin': CleanWebpackPlugin + '@roots/bud-extensions/clean-webpack-plugin': BudClean '@roots/bud-extensions/copy-webpack-plugin': CopyWebpackPlugin '@roots/bud-extensions/esm': BudESM '@roots/bud-extensions/fix-style-only-entrypoints': BudFixStyleOnlyEntrypoints diff --git a/sources/@roots/bud-extensions/src/interpolate-html-webpack-plugin/index.ts b/sources/@roots/bud-extensions/src/interpolate-html-webpack-plugin/index.ts index 6a7bda8525..c20c9aa608 100644 --- a/sources/@roots/bud-extensions/src/interpolate-html-webpack-plugin/index.ts +++ b/sources/@roots/bud-extensions/src/interpolate-html-webpack-plugin/index.ts @@ -30,7 +30,7 @@ class BudInterpolateHtmlExtension extends Extension< * {@link Extension.make} */ @bind - public override async make(bud: Bud) { + public override async make(bud: Bud, options: Options) { const InterpolateHtmlWebpackPlugin = await bud.module.import( `@roots/bud-extensions/interpolate-html-webpack-plugin/plugin`, import.meta.url, @@ -42,7 +42,7 @@ class BudInterpolateHtmlExtension extends Extension< ) return new InterpolateHtmlWebpackPlugin(getHooks, { - ...(this.options ?? {}), + ...(options ?? {}), ...(bud.extensions.get(`@roots/bud-extensions/webpack-define-plugin`) ?.options ?? {}), ...(bud.env.getPublicEnv() ?? {}), diff --git a/sources/@roots/bud-extensions/src/service/index.ts b/sources/@roots/bud-extensions/src/service/index.ts index 419f1bdfa5..1e658316ff 100644 --- a/sources/@roots/bud-extensions/src/service/index.ts +++ b/sources/@roots/bud-extensions/src/service/index.ts @@ -13,7 +13,6 @@ import {Extension} from '@roots/bud-framework/extension' import {Service} from '@roots/bud-framework/service' import {bind} from '@roots/bud-support/decorators/bind' import {ExtensionError} from '@roots/bud-support/errors' -import isFunction from '@roots/bud-support/lodash/isFunction' import isUndefined from '@roots/bud-support/lodash/isUndefined' import Container from '@roots/container' @@ -85,31 +84,31 @@ class Extensions extends Service implements BudExtensions { await arrayed.reduce(async (promised, item) => { await promised - const moduleObject = + const source = typeof item === `string` - ? await import(item).then(pkg => pkg.default ?? pkg) + ? await this.app.module + .import( + item.startsWith(`./`) ? this.app.path(item) : item, + import.meta.url, + ) + .then(pkg => pkg.default ?? pkg) : item - const extension = await this.instantiate(moduleObject) + const extension = await this.instantiate(source) this.set(extension) + this.logger.log(`added`, extension.label) await this.run(extension, `register`) await this.run(extension, `boot`) }, Promise.resolve()) } - /** - * {@link BudExtensions.boot} - */ - public override async boot?(bud: Bud) { - await this.runAll(`boot`) - } - /** * {@link BudExtensions.bootstrap} */ - public override async bootstrap?(bud: Bud) { + @bind + public async configBefore?(bud: Bud) { handleManifestSchemaWarning.bind(this)(bud) const {extensions, manifest} = bud.context @@ -183,11 +182,15 @@ class Extensions extends Service implements BudExtensions { await this.import(signifier, true), ), ) + + await this.runAll(`register`) + await this.runAll(`boot`) } /** * {@link BudExtensions.buildBefore} */ + @bind public override async buildAfter?(bud: Bud) { await this.runAll(`buildAfter`) } @@ -195,6 +198,7 @@ class Extensions extends Service implements BudExtensions { /** * {@link BudExtensions.buildBefore} */ + @bind public override async buildBefore?(bud: Bud) { await this.runAll(`buildBefore`) } @@ -202,6 +206,7 @@ class Extensions extends Service implements BudExtensions { /** * {@link BudExtensions.compilerDone} */ + @bind public override async compilerDone?(bud, stats) { await this.runAll(`compilerDone`) } @@ -209,6 +214,7 @@ class Extensions extends Service implements BudExtensions { /** * {@link BudExtensions.configAfter} */ + @bind public override async configAfter?(bud: Bud) { await this.runAll(`configAfter`) } @@ -242,7 +248,7 @@ class Extensions extends Service implements BudExtensions { if (signifier.startsWith(`.`)) { signifier = this.app.path(signifier) - this.logger.info(`path resolved to`, signifier) + this.logger.info(`local path interpretation:`, signifier) } if (this.has(signifier)) { @@ -309,11 +315,21 @@ class Extensions extends Service implements BudExtensions { if (source instanceof Extension) return source if (isConstructor(source)) { - return new source(this.app) + const instance = new source(this.app) + if (!instance.label) + instance.label = + instance.constructor.name ?? + (randomUUID() as keyof Modules & string) + return instance } if (typeof source === `function`) { - return source(this.app) + const instance = source(this.app) + if (!instance.label) + instance.label = + instance.constructor.name ?? + (randomUUID() as keyof Modules & string) + return instance } if (typeof source.apply === `function`) { @@ -329,12 +345,13 @@ class Extensions extends Service implements BudExtensions { } instance[k] = v }) + if (!instance.label) + instance.label = randomUUID() as keyof Modules & string return instance } - return new source() + return new source(this.app) } - /** * {@link BudExtensions.isAllowed} */ @@ -356,21 +373,23 @@ class Extensions extends Service implements BudExtensions { */ @bind public async make(): Promise { - return await Promise.all( - Object.values(this.repository).map(async extension => - extension.apply ? extension : await extension._make(), - ), + const results = await Promise.all( + Object.entries(this.repository).map(async ([label, extension]) => { + return [label, await extension.execute(`make`)] + }), ).then( - (result: Array): Array => - result.filter(Boolean), + (results): Array => + results + .filter(([_label, result]) => result) + .map(([label, result]) => { + this.logger.log(`defined compiler plugin:`, label).info(result) + return result + }), ) - } - /** - * {@link BudExtensions.register} - */ - public override async register?(bud: Bud) { - await this.runAll(`register`) + this.logger.log(`using`, results.length, `compiler plugins`) + + return results } /** @@ -386,10 +405,10 @@ class Extensions extends Service implements BudExtensions { * Run an extension lifecycle method * * @remarks - * - `_register` - * - `_boot` - * - `_buildBefore` - * - `_make` + * - `register` + * - `boot` + * - `buildBefore` + * - `make` */ @bind public async run( @@ -398,9 +417,7 @@ class Extensions extends Service implements BudExtensions { ): Promise { try { await this.runDependencies(extension, methodName) - const method = extension[`_${methodName}`] - if (isFunction(method)) await method() - await this.app.promise() + if (extension.execute) await extension.execute(methodName) return this } catch (error) { @@ -445,11 +462,7 @@ class Extensions extends Service implements BudExtensions { await promised if (!this.has(signifier)) await this.import(signifier, true) - if ( - this.get(signifier) && - !this.get(signifier).meta?.[methodName] - ) - await this.run(this.get(signifier), methodName) + await this.run(this.get(signifier), methodName) }, Promise.resolve()) } @@ -465,11 +478,7 @@ class Extensions extends Service implements BudExtensions { return } - if ( - this.get(signifier) && - !this.get(signifier).meta?.[methodName] - ) - await this.run(this.get(signifier), methodName) + await this.run(this.get(signifier), methodName) }, Promise.resolve()) } diff --git a/sources/@roots/bud-extensions/src/tsconfig-values/index.ts b/sources/@roots/bud-extensions/src/tsconfig-values/index.ts index 8b83965aaa..91598b88c1 100644 --- a/sources/@roots/bud-extensions/src/tsconfig-values/index.ts +++ b/sources/@roots/bud-extensions/src/tsconfig-values/index.ts @@ -143,6 +143,13 @@ export default class BudTsConfigValues */ public declare setInclude: Api['setInclude'] + private get derivedBaseDir(): string | undefined { + return ( + this.getCompilerOptions()?.rootDir ?? + this.getCompilerOptions()?.baseUrl + ) + } + /** * The `configAfter` method adjusts the bud.js application * configuration by setting up paths and determining file inclusion and exclusion @@ -150,6 +157,7 @@ export default class BudTsConfigValues * * {@link Extension.configAfter} */ + @bind public override async configAfter(bud: Bud) { if (!this.isEnabled()) return @@ -216,14 +224,6 @@ export default class BudTsConfigValues bud.compilePaths(directories.map(dir => bud.path(dir))) } } - - public get derivedBaseDir(): string | undefined { - return ( - this.getCompilerOptions()?.rootDir ?? - this.getCompilerOptions()?.baseUrl - ) - } - /** * Make absolute path * @@ -231,7 +231,7 @@ export default class BudTsConfigValues * @returns string */ @bind - public makeAbsolute(path: string): string { + private makeAbsolute(path: string): string { return isAbsolute(path) ? path : this.app.path(path) } @@ -245,7 +245,7 @@ export default class BudTsConfigValues * @returns */ @bind - public normalizePaths( + private normalizePaths( paths: Record>, ): Record | undefined { if (!paths) return @@ -272,6 +272,7 @@ export default class BudTsConfigValues /** * {@link Extension.register} */ + @bind public override async register(bud: Bud) { const fetchConfigModule = bud.context.files[`tsconfig`]?.module ?? diff --git a/sources/@roots/bud-extensions/src/webpack-hot-module-replacement-plugin/index.ts b/sources/@roots/bud-extensions/src/webpack-hot-module-replacement-plugin/index.ts index 5f6a43ac7a..6222640244 100644 --- a/sources/@roots/bud-extensions/src/webpack-hot-module-replacement-plugin/index.ts +++ b/sources/@roots/bud-extensions/src/webpack-hot-module-replacement-plugin/index.ts @@ -1,21 +1,29 @@ import type {Bud} from '@roots/bud-framework' import {Extension} from '@roots/bud-framework/extension' -import {label, plugin} from '@roots/bud-framework/extension/decorators' -import { - type HotModuleReplacementPlugin, - default as Webpack, -} from '@roots/bud-support/webpack' +import {label} from '@roots/bud-framework/extension/decorators' +import {type HotModuleReplacementPlugin} from '@roots/bud-support/webpack' /** * Hot module replacement plugin configuration */ @label(`@roots/bud-extensions/webpack-hot-module-replacement-plugin`) -@plugin(Webpack.HotModuleReplacementPlugin) export default class BudHMR extends Extension< NonNullable, HotModuleReplacementPlugin > { + /** + * {@link Extension.make} + */ + public override async make(bud: Bud) { + const webpack = await bud.module.import( + `@roots/bud-support/webpack`, + import.meta.url, + ) + return new webpack.HotModuleReplacementPlugin() + + } + /** * {@link Extension.when} */ diff --git a/sources/@roots/bud-extensions/test/extensions/webpack-hot-module-replacement.test.ts b/sources/@roots/bud-extensions/test/extensions/webpack-hot-module-replacement.test.ts index 656fdc41a0..c2aa89a96c 100644 --- a/sources/@roots/bud-extensions/test/extensions/webpack-hot-module-replacement.test.ts +++ b/sources/@roots/bud-extensions/test/extensions/webpack-hot-module-replacement.test.ts @@ -1,10 +1,9 @@ import {factory} from '@repo/test-kit' +import HmrExtension from '@roots/bud-extensions/webpack-hot-module-replacement-plugin' import {Extension} from '@roots/bud-framework/extension' import webpack from '@roots/bud-support/webpack' import {describe, expect, it, test, vi} from 'vitest' -import HmrExtension from '@roots/bud-extensions/webpack-hot-module-replacement-plugin' - describe(`webpack-hot-module-replacement-plugin`, () => { it(`is an instance of Extension`, () => { expect(HmrExtension).toBeInstanceOf(Function) @@ -23,26 +22,25 @@ describe(`webpack-hot-module-replacement-plugin`, () => { // @ts-ignore const extension = new HmrExtension(bud) expect(bud.mode).toBe(`production`) - expect(await extension.isEnabled()).toBe(false) + expect(extension.isEnabled()).toBe(false) }) - it(`is enabled in development`, async () => { + it(`should be enabled in development`, async () => { vi.restoreAllMocks() const bud = await factory({mode: `development`}) expect(bud.mode).toBe(`development`) - // @ts-ignore const extension = new HmrExtension(bud) - expect(await extension.isEnabled()).toBe(true) + expect(extension.isEnabled()).toBe(true) }) - it(`produces webpack hmr plugin`, async () => { + it(`should produce webpack hmr plugin`, async () => { const bud = await factory({mode: `development`}) // @ts-ignore const extension = new HmrExtension(bud) - expect(extension.plugin).toBe(webpack.HotModuleReplacementPlugin) + expect((await extension.make(bud)).constructor.name).toBe(`HotModuleReplacementPlugin`) }) test.todo(`should be tested`) diff --git a/sources/@roots/bud-extensions/test/service/__snapshots__/index.test.ts.snap b/sources/@roots/bud-extensions/test/service/__snapshots__/index.test.ts.snap index 41a4db5517..77210befb4 100644 --- a/sources/@roots/bud-extensions/test/service/__snapshots__/index.test.ts.snap +++ b/sources/@roots/bud-extensions/test/service/__snapshots__/index.test.ts.snap @@ -1,3 +1,29 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`@roots/bud-extensions > bud.extensions.repository options should match snapshot in development 1`] = `[]`; +exports[`@roots/bud-extensions > bud.extensions.repository options should match snapshot in development 1`] = ` +[ + "@roots/bud-entrypoints", + "@roots/bud-extensions/cdn", + "@roots/bud-extensions/clean-webpack-plugin", + "@roots/bud-extensions/copy-webpack-plugin", + "@roots/bud-extensions/esm", + "@roots/bud-extensions/fix-style-only-entrypoints", + "@roots/bud-extensions/html-webpack-plugin", + "@roots/bud-extensions/import-map", + "@roots/bud-extensions/interpolate-html-webpack-plugin", + "@roots/bud-extensions/mini-css-extract-plugin", + "@roots/bud-extensions/tsconfig-values", + "@roots/bud-extensions/webpack-define-plugin", + "@roots/bud-extensions/webpack-hot-module-replacement-plugin", + "@roots/bud-extensions/webpack-lifecycle-plugin", + "@roots/bud-extensions/webpack-manifest-plugin", + "@roots/bud-extensions/webpack-provide-plugin", + "@roots/bud-minify", + "@roots/bud-minify/minify-css", + "@roots/bud-minify/minify-js", + "@roots/bud-postcss", + "@roots/bud-react", + "@roots/bud-swc", + "@roots/bud-tailwindcss", +] +`; diff --git a/sources/@roots/bud-extensions/test/service/index.test.ts b/sources/@roots/bud-extensions/test/service/index.test.ts index 97438cc374..7f1e37a13e 100644 --- a/sources/@roots/bud-extensions/test/service/index.test.ts +++ b/sources/@roots/bud-extensions/test/service/index.test.ts @@ -124,12 +124,9 @@ describe(`@roots/bud-extensions`, () => { const extensions = new Extensions(() => bud) if (!extensions) throw new Error(`Extensions not found`) - if (!extensions.register) - throw new Error(`Extensions.register not found`) - await extensions.register(bud) - - if (!extensions.boot) throw new Error(`Extensions.boot not found`) - await extensions.boot(bud) + if (!extensions.configBefore) + throw new Error(`Extensions.configBefore not found`) + await extensions.configBefore(bud) expect(Object.keys(extensions.repository).sort()).toMatchSnapshot() }) diff --git a/sources/@roots/bud-framework/package.json b/sources/@roots/bud-framework/package.json index bd34ae1b66..873b789ee6 100644 --- a/sources/@roots/bud-framework/package.json +++ b/sources/@roots/bud-framework/package.json @@ -52,7 +52,7 @@ "type": "module", "exports": { ".": "./lib/index.js", - "./bud": "./lib/bud.js", + "./bud": "./lib/bud/index.js", "./configuration": "./lib/configuration/index.js", "./config": "./lib/config/index.js", "./context": "./lib/context.js", diff --git a/sources/@roots/bud-framework/src/bootstrap.ts b/sources/@roots/bud-framework/src/bootstrap.ts index 34c3195a26..1d7fb86057 100644 --- a/sources/@roots/bud-framework/src/bootstrap.ts +++ b/sources/@roots/bud-framework/src/bootstrap.ts @@ -3,6 +3,7 @@ import type {Bud, BudService, Registry} from '@roots/bud-framework' import {BudError} from '@roots/bud-support/errors' import camelCase from '@roots/bud-support/lodash/camelCase' import isString from '@roots/bud-support/lodash/isString' +import logger from '@roots/bud-support/logger' import {FS} from './fs.js' import {Module} from './module.js' @@ -17,6 +18,7 @@ export const lifecycleHookHandles: Partial< `bootstrap`, `register`, `boot`, + `config.before`, `config.after`, `compiler.before`, `build.before`, @@ -33,6 +35,7 @@ export const lifecycleMethods: Array<`${keyof BudService}`> = [ `bootstrap`, `register`, `boot`, + `configBefore`, `configAfter`, `compilerBefore`, `buildBefore`, @@ -67,6 +70,7 @@ export const lifecycle = { 'compiler.before': `compilerBefore`, 'compiler.done': `compilerDone`, 'config.after': `configAfter`, + 'config.before': `configBefore`, register: `register`, 'server.after': `serverAfter`, 'server.before': `serverBefore`, @@ -186,7 +190,7 @@ export const bootstrap = async function (bud: Bud) { 'pattern.sassModule': /\.module\.(scss|sass)$/, 'pattern.svg': /\.svg$/, 'pattern.toml': /\.toml$/, - 'pattern.ts': /\.(tsx?)$/, + 'pattern.ts': /\.(m?tsx?)$/, 'pattern.vue': /\.vue$/, 'pattern.webp': /\.webp$/, 'pattern.xml': /\.xml$/, @@ -204,32 +208,15 @@ export const bootstrap = async function (bud: Bud) { .filter(Boolean) .filter(instance => callbackName in instance) .map(instance => { - bud.hooks.action( - eventHandle as any, - instance[callbackName].bind(instance), + logger.log( + `register service callback:`, + `${instance.constructor.name}.${callbackName}`, ) + bud.hooks.action(eventHandle as any, instance[callbackName]) }), ) bud.after(bud.module.after) - const initializeEvents: Array<`${keyof Registry.EventsStore}`> = [ - `bootstrap`, - `register`, - `boot`, - ] - - await initializeEvents.reduce( - async (promised: Promise, event: any) => { - try { - await promised - await bud.executeServiceCallbacks(event).catch(bud.catch) - } catch (error) { - throw error - } - }, - Promise.resolve({}), - ) - return bud } diff --git a/sources/@roots/bud-framework/src/bud.ts b/sources/@roots/bud-framework/src/bud/index.ts similarity index 71% rename from sources/@roots/bud-framework/src/bud.ts rename to sources/@roots/bud-framework/src/bud/index.ts index e6a1e595d0..a674a23505 100644 --- a/sources/@roots/bud-framework/src/bud.ts +++ b/sources/@roots/bud-framework/src/bud/index.ts @@ -21,17 +21,24 @@ import isString from '@roots/bud-support/lodash/isString' import isUndefined from '@roots/bud-support/lodash/isUndefined' import logger from '@roots/bud-support/logger' -import type {FS} from './fs.js' -import type {Module} from './module.js' -import type {Notifier} from './notifier.js' -import type {EventsStore} from './registry/index.js' +import type {FS} from '../fs.js' +import type {Module} from '../module.js' +import type {Notifier} from '../notifier.js' +import type {EventsStore} from '../registry/index.js' -import {bootstrap} from './bootstrap.js' +import {bootstrap} from '../bootstrap.js' /** * Bud core class */ export class Bud { + /** + * Promised tasks + */ + public promised: Array<(bud: Bud) => Promise> = [] + + public declare addConfig: typeof methods.addConfig + public declare after: typeof methods.after public declare api: Service & Api @@ -97,8 +104,6 @@ export class Bud { public declare project: Project - public promised: Promise = Promise.resolve() - public declare publicPath: typeof methods.publicPath public declare relPath: typeof methods.relPath @@ -190,12 +195,25 @@ export class Bud { return this.context?.mode ?? `production` } + /** + * Constructor + */ + public constructor(context?: Context) { + if (context) this.context = {...context} + + this.set(`implementation`, this.constructor as any) + + Object.entries(methods).map(([k, v]) => + this.set(k as any, v.bind(this)), + ) + } + /** * Boot application services */ @bind public async boot() { - await this.executeServiceCallbacks(`boot`) + await this.executeServiceCallbacks(`boot`).catch(this.catch) } /** @@ -203,31 +221,20 @@ export class Bud { */ @bind public async bootstrap() { - await this.executeServiceCallbacks(`bootstrap`) + await this.executeServiceCallbacks(`bootstrap`).catch(this.catch) } + /** + * Error handler + */ @bind public catch(error: Error): never { - if (error instanceof BudError) { - error.instance = this.label - throw error - } - const normalizedError = BudError.normalize(error) - normalizedError.instance = this.label + if (!normalizedError.instance && this?.isChild) + normalizedError.instance = this.label throw normalizedError } - /** - * Log error - * @deprecated Import logger instance from `@roots/bud-support/logger` - */ - @bind - public error(...messages: Array): Bud { - logger.scope(this.label).error(...messages) - return this - } - /** * Execute service callbacks for a given stage * @internal @@ -235,37 +242,26 @@ export class Bud { @bind public async executeServiceCallbacks( stage: `${keyof EventsStore & string}`, - ): Promise { - return await this.promise(async () => - this.hooks.fire(stage, this), - ).catch(this.catch) - } - - /** - * Log info - * @deprecated Import logger instance from `@roots/bud-support/logger` - */ - @bind - public info(...messages: any[]) { - logger.scope(this.label).info(...messages) - return this + ): Promise { + await this.resolvePromises().catch(this.catch) + await this.hooks.fire(stage, this) } /** * Bud initialize */ @bind - public async initialize(context: Context): Promise { - this.set(`context`, {...context}) - .set(`promised`, Promise.resolve()) - .set(`implementation`, this.constructor as any) - - Object.entries(methods).reduce( - (_, [k, v]) => this.set(k as any, v.bind(this)), - {}, - ) + public async initialize(context?: Context): Promise { + if (context) this.context = {...(this.context ?? {}), ...context} + + await bootstrap(this).catch(this.catch) - return await bootstrap(this).catch(this.catch) + await this.bootstrap().catch(this.catch) + await this.register().catch(this.catch) + await this.boot().catch(this.catch) + await this.executeServiceCallbacks(`config.before`).catch(this.catch) + + return this } /** @@ -278,7 +274,9 @@ export class Bud { ) { if (!this.isRoot) { return this.catch( - new InputError(`bud.make: must be called from the root context`), + InputError.normalize( + `bud.make: must be called from the root context`, + ), ) } @@ -288,7 +286,7 @@ export class Bud { if (isUndefined(context.label)) { return this.catch( - new InputError(`bud.make: context.label must be a string`), + InputError.normalize(`bud.make: context.label must be a string`), ) } @@ -296,10 +294,13 @@ export class Bud { !isUndefined(this.context.filter) && !this.context.filter.includes(context.label) ) { - logger.log( - `skipping child instance based on --filter flag:`, - context.label, - ) + logger + .scope(`make`) + .log( + `skipping child instance based on --filter flag:`, + context.label, + ) + return this } @@ -307,13 +308,13 @@ export class Bud { if (this.children[context.label]) { return this.catch( - new InputError( + InputError.normalize( `bud.make: child instance ${context.label} already exists`, ), ) } - logger.log(`instantiating ${context.label}`) + logger.scope(`make`).log(`instantiating ${context.label}`) this.children[context.label] = await new this.implementation().initialize({ @@ -321,16 +322,14 @@ export class Bud { }) if (setupFn) await setupFn(this.children[context.label]) - await this.children[context.label].promise() + await this.children[context.label].resolvePromises().catch(this.catch) - this.get(context.label)?.hooks.on( - `build.dependencies`, - typeof request !== `string` && request.dependsOn - ? request.dependsOn - : Object.values(this.children) - .map(({label}) => label) - .filter(label => label !== context.label), - ) + if (typeof request !== `string` && request.dependsOn) { + this.get(context.label)?.hooks.on( + `build.dependencies`, + request.dependsOn, + ) + } return this } @@ -339,12 +338,22 @@ export class Bud { * Await all promised tasks */ @bind - public async promise(promise?: (bud: Bud) => Promise) { - await this.promised - .then(async () => promise && (await promise(this))) - .catch(this.catch) + public promise(promise: (bud: Bud) => Promise) { + this.promised.push(promise) + } + + @bind + public async resolvePromises() { + if (this.promised.length === 0) return - return this + const promises = this.promised + + this.promised = [] + + await promises.reduce(async (promised, promise) => { + await promised + await promise(this) + }, Promise.resolve()) } /** @@ -352,7 +361,7 @@ export class Bud { */ @bind public async register() { - await this.executeServiceCallbacks(`register`) + await this.executeServiceCallbacks(`register`).catch(this.catch) } /** @@ -372,12 +381,12 @@ export class Bud { @bind public set( key: K, - unknownValue: Bud[K], + input: Bud[K], bind: boolean = true, ): Bud { - const bindable = - bind && isFunction(unknownValue) && `bind` in unknownValue - const value = bindable ? unknownValue.bind(this) : unknownValue + const toBind = bind && isFunction(input) && `bind` in input + + const value = toBind ? input.bind(this) : input Object.defineProperty(this, key, { configurable: true, @@ -385,6 +394,7 @@ export class Bud { value, writable: true, }) + return this } @@ -393,16 +403,17 @@ export class Bud { * @deprecated Import logger instance from `@roots/bud-support/logger` */ @bind - public log(...messages: any[]) { + public log(...messages: Array) { logger.scope(this.label).log(...messages) return this } + /** * Log success * @deprecated Import logger instance from `@roots/bud-support/logger` */ @bind - public success(...messages: any[]) { + public success(...messages: Array) { logger.scope(this.label).log(...messages) return this } @@ -411,8 +422,28 @@ export class Bud { * @deprecated Import logger instance from `@roots/bud-support/logger` */ @bind - public warn(...messages: any[]) { + public warn(...messages: Array) { logger.scope(this.label).warn(...messages) return this } + + /** + * Log info + * @deprecated Import logger instance from `@roots/bud-support/logger` + */ + @bind + public info(...messages: Array) { + logger.scope(this.label).info(...messages) + return this + } + + /** + * Log error + * @deprecated Import logger instance from `@roots/bud-support/logger` + */ + @bind + public error(...messages: Array) { + logger.scope(this.label).error(...messages) + return this + } } diff --git a/sources/@roots/bud-framework/src/configuration/configuration.ts b/sources/@roots/bud-framework/src/configuration/configuration.ts deleted file mode 100644 index 2eeae4a702..0000000000 --- a/sources/@roots/bud-framework/src/configuration/configuration.ts +++ /dev/null @@ -1,112 +0,0 @@ -import type {Bud} from '@roots/bud-framework' -import type {File} from '@roots/bud-framework/context' - -import {bind} from '@roots/bud-support/decorators/bind' -import {BudError} from '@roots/bud-support/errors' -import get from '@roots/bud-support/lodash/get' -import isArray from '@roots/bud-support/lodash/isArray' -import isFunction from '@roots/bud-support/lodash/isFunction' -import isObject from '@roots/bud-support/lodash/isObject' -import isString from '@roots/bud-support/lodash/isString' - -/** - * User config parser - */ -class Configuration { - /** - * Class constructor - */ - public constructor(public bud: Bud) {} - - /** - * Process dynamic configuration - */ - @bind - public async dynamicConfig( - config: (bud: Bud) => Promise, - ): Promise { - try { - return await config(this.bud) - } catch (cause) { - throw cause - } - } - - @bind - public async handleConfigEntry( - obj: any, - [key, value]: [string, unknown], - ) { - if (!(key in obj)) return - - const request = obj[key] - const normalValue = isArray(value) ? value : [value] - - const parsedValue = normalValue.map(v => { - if ( - isString(v) && - (v.startsWith(`_app.`) || v.startsWith(`_bud.`)) - ) { - return get( - this.bud, - v.replace(`_app.`, ``).replace(`_bud.`, ``).trim(), - ) - } - if ( - isString(v) && - (v.startsWith(`bud =>`) || v.startsWith(`app =>`)) - ) { - return eval(v.trim())(this.bud) - } - if (isString(v) && v.startsWith(`=>`)) { - return eval(v.slice(3)) - } - return v - }) - - if (isFunction(request)) await Promise.resolve(request(...parsedValue)) - - if (isObject(request)) - await Promise.all( - Object.entries(parsedValue).map(async ([key, value]) => { - return await Promise.resolve( - this.handleConfigEntry(request, [key, value]), - ) - }), - ) - } - - /** - * Process configuration - */ - @bind - public async run(file: File): Promise { - if (!file?.module) { - throw new BudError(`No module found`, { - details: `There should be a module here. This is like an error with bud.js`, - file, - }) - } - - const config = await file.module() - isFunction(config) - ? await this.dynamicConfig(config) - : await this.staticConfig(config) - } - - /** - * Process static configuration - */ - @bind - public async staticConfig( - config: Record, - ): Promise { - return await Promise.all( - Object.entries(config).map(async ([key, value]) => { - await this.handleConfigEntry(this.bud, [key, value]) - }), - ) - } -} - -export default Configuration diff --git a/sources/@roots/bud-framework/src/configuration/dynamic/index.ts b/sources/@roots/bud-framework/src/configuration/dynamic/index.ts new file mode 100644 index 0000000000..1248fe64c5 --- /dev/null +++ b/sources/@roots/bud-framework/src/configuration/dynamic/index.ts @@ -0,0 +1,23 @@ +import type {Bud} from '@roots/bud-framework' + +import {bind} from '@roots/bud-support/decorators/bind' + +/** + * User config parser + */ +class DynamicConfiguration { + /** + * Class constructor + */ + public constructor(public bud: Bud) {} + + /** + * Process static configuration + */ + @bind + public async execute(config: (bud: Bud) => Promise): Promise { + await config.call(this.bud, this.bud) + } +} + +export default DynamicConfiguration diff --git a/sources/@roots/bud-framework/src/configuration/index.ts b/sources/@roots/bud-framework/src/configuration/index.ts index 3d9cd69487..4be71c7a73 100644 --- a/sources/@roots/bud-framework/src/configuration/index.ts +++ b/sources/@roots/bud-framework/src/configuration/index.ts @@ -1,65 +1,68 @@ +import type {Bud} from '@roots/bud-framework' import type {File} from '@roots/bud-framework/context' +import {bind} from '@roots/bud-support/decorators/bind' import {BudError, ConfigError} from '@roots/bud-support/errors' -import sortBy from '@roots/bud-support/lodash/sortBy' -import logger from '@roots/bud-support/logger' -import type {Bud} from '../index.js' +import DynamicConfiguration from './dynamic/index.js' +import StaticConfiguration from './static/index.js' -import Configuration from './configuration.js' +interface Config { + ext: File[`ext`] + module: File[`module`] + name: File[`name`] +} /** - * Process configurations + * User config parser */ -export const process = async (app: Bud) => { - const configuration = new Configuration(app) - - const configs: Array = Object.values(app.context.files).filter( - ({bud}) => bud, - ) +class Configuration { + /** + * Class constructor + */ + public constructor(public bud: Bud) {} - const find = (isTarget: string, isLocal: boolean) => - sortBy( - configs - .filter(({target}) => target === isTarget) - .filter(({local}) => local === isLocal), - `name`, - ) + /** + * Process configuration + */ + @bind + public async run(source: Config): Promise { + if (!source?.module) { + throw BudError.normalize(`No module found`, { + details: `There should be a module here. This is likely an internal error in bud.js.`, + file: source, + }) + } - const processConfig = async (file: File) => { - logger.log(`processing`, file.name) - await configuration.run(file).catch(makeError(file)) - await app.promise().catch(makeError(file)) - } + const config = await source.module().catch(origin => { + throw ConfigError.normalize(`Error parsing ${source.name}`, { + file: source, + origin, + }) + }) - await Promise.all(find(`base`, false).map(processConfig)) - await Promise.all(find(`base`, true).map(processConfig)) - await Promise.all(find(app.mode, false).map(processConfig)) - await Promise.all(find(app.mode, true).map(processConfig)) + this.bud.log(`config`, config) - await app.executeServiceCallbacks(`config.after`).catch(error => { - throw error - }) + if (!config) { + throw ConfigError.normalize(`No configuration found`, { + file: source, + }) + } - if (!app.hasChildren) return + if (typeof config === `function`) { + return await new DynamicConfiguration(this.bud) + .execute(config) + .catch(error => { + throw error + }) + } - await Promise.all( - Object.values(app.children).map(async child => { - await child.promise().catch(error => { - throw error - }) - await child.executeServiceCallbacks(`config.after`).catch(error => { + await new StaticConfiguration(this.bud, `${source.name}${source.ext}`) + .execute(config) + .catch(error => { throw error }) - }), - ) + } } -const makeError = - (file: File) => (error: Error & {isBudError?: boolean}) => { - throw new ConfigError(`Error in ${file.name}`, { - file, - origin: - error instanceof BudError ? error : BudError.normalize(error), - }) - } +export default Configuration diff --git a/sources/@roots/bud-framework/src/configuration/static/index.ts b/sources/@roots/bud-framework/src/configuration/static/index.ts new file mode 100644 index 0000000000..a767dab4fe --- /dev/null +++ b/sources/@roots/bud-framework/src/configuration/static/index.ts @@ -0,0 +1,147 @@ +import type {Bud} from '@roots/bud-framework' + +import {bind} from '@roots/bud-support/decorators/bind' +import {BudError} from '@roots/bud-support/errors' +import get from '@roots/bud-support/lodash/get' +import isFunction from '@roots/bud-support/lodash/isFunction' +import isObject from '@roots/bud-support/lodash/isObject' +import isString from '@roots/bud-support/lodash/isString' +import logger from '@roots/bud-support/logger' + +/** + * User config parser + */ +class StaticConfiguration { + /** + * Process + */ + public get logger() { + return logger.scope(`config`, this.name) + } + + /** + * Class constructor + */ + public constructor( + public bud: Bud, + public name: string, + ) {} + + /** + * Handle static config object entry + */ + @bind + public async processRecordEntry( + [key, value]: [string, unknown], + path: Array = [], + ) { + path.push(key) + + const interpretValue = async (value: unknown) => { + if ( + isString(value) && + (value.startsWith(`_app.`) || value.startsWith(`_bud.`)) + ) { + return get( + this.bud, + value.replace(`_app.`, ``).replace(`_bud.`, ``).trim(), + ) + } + + if ( + isString(value) && + (value.startsWith(`bud =>`) || value.startsWith(`app =>`)) + ) { + return eval(value.trim())(this.bud) + } + + if ( + isString(value) && + (value.startsWith(`async bud =>`) || + value.startsWith(`async app =>`)) + ) { + return await Promise.resolve(eval(value.trim())(this.bud)).catch( + error => { + throw error + }, + ) + } + + if (isString(value) && value.startsWith(`=>`)) { + return eval(value.slice(3)) + } + + return value + } + + const parse = async (value: Array | unknown) => { + return Array.isArray(value) + ? await Promise.all(value.map(interpretValue)).catch(error => { + throw error + }) + : await interpretValue(value).catch(error => { + throw error + }) + } + + const target = path?.reduce(get, this.bud) + + if (!target) { + throw new BudError( + `Attempted to access bud.${path} but this doesn't seem to be an object or function.`, + ) + } + + if (isFunction(target)) { + const args = await parse(asArray(value)) + + this.logger + .scope(`config`) + .log(`calling bud.${path.join(`.`)}`, `with args:`, ...args) + + const result = target(...args) + + if (result instanceof Promise) { + await result.catch(error => { + throw error + }) + } + + return result + } + + if (isObject(target)) { + return await Promise.all( + Object.entries(parse(value)).map( + async entry => + await this.processRecordEntry(entry, path).catch(error => { + throw error + }), + ), + ).catch(error => { + throw error + }) + } + } + + /** + * Process static configuration + */ + @bind + public async execute(config: Record): Promise { + try { + await Promise.all( + Object.entries(config).map(async ([key, value]) => { + await this.processRecordEntry([key, value]) + }), + ) + } catch (error) { + throw error + } + } +} + +const asArray = (value: Array | T) => + Array.isArray(value) ? value : [value] + +export default StaticConfiguration diff --git a/sources/@roots/bud-framework/src/extension/decorators/options.ts b/sources/@roots/bud-framework/src/extension/decorators/options.ts index a91b84ec43..94680ec7be 100644 --- a/sources/@roots/bud-framework/src/extension/decorators/options.ts +++ b/sources/@roots/bud-framework/src/extension/decorators/options.ts @@ -38,6 +38,8 @@ export const options = if (noPropertyDefined(this, key)) { // Define a property on the Extension instance with getter and setter methods. Object.defineProperty(this, key, { + configurable: true, + enumerable: true, get: () => this.getOption(key), }) } @@ -48,10 +50,13 @@ export const options = if (noPropertyDefined(this, setFn)) { // Define a setter method on the Extension instance. Object.defineProperty(this, setFn, { + configurable: true, + enumerable: true, value: (value: any) => { this.setOption(key, value) return this }, + writable: true, }) } @@ -61,7 +66,10 @@ export const options = if (noPropertyDefined(this, getFn)) { // Define a getter method on the Extension instance. Object.defineProperty(this, getFn, { + configurable: true, + enumerable: true, value: () => this.getOption(key), + writable: true, }) } }) diff --git a/sources/@roots/bud-framework/src/extension/index.ts b/sources/@roots/bud-framework/src/extension/index.ts index 1f1f688c4f..a1017089c9 100644 --- a/sources/@roots/bud-framework/src/extension/index.ts +++ b/sources/@roots/bud-framework/src/extension/index.ts @@ -1,8 +1,8 @@ -import type {Bud} from '@roots/bud-framework' import type {Modules} from '@roots/bud-framework' import type {Compiler} from '@roots/bud-framework/config' import type {ApplyPluginConstructor} from '@roots/bud-framework/extension/decorators/plugin' +import {Bud} from '@roots/bud-framework' import {bind} from '@roots/bud-support/decorators/bind' import {BudError, ExtensionError} from '@roots/bud-support/errors' import get from '@roots/bud-support/lodash/get' @@ -63,6 +63,7 @@ export interface Meta { boot: boolean buildAfter: boolean buildBefore: boolean + compilerDone: boolean configAfter: boolean register: boolean } @@ -93,33 +94,338 @@ export type WithOptions = { [K in keyof Options as `${K & string}`]: Options[K] } -export type StrictPublicExtensionApi = { - app: Bud - enable: (boolean?: boolean) => Context - enabled: boolean - get: (key: K) => Opts[K] - getOptions: () => Opts +/** + * Public extension interface + */ +export type PublicExtensionApi< + ExtensionImplementation extends Extension = Extension, +> = { + /** + * ## Extension.app + * + * {@link Bud} instance + */ + app: ExtensionImplementation[`app`] + + /** + * ## Extension.done + * + * {@link ExtensionImplementation.done} + * + * @remarks + * Returns the {@link Bud} instance from the extension. This is useful + * for chaining method calls. + * + * @example + * ```js + * app + * .extensions + * .get('@roots/bud-postcss') + * .set('plugins', []) + * .done() + * + * .entry('app', 'src/index.js') + * ``` + */ + done: ExtensionImplementation[`done`] + + /** + * ## Extension.enable + * + * Enable or disable extension + * + * @remarks + * The following methods are skipped if `enabled` is false: + * - {@link Extension.buildBefore} + * - {@link Extension.buildAfter} + * - {@link Extension.make} + * + * @example + * Enable extension: + * ```js + * app.extensions.get('@roots/bud-postcss').enable() + * ``` + * + * @example + * Disable extension: + * ```js + * app.extensions.get('@roots/bud-postcss').enable(false) + * ``` + * + * @example + * Functional callback: + * ```js + * app.when(app.isProduction, app.extensions.get('@roots/bud-postcss').enable) + * ``` + */ + enable: ExtensionImplementation['enable'] + + /** + * ## Extension.enabled + * + * Property indicating if the extension is enabled + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').enabled + * ``` + */ + enabled: ExtensionImplementation['enabled'] + + /** + * ## Extension.get + * + * Get the value of an option record by key + * + * @remarks + * Alias for {@link Extension.getOption} + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').get('plugins') + * ``` + */ + get: ExtensionImplementation[`getOption`] + + /** + * ## Extension.getOption + * + * Get the value of an option record by key + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').getOption('plugins') + * ``` + */ + getOption: ExtensionImplementation[`getOption`] + + /** + * ## Extension.getOptions + * + * Get all options records + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').getOptions() + * ``` + */ + getOptions: ExtensionImplementation[`getOptions`] + + /** + * ## Extension.logger + * + * @remarks + * This logger is scoped to the extension + */ + logger: ExtensionImplementation[`logger`] + + /** + * ## Extension.options + * + * Options accessor + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').options + * ``` + */ + options: ExtensionImplementation['options'] + + /** + * ## Extension.set + * + * Set an option value + * + * @remarks + * Alias for {@link Extension.setOption} + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').set('plugins', []) + * ``` + */ + set: ExtensionImplementation[`set`] + + /** + * ## Extension.setOption + * + * Set an option value + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').setOption('plugins', []) + * ``` + */ + setOption: ExtensionImplementation[`setOption`] + + /** + * ## Extension.setOptions + * + * Overwrite existing options + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').setOptions({plugins: []}) + * ``` + */ + setOptions: ExtensionImplementation[`setOptions`] +} + +export type StrictPublicExtensionApi< + Context, + OptionsRecords extends Options, +> = { + /** + * ## Extension.app + * + * {@link Bud} instance + */ + app: PublicExtensionApi[`app`] + + /** + * ## Extension.done + * + * Returns the {@link Bud} instance from the extension. + * + * @remarks + * This is useful for chaining method calls. + * + * @example + * ```js + * app + * .extensions + * .get('@roots/bud-postcss') + * .set('plugins', []) + * .done() + * + * .entry('app', 'src/index.js') + * ``` + */ + done: PublicExtensionApi[`done`] + + /** + * ## Extension.enable + * + * Enable or disable extension + * + * @remarks + * The following methods are skipped if `enabled` is false: + * - {@link Extension.buildBefore} + * - {@link Extension.buildAfter} + * - {@link Extension.make} + * + * @example + * Enable extension: + * ```js + * app.extensions.get('@roots/bud-postcss').enable() + * ``` + * + * @example + * Disable extension: + * ```js + * app.extensions.get('@roots/bud-postcss').enable(false) + * ``` + * + * @example + * Functional callback: + * ```js + * app.when(app.isProduction, app.extensions.get('@roots/bud-postcss').enable) + * ``` + */ + enable: PublicExtensionApi[`enable`] + + /** + * ## Extension.enabled + * + * Property indicating if the extension is enabled + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').enabled + * ``` + */ + enabled: PublicExtensionApi[`enabled`] + + /** + * ## Extension.get + * + * Get the value of an option record by key + * + * @remarks + * Alias for {@link Extension.getOption} + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').get('plugins') + * ``` + */ + get: ( + key: K, + ) => OptionsRecords[K] + + /** + * ## Extension.getOptions + * + * Get all options records + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').getOptions() + * ``` + */ + getOptions: () => OptionsRecords + + /** + * ## Extension.logger + * + * @remarks + * This logger is scoped to the extension + */ logger: typeof logger - options: Opts - set: ( + + /** + * ## Extension.options + * + * Options records accessor + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').options + * ``` + */ + options: OptionsRecords + + /** + * ## Extension.set + * + * Set an option value + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').set('plugins', []) + * ``` + */ + set: ( key: K, - value: ((value: Opts[K]) => Opts[K]) | Opts[K], + value: + | ((value: OptionsRecords[K]) => OptionsRecords[K]) + | OptionsRecords[K], ) => Context - setOptions: (O: Partial>) => Context -} & WithOptions - -export type PublicExtensionApi = { - app: E[`app`] - enable: E['enable'] - enabled: E['enabled'] - get: E[`getOption`] - getOption: E[`getOption`] - getOptions: E[`getOptions`] - options: E['options'] - set: E[`set`] - setOption: E[`setOption`] - setOptions: E[`setOptions`] -} + + /** + * ## Extension.setOptions + * + * Overwrite existing options + * + * @example + * ```js + * app.extensions.get('@roots/bud-postcss').setOptions({plugins: []}) + * ``` + */ + setOptions: ( + O: Partial>, + ) => Context +} & WithOptions export type ExtensionLiteral = Partial @@ -133,46 +439,28 @@ export class Extension< Plugin extends ApplyPlugin = ApplyPlugin, > { /** - * Application + * {@link Bud} instance get fn */ - public _app: () => Bud + private _app: () => Bud + /** * Extension options */ - public _options: Partial> + public declare _options: Partial> + /** * Depends on */ - public dependsOn?: Set + public declare dependsOn?: Set /** * Depends on (optional) */ - public dependsOnOptional?: Set<`${keyof Modules & string}`> - /** - * Is extension enabled - * - * @remarks - * The following methods are skipped if `enabled` is false: - * - {@link Extension.buildBefore} - * - {@link Extension.make} - */ - public enabled: boolean = true + public declare dependsOnOptional?: Set<`${keyof Modules & string}`> /** * The module name */ - public label: `${keyof Modules & string}` - - /** - * Extension meta - */ - public meta: Meta = { - boot: false, - buildAfter: false, - buildBefore: false, - configAfter: false, - register: false, - } + public declare label?: `${keyof Modules & string}` /** * Extension options @@ -182,77 +470,100 @@ export class Extension< public options: ExtensionOptions /** - * Plugin constructor + * {@link ApplyPlugin.apply} callback */ - public plugin?: ApplyPluginConstructor + public apply?(compiler: Compiler): unknown | void /** - * Class constructor + * `boot` callback */ - public constructor(app: Bud) { - this._app = () => app + public boot?(app: Bud): Promise - this._options = this.options ? {...this.options} : {} - delete this.options + /** + * `buildAfter` callback + */ + public buildAfter?(app: Bud): Promise - Object.defineProperty(this, `options`, { - get: this.getOptions.bind(this), - }) - } + /** + * `buildBefore` callback + */ + public buildBefore?(app: Bud): Promise + + /** + * `configAfter` callback + */ + public configAfter?(app: Bud): Promise + + /** + * Plugin constructor + */ + public declare plugin?: ApplyPluginConstructor /** - * Application accessor + * Function returning a boolean indicating if the {@link Extension} should be utilized. + * + * @remarks + * If set this takes precedence over {@link Extension.enabled}. */ + public when?(bud: Bud, options?: ExtensionOptions): boolean + public get app(): Bud { return this._app() } + public enabled: boolean = true + /** - * {@link ApplyPlugin.apply} - */ - public apply?(compiler: Compiler): unknown | void - /** - * `boot` callback + * ## Extension.meta + * + * @remarks + * Tracks which extension methods have been executed to prevent + * duplicate execution in race conditions, etc. */ - public async boot?(app: Bud): Promise + public meta: Meta = { + boot: false, + buildAfter: false, + buildBefore: false, + compilerDone: false, + configAfter: false, + register: false, + } /** - * `buildAfter` callback + * Class constructor */ - public async buildAfter?(app: Bud): Promise + public constructor(app: Bud) { + this._app = () => app + + this._options = this.options ? {...this.options} : {} + delete this.options + + Object.defineProperty(this, `options`, { + get: this.getOptions.bind(this), + }) + } /** - * `buildBefore` callback + * Error handler */ - public async buildBefore?(app: Bud): Promise @bind public catch(error: Error | string): never { - const thrownBy = + const label = this.label ?? this.constructor?.name ?? `unknown_extension` - if (error instanceof ExtensionError) { - error.instance = this.app.label - error.thrownBy = error.thrownBy ?? thrownBy + if (error instanceof BudError) { throw error } - throw new ExtensionError( - typeof error === `string` ? error : error.message, - { - docs: new URL(`https://bud.js.org/docs/extensions`), - issue: new URL( - `https://github.com/roots/bud/search?q=is:issue+${thrownBy} in:title`, - ), - origin: BudError.normalize(error), - thrownBy, - }, - ) + throw ExtensionError.normalize(error, { + docs: new URL(`https://bud.js.org/docs/extensions`), + issue: new URL( + `https://github.com/roots/bud/search?q=is:issue+${label} in:title`, + ), + thrownBy: import.meta.url, + }) } - /** - * `configAfter` callback - */ - public async configAfter?(app: Bud): Promise /** * Disable extension * @deprecated pass `false` to {@link Extension.enable} @@ -274,10 +585,15 @@ export class Extension< * Enable extension */ @bind - public enable(enabled: boolean = true) { - this.logger.info(`enabled`, this.label) - this.enabled = enabled + public enable(enabled: boolean | Bud = true) { + this.logger.log(enabled ? `enabled` : `disabled`) + if (enabled instanceof Bud) { + this.enabled = true + return this + } + + this.enabled = enabled return this } @@ -286,7 +602,7 @@ export class Extension< */ @bind public getOption(key: K): ExtensionOptions[K] { - return get(this.options, key) + return get(this.getOptions(), key) } /** * Get an option value @@ -311,9 +627,14 @@ export class Extension< ) } - const unwrapped = isDynamicOption(value) - ? value.get()(this.app) - : value + const isDynamic = isDynamicOption(value) + const unwrapped = isDynamic ? value.get()(this.app) : value + + this.logger.info( + key, + `has value:`, + isDynamic ? `${typeof unwrapped} (dynamic)` : typeof unwrapped, + ) if (isUndefined(unwrapped)) return acc return {...acc, [key]: unwrapped} @@ -342,7 +663,9 @@ export class Extension< */ @bind public isEnabled(): boolean { - return this.when(this.app, this.options) + return `when` in this + ? this.when(this.app, this.getOptions()) + : this.enabled } /** @@ -375,16 +698,7 @@ export class Extension< signifier: string, context: string, ): Promise { - return await this.app.module - .resolve(signifier, context) - .catch(error => { - this.catch( - new ExtensionError(`could not resolve ${signifier}`, { - origin: error, - thrownBy: this.label, - }), - ) - }) + return await this.app.module.resolve(signifier, context) } /** @@ -415,6 +729,7 @@ export class Extension< /** * Set options */ + @bind public setOptions( value: Partial>, ): this { @@ -423,111 +738,48 @@ export class Extension< return this } - /** - * Function returning a boolean indicating if the {@link Extension} should be utilized. - * - * @remarks - * By default returns {@link Extension.enabled} - */ - public when(bud: Bud, options?: ExtensionOptions): boolean { - return this.enabled - } - - /** - * `register` callback handler - */ - @bind - public async _register() { - if (isUndefined(this.register)) return - - if (this.meta[`register`] === true) return - this.meta[`register`] = true - - await this.register(this.app).catch(this.catch) - } - - /** - * `boot` callback handler - */ - @bind - public async _boot() { - if (isUndefined(this.boot)) return - - if (this.meta[`boot`] === true) return - this.meta[`boot`] = true - - await this.boot(this.app).catch(this.catch) - } - - /** - * `buildAfter` callback handler - */ @bind - public async _buildAfter() { - if (isUndefined(this.buildAfter)) return - if (!this.isEnabled()) return + public async execute(key: `${keyof Meta & string}` | `make`) { + await this.app.resolvePromises() - if (this.meta[`buildAfter`] === true) return - this.meta[`buildAfter`] = true - - await this.buildAfter(this.app).catch(this.catch) - } - - /** - * `buildBefore` callback handler - */ - @bind - public async _buildBefore() { - if (isUndefined(this.buildBefore)) return - if (!this.isEnabled()) return - - if (this.meta[`buildBefore`] === true) return - this.meta[`buildBefore`] = true - - await this.buildBefore(this.app).catch(this.catch) - } + if (key === `make`) { + if (!this.isEnabled()) return false - /** - * `configAfter` callback handler - */ - @bind - public async _configAfter() { - if (isUndefined(this.configAfter)) return - - if (this.meta[`configAfter`] === true) return - this.meta[`configAfter`] = true - - await this.configAfter(this.app).catch(this.catch) - } - - /** - * `make` callback handler - */ - @bind - public async _make() { - if (isUndefined(this.make) && isUndefined(this.plugin)) return false - if (this.isEnabled() === false) return false - - try { if (!isUndefined(this.apply)) { - this.logger.info(`apply method found. return extension instance.`) + this.logger.success(`produced hybrid compiler/bud plugin`) + this.logger.info(this) return this } if (!isUndefined(this.plugin)) { - const plugin = new this.plugin({...this.options}) - this.logger.success(`produced webpack plugin`) + const plugin = new this.plugin({...this.getOptions()}) + this.logger.success(`produced compiler plugin`) + this.logger.info(plugin) return plugin } if (!isUndefined(this.make)) { - const plugin = await this.make(this.app, {...this.options}) - this.logger.success(`produced webpack plugin`) + const plugin = await this.make(this.app, {...this.getOptions()}) + this.logger.success(`produced make plugin`) + this.logger.info(plugin) return plugin } - } catch (error) { - this.catch(error) + + return false } + + if (isUndefined(this[key])) return + + if (this.meta[key] === true) return + this.meta[key] = true + + if ([`buildAfter`, `buildBefore`].includes(key) && !this.isEnabled()) + return + + this.logger.log(`executing`, key) + + await this[key](this.app) + await this.app.resolvePromises() } } diff --git a/sources/@roots/bud-framework/src/fs.ts b/sources/@roots/bud-framework/src/fs.ts index 7db4ea5ff2..0bf2140225 100644 --- a/sources/@roots/bud-framework/src/fs.ts +++ b/sources/@roots/bud-framework/src/fs.ts @@ -160,7 +160,7 @@ export class FS extends Filesystem implements Contract { source?: string }): this { if (!this.s3.config.credentials) { - throw new BudError( + throw BudError.normalize( `S3 is not configured. See https://bud.js.org/reference/bud.fs/s3`, ) } diff --git a/sources/@roots/bud-framework/src/index.ts b/sources/@roots/bud-framework/src/index.ts index 87d40e2aec..18f23977a5 100644 --- a/sources/@roots/bud-framework/src/index.ts +++ b/sources/@roots/bud-framework/src/index.ts @@ -8,7 +8,8 @@ * @see https://github.com/roots/bud */ -export * from './bud.js' +export * from '@roots/bud-framework/bud' + export { type Contract as BudService, default as Service, diff --git a/sources/@roots/bud-framework/src/methods/addConfig.ts b/sources/@roots/bud-framework/src/methods/addConfig.ts new file mode 100644 index 0000000000..7ba71ce4d6 --- /dev/null +++ b/sources/@roots/bud-framework/src/methods/addConfig.ts @@ -0,0 +1,34 @@ +import type {Bud} from '@roots/bud-framework' + +import {basename, extname} from 'node:path' + +import Configuration from '@roots/bud-framework/configuration' +import {ConfigError} from '@roots/bud-support/errors' + +/** + * Process user configurations + */ +export interface addConfig { + (path: string): Bud +} + +export const addConfig: addConfig = function (this: Bud, path: string) { + const file = { + ext: extname(path), + module: [`.json`, `.yaml`, `.yml`].includes(extname(path)) + ? async () => await this.fs.read(path) + : async () => await this.module.import(path, import.meta.url), + name: basename(path), + } + + this.promise(async bud => { + await new Configuration(bud).run(file).catch(error => { + throw ConfigError.normalize(`Error parsing ${file.name}`, { + file, + origin: ConfigError.normalize(error), + }) + }) + }) + + return this +} diff --git a/sources/@roots/bud-framework/src/methods/after.ts b/sources/@roots/bud-framework/src/methods/after.ts index 64312946e5..6110875e6e 100644 --- a/sources/@roots/bud-framework/src/methods/after.ts +++ b/sources/@roots/bud-framework/src/methods/after.ts @@ -16,12 +16,10 @@ export const after: after = function ( errorHandler?: (error: Error) => unknown, ): Bud { this.hooks.action(`compiler.done`, async bud => { - try { - await bud.promise(fn) - } catch (error) { - if (!errorHandler) throw error - errorHandler(error) - } + await fn(bud).catch(error => { + if (errorHandler) return errorHandler(error) + bud.catch(error) + }) }) return this diff --git a/sources/@roots/bud-framework/src/methods/bindFacade.ts b/sources/@roots/bud-framework/src/methods/bindFacade.ts index 12a4972171..ddb5b83b98 100644 --- a/sources/@roots/bud-framework/src/methods/bindFacade.ts +++ b/sources/@roots/bud-framework/src/methods/bindFacade.ts @@ -23,12 +23,21 @@ export const bindFacade: bindFacade = function (key, fn, binding?) { if (`bind` in fn) fn = fn.bind(binding ?? this) this.set(key, (...args: Array) => { - this.promised - .then(async () => await fn(...args)) - .catch(this.catch) - .finally(() => { - logger.success(`finished promised call:`, `bud.${key}`) - }) + logger + .scope(`bud.${key}`) + .log( + `called with args:`, + ...args.map(arg => this.fs.json.stringify(arg)), + ) + + this.promise(async () => { + try { + await this.resolvePromises().then(async () => await fn(...args)) + } catch (error) { + throw error + } + }) + return this }) diff --git a/sources/@roots/bud-framework/src/methods/index.ts b/sources/@roots/bud-framework/src/methods/index.ts index 3ee7bce14e..019ff71ece 100644 --- a/sources/@roots/bud-framework/src/methods/index.ts +++ b/sources/@roots/bud-framework/src/methods/index.ts @@ -1,5 +1,6 @@ import type {Bud} from '@roots/bud-framework' +import {addConfig} from '@roots/bud-framework/methods/addConfig' import {after} from '@roots/bud-framework/methods/after' import {bindFacade} from '@roots/bud-framework/methods/bindFacade' import {close} from '@roots/bud-framework/methods/close' @@ -20,7 +21,8 @@ import { import {setPath} from '@roots/bud-framework/methods/setPath' import {setPublicPath} from '@roots/bud-framework/methods/setPublicPath' import {sh} from '@roots/bud-framework/methods/sh' -import {tap, tapAsync} from '@roots/bud-framework/methods/tap' +import {tap} from '@roots/bud-framework/methods/tap' +import {tapAsync} from '@roots/bud-framework/methods/tapAsync' import {when} from '@roots/bud-framework/methods/when' type methods = Partial<{ @@ -28,6 +30,7 @@ type methods = Partial<{ }> const methods = { + addConfig, after, bindFacade, close, diff --git a/sources/@roots/bud-framework/src/methods/path.ts b/sources/@roots/bud-framework/src/methods/path.ts index 03500510f3..c356b724a2 100644 --- a/sources/@roots/bud-framework/src/methods/path.ts +++ b/sources/@roots/bud-framework/src/methods/path.ts @@ -22,8 +22,7 @@ export const path: path = function (...values) { values = values.flatMap(value => value.split(sep)) - const hash = - this.hooks.filter(`value.hashFormat`, `[contenthash:6]`) + const hash = this.hooks.filter(`value.hashFormat`, `[contenthash:6]`) const name = this.hooks.filter(`value.fileFormat`, `[name]`) const transformMagicString = makeParseMagicString( diff --git a/sources/@roots/bud-framework/src/methods/processConfigs.ts b/sources/@roots/bud-framework/src/methods/processConfigs.ts index 3a35ef3123..b39ec99ad6 100644 --- a/sources/@roots/bud-framework/src/methods/processConfigs.ts +++ b/sources/@roots/bud-framework/src/methods/processConfigs.ts @@ -1,6 +1,9 @@ import type {Bud} from '@roots/bud-framework' +import type {File} from '@roots/bud-framework/context' -import * as configuration from '@roots/bud-framework/configuration' +import Configuration from '@roots/bud-framework/configuration' +import sortBy from '@roots/bud-support/lodash/sortBy' +import logger from '@roots/bud-support/logger' /** * Process user configurations @@ -9,7 +12,52 @@ export interface processConfigs { (): Promise } -export const processConfigs: processConfigs = async function () { - await configuration.process(this) +export const processConfigs: processConfigs = async function (this: Bud) { + const configuration = new Configuration(this) + + const find = (isTarget: string, isLocal: boolean) => + sortBy( + Object.values(this.context.files) + .filter(({bud}) => bud) + .filter(({target}) => isTarget === target) + .filter(({local}) => local === isLocal), + `name`, + ) + + const processConfig = async (file: File) => { + logger.scope(`config`).log(`processing`, file.name) + + await this.resolvePromises() + .then(async () => { + await configuration.run(file).catch(this.catch) + }) + .catch(this.catch) + } + + await Promise.all(find(`base`, false).map(processConfig)).catch( + this.catch, + ) + await Promise.all(find(`base`, true).map(processConfig)).catch( + this.catch, + ) + await Promise.all(find(this.mode, false).map(processConfig)).catch( + this.catch, + ) + await Promise.all(find(this.mode, true).map(processConfig)).catch( + this.catch, + ) + + await this.executeServiceCallbacks(`config.after`).catch(this.catch) + + if (!this.hasChildren) return + + await Promise.all( + Object.values(this.children).map(async child => { + await child.tapAsync(async bud => { + await bud.executeServiceCallbacks(`config.after`).catch(this.catch) + }) + }), + ).catch(this.catch) + return this } diff --git a/sources/@roots/bud-framework/src/methods/run.ts b/sources/@roots/bud-framework/src/methods/run.ts index 754baf3e3e..c0cc0ca616 100644 --- a/sources/@roots/bud-framework/src/methods/run.ts +++ b/sources/@roots/bud-framework/src/methods/run.ts @@ -14,10 +14,7 @@ export const run: run = async function (this: Bud) { .catch(this.catch) compilation?.run((error?: Error | null | undefined) => { - if (!hasCompiler(this)) return - if (error) this.compiler.onError(error) - - compilation.close((error?: Error | null | undefined) => { + compilation?.close((error?: Error | null | undefined) => { if (!hasCompiler(this)) return if (error) this.compiler.onError(error) }) diff --git a/sources/@roots/bud-framework/src/methods/sequence.ts b/sources/@roots/bud-framework/src/methods/sequence.ts index 7c6cbc26ca..2e6d6dccde 100644 --- a/sources/@roots/bud-framework/src/methods/sequence.ts +++ b/sources/@roots/bud-framework/src/methods/sequence.ts @@ -12,7 +12,7 @@ export interface sequence { * Run a value through an array of asyncronous, non-mutational functions. * * @remarks - * Unlike {@link pipe} the value returned from each function is ignored. + * Unlike {@link Bud.pipe} the value returned from each function is ignored. */ export const sequence = async function ( fns: Array, diff --git a/sources/@roots/bud-framework/src/methods/setPublicPath.ts b/sources/@roots/bud-framework/src/methods/setPublicPath.ts index 1065911bdf..c55fa704a9 100644 --- a/sources/@roots/bud-framework/src/methods/setPublicPath.ts +++ b/sources/@roots/bud-framework/src/methods/setPublicPath.ts @@ -1,9 +1,7 @@ -import {sep} from 'node:path' +import type {Bud} from '@roots/bud-framework' import isString from '@roots/bud-support/lodash/isString' -import type {Bud} from '../index.js' - export interface setPublicPath { ( publicPath: @@ -44,7 +42,7 @@ export const setPublicPath: setPublicPath = function (publicPath) { if (value === `` || value === `auto`) return value if (!isString(value)) return value - return !value.endsWith(sep) ? `${value}${sep}` : value + return !value.endsWith(`/`) ? value.concat(`/`) : value }) return this diff --git a/sources/@roots/bud-framework/src/methods/tap.ts b/sources/@roots/bud-framework/src/methods/tap.ts index 346c7cd5dc..0660c531ad 100644 --- a/sources/@roots/bud-framework/src/methods/tap.ts +++ b/sources/@roots/bud-framework/src/methods/tap.ts @@ -33,41 +33,6 @@ export const tap: tap = function ( fn: (app: Bud) => unknown, ): Bud { fn.call(this, this) - return this -} - -export interface tapAsync { - (fn: (app: Bud) => Promise): Promise -} - -/** - * Execute an async callback - * - * @remarks - * Callback is provided {@link Bud | the Bud instance} as a parameter. - * - * @example - * ```js - * bud.tapAsync(async bud => { - * // do something with bud - * }) - * ``` - * - * @example - * Lexical scope is bound to Bud where applicable, so it - * is possible to reference the Bud using `this`. - * - * ```js - * bud.tapAsync(async function () { - * // do something with this - * }) - * ``` - */ -export const tapAsync: tapAsync = async function ( - this: Bud, - fn: (app: Bud) => Promise, -): Promise { - await fn.call(this, this) return this } diff --git a/sources/@roots/bud-framework/src/methods/tapAsync.ts b/sources/@roots/bud-framework/src/methods/tapAsync.ts new file mode 100644 index 0000000000..52f9dcc06b --- /dev/null +++ b/sources/@roots/bud-framework/src/methods/tapAsync.ts @@ -0,0 +1,41 @@ +import type {Bud} from '../index.js' + +export interface tapAsync { + (fn: (app: Bud) => Promise): Promise +} + +/** + * Execute an async callback + * + * @remarks + * Callback is provided {@link Bud | the Bud instance} as a parameter. + * + * @example + * ```js + * bud.tapAsync(async bud => { + * // do something with bud + * }) + * ``` + * + * @example + * Lexical scope is bound to Bud where applicable, so it + * is possible to reference the Bud using `this`. + * + * ```js + * bud.tapAsync(async function () { + * // do something with this + * }) + * ``` + */ +export const tapAsync: tapAsync = async function ( + this: Bud, + fn: (app: Bud) => Promise, +): Promise { + await this.resolvePromises() + .then(async () => { + await fn.call(this, this).catch(this.catch) + }) + .catch(this.catch) + + return this +} diff --git a/sources/@roots/bud-framework/src/methods/when.ts b/sources/@roots/bud-framework/src/methods/when.ts index 9f0f65cd11..5ef075a567 100644 --- a/sources/@roots/bud-framework/src/methods/when.ts +++ b/sources/@roots/bud-framework/src/methods/when.ts @@ -60,7 +60,7 @@ export function when( /* validate */ if (![...whenTrue, ...whenFalse].every(isFunction)) { - throw new InputError( + throw InputError.normalize( `bud.when: all supplied conditionals must be functions`, { docs: new URL(`https://bud.js.org/docs/bud.when`), diff --git a/sources/@roots/bud-framework/src/module.ts b/sources/@roots/bud-framework/src/module.ts index ef87bbcf2d..9bc96d77eb 100644 --- a/sources/@roots/bud-framework/src/module.ts +++ b/sources/@roots/bud-framework/src/module.ts @@ -195,7 +195,7 @@ export class Module extends Service { ) if (!code) { - throw new BudError(`Could not import ${signifier}`) + throw BudError.normalize(`Could not import ${signifier}`) } this.setModule(signifier, code) diff --git a/sources/@roots/bud-framework/src/project.ts b/sources/@roots/bud-framework/src/project.ts index b4242a98f5..6df58fd2e1 100644 --- a/sources/@roots/bud-framework/src/project.ts +++ b/sources/@roots/bud-framework/src/project.ts @@ -21,50 +21,48 @@ export default class Project extends Service { return bud } - await bud.promise(async bud => { - await bud.fs - .write(bud.path(`@storage`, bud.label, `debug`, `profile.yml`), { - ...omit(bud.context, [`env`, `stdout`, `stderr`, `stdin`]), - bootstrap: { - args: args.raw, - resolutions: bud.module.resolutions ?? {}, - }, - children: bud.children ? Object.keys(bud.children) : [], - env: bud.env.getKeys(), - loaded: Object.entries(bud.extensions?.repository).map( - ([key, extension]) => ({ - key, - label: extension.label, - meta: extension.meta, - options: extension.options, - }), - ), - }) - .catch(error => { - throw new BudError(`Could not write profile.yml`, { - details: `An error occurred while writing \`profile.yml\` to the filesystem.`, - origin: BudError.normalize(error), - }) - }) - .finally(() => { - this.logger.log(`profile.yml written to disk`) + await bud.fs + .write(bud.path(`@storage`, bud.label, `debug`, `profile.yml`), { + ...omit(bud.context, [`env`, `stdout`, `stderr`, `stdin`]), + bootstrap: { + args: args.raw, + resolutions: bud.module.resolutions ?? {}, + }, + children: bud.children ? Object.keys(bud.children) : [], + env: bud.env.getKeys(), + loaded: Object.entries(bud.extensions?.repository).map( + ([key, extension]) => ({ + key, + label: extension.label, + meta: extension.meta, + options: extension.options, + }), + ), + }) + .catch(origin => { + throw BudError.normalize(`Could not write profile.yml`, { + details: `An error occurred while writing \`profile.yml\` to the filesystem.`, + origin, }) + }) + .finally(() => { + this.logger.log(`profile.yml written to disk`) + }) - await bud.fs - .write( - bud.path(`@storage`, bud.label, `debug`, `build.config.yml`), - bud.build.config, - ) - .catch(error => { - throw new BudError(`Could not write webpack.output.yml`, { - details: `An error occurred while writing \`webpack.output.yml\` to the filesystem.`, - origin: BudError.normalize(error), - }) - }) - .finally(() => { - this.logger.log(`webpack.output.yml written to disk`) + await bud.fs + .write( + bud.path(`@storage`, bud.label, `debug`, `build.config.yml`), + bud.build.config, + ) + .catch(origin => { + throw BudError.normalize(`Could not write webpack.output.yml`, { + details: `An error occurred while writing \`webpack.output.yml\` to the filesystem.`, + origin, }) - }) + }) + .finally(() => { + this.logger.log(`webpack.output.yml written to disk`) + }) return bud } diff --git a/sources/@roots/bud-framework/src/registry/build.ts b/sources/@roots/bud-framework/src/registry/build.ts index 8722626be6..27356693fc 100644 --- a/sources/@roots/bud-framework/src/registry/build.ts +++ b/sources/@roots/bud-framework/src/registry/build.ts @@ -15,8 +15,8 @@ export interface Sync { 'cache.buildDependencies': FileCacheOptions[`buildDependencies`] 'cache.cacheDirectory': FileCacheOptions[`cacheDirectory`] 'cache.managedPaths': FileCacheOptions[`managedPaths`] - 'cache.name': FileCacheOptions[`name`] - 'cache.type': FileCacheOptions[`type`] + 'cache.name': string + 'cache.type': `filesystem` | `memory` 'cache.version': string context: Configuration[`context`] dependencies: Configuration[`dependencies`] diff --git a/sources/@roots/bud-framework/src/registry/events.ts b/sources/@roots/bud-framework/src/registry/events.ts index a755d5d8ee..f77664e0c0 100644 --- a/sources/@roots/bud-framework/src/registry/events.ts +++ b/sources/@roots/bud-framework/src/registry/events.ts @@ -9,6 +9,7 @@ export interface Events { 'compiler.before': [Bud] 'compiler.done': [Bud, StatsCompilation] 'config.after': [Bud] + 'config.before': [Bud] register: [Bud] 'server.after': [Bud] 'server.before': [Bud] diff --git a/sources/@roots/bud-framework/src/service.ts b/sources/@roots/bud-framework/src/service.ts index 6bba787873..cf196616fd 100644 --- a/sources/@roots/bud-framework/src/service.ts +++ b/sources/@roots/bud-framework/src/service.ts @@ -53,6 +53,11 @@ interface Contract { */ configAfter?(app: Bud): Promise + /** + * Before config callback + */ + configBefore?(app: Bud): Promise + /** * Return the bud instance */ @@ -247,11 +252,10 @@ abstract class BaseContainer extends Container implements Contract { */ @bind public catch(error: BudError | string): never { - if (typeof error === `string`) { - throw BudError.normalize(error) - } + if (!error) + throw BudError.normalize(`An error occured in ${this.label}`) - throw error + throw BudError.normalize(error) } /** diff --git a/sources/@roots/bud-framework/src/services/cache/index.ts b/sources/@roots/bud-framework/src/services/cache/index.ts index 00db168031..1ae88db269 100644 --- a/sources/@roots/bud-framework/src/services/cache/index.ts +++ b/sources/@roots/bud-framework/src/services/cache/index.ts @@ -88,7 +88,7 @@ export interface Cache { /** * Set cache type */ - setType(value: CacheCallback): this + setType(value: CacheCallback<`filesystem` | `memory`>): this /** * Cache type diff --git a/sources/@roots/bud-framework/test/bud.test.ts b/sources/@roots/bud-framework/test/bud.test.ts index 07ae6bb5d2..0b0ea01775 100644 --- a/sources/@roots/bud-framework/test/bud.test.ts +++ b/sources/@roots/bud-framework/test/bud.test.ts @@ -1,7 +1,6 @@ +import {Bud} from '@roots/bud-framework/bud' import {describe, expect, it} from 'vitest' -import {Bud} from '../src/bud.js' - describe(`Bud`, function () { it(`is a class`, () => { expect(Bud).toBeInstanceOf(Function) diff --git a/sources/@roots/bud-framework/test/configuration/configuration.test.ts b/sources/@roots/bud-framework/test/configuration/configuration.test.ts index 7064bfadc1..c847f1034a 100644 --- a/sources/@roots/bud-framework/test/configuration/configuration.test.ts +++ b/sources/@roots/bud-framework/test/configuration/configuration.test.ts @@ -1,26 +1,26 @@ -import '../../src/index.js' +import {parse} from 'node:path' import {Bud, factory} from '@repo/test-kit' +import {BudError} from '@roots/bud-support/errors' import {beforeEach, describe, expect, it, vi} from 'vitest' -import Configuration from '../../src/configuration/configuration.js' +import Configuration from '../../src/configuration/index.js' import {File} from '../../src/context.js' -import {BudError} from '@roots/bud-support/errors' -import {parse} from 'node:path' +import '../../src/index.js' const testFileDescription: File = { - name: `test.config.js`, - local: false, bud: false, - path: `foo/test.config.js`, - target: `base`, - type: `module`, - parsed: parse(`foo/test.config.js`), - size: 0, - sha1: `abcdefg`, + local: false, mode: 0, // @ts-ignore intentionally invalid module: undefined, + name: `test.config.js`, + parsed: parse(`foo/test.config.js`), + path: `foo/test.config.js`, + sha1: `abcdefg`, + size: 0, + target: `base`, + type: `module`, } describe(`@roots/bud-framework/configuration`, function () { @@ -55,40 +55,4 @@ describe(`@roots/bud-framework/configuration`, function () { expect(error).toBeInstanceOf(BudError) }) }) - - it(`calls dynamicConfig when config is a fn`, async () => { - const dynamicSpy = vi.spyOn(configuration, `dynamicConfig`) - const configFn = vi.fn(async bud => bud) - - const testDynamicConfig = { - ...testFileDescription, - dynamic: true, - module: async () => configFn, - } - await configuration.run(testDynamicConfig) - - expect(dynamicSpy).toHaveBeenCalledWith(configFn) - expect(configFn).toHaveBeenCalledWith(bud) - }) - - it(`calls staticConfig when config is static`, async () => { - const staticSpy = vi.spyOn(configuration, `staticConfig`) - - const testStaticConfig = { - ...testFileDescription, - dynamic: false, - module: async () => ({ - foo: `bar`, - info: [`foo`, `bar`], - }), - } - await configuration.run(testStaticConfig) - - expect(staticSpy).toHaveBeenCalledWith( - expect.objectContaining({ - foo: `bar`, - info: [`foo`, `bar`], - }), - ) - }) }) diff --git a/sources/@roots/bud-framework/test/configuration/index.test.ts b/sources/@roots/bud-framework/test/configuration/index.test.ts deleted file mode 100644 index 2c17404876..0000000000 --- a/sources/@roots/bud-framework/test/configuration/index.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {type Bud, factory} from '@repo/test-kit' -import {beforeEach, describe, expect, it, vi} from 'vitest' - -import * as configuration from '../../src/configuration/index.js' - -describe(`bud.configuration`, function () { - let bud: Bud - - beforeEach(async () => { - vi.clearAllMocks() - bud = await factory() - }) - - it(`should have a process fn`, () => { - expect(configuration.process).toBeInstanceOf(Function) - }) -}) diff --git a/sources/@roots/bud-hooks/src/base/base.ts b/sources/@roots/bud-hooks/src/base/base.ts index 20a6f42144..29db57a4ed 100644 --- a/sources/@roots/bud-hooks/src/base/base.ts +++ b/sources/@roots/bud-hooks/src/base/base.ts @@ -39,11 +39,18 @@ export abstract class Hooks { } @bind - public catch(e: Error, id?: string, iteration?: number): void { + public catch(e: Error, id?: string, iteration?: number): never { if (!id) { - throw new BudError(e.message ?? `${e}`) + throw BudError.normalize(e, { + details: `An error occured while running a hook`, + thrownBy: import.meta.url, + }) } - throw new BudError(`problem running hook ${id}`, {origin: e}) + + throw BudError.normalize(e, { + details: `An error occured while running hook id: ${id}`, + thrownBy: import.meta.url, + }) } /** @@ -51,6 +58,6 @@ export abstract class Hooks { */ @bind public has(path: T): boolean { - return isUndefined(this.store[path]) === false + return !isUndefined(this.store?.[path]) } } diff --git a/sources/@roots/bud-hooks/src/event/event.ts b/sources/@roots/bud-hooks/src/event/event.ts index 35bdaa7c05..9e0cff132c 100644 --- a/sources/@roots/bud-hooks/src/event/event.ts +++ b/sources/@roots/bud-hooks/src/event/event.ts @@ -2,6 +2,7 @@ import type {Bud} from '@roots/bud-framework' import type {Events, EventsStore} from '@roots/bud-framework/registry' import {bind} from '@roots/bud-support/decorators/bind' +import {BudError} from '@roots/bud-support/errors' import {Hooks} from '../base/base.js' @@ -19,18 +20,16 @@ export class EventHooks extends Hooks { ): Promise { if (!(id in this.store) || !this.store[id].length) return this.app - await Promise.all( - this.store[id].map(async (action: any) => { - if (typeof action === `undefined`) return - - this.logger.info(`running ${id} callback:`, action) - - await action(...value).catch((error: Error) => { - this.logger.error(`problem running ${id} callback`) - throw error - }) - }), - ).catch(this.catch) + for await (const action of this.store[id] as any) { + this.logger.info(`running ${id}`) + try { + await action(...value) + await this.app.resolvePromises() + } catch (error) { + this.logger.error(`problem running ${id} callback`) + throw BudError.normalize(error) + } + } return this.app } diff --git a/sources/@roots/bud-imagemin/README.md b/sources/@roots/bud-imagemin/README.md index 4311c88087..f493a23922 100644 --- a/sources/@roots/bud-imagemin/README.md +++ b/sources/@roots/bud-imagemin/README.md @@ -60,7 +60,7 @@ In addition to the preconfigured `?as=webp` parameter, you may define additional For example, this custom generator will convert an asset to `png` at 80% quality when `?as=png` is appended to an image asset path. -```typescript title="bud.config.js" +```ts title=bud.config.ts export default async (bud) => { bud.imagemin.sharp.setGenerator(`png`, { options: { @@ -75,7 +75,7 @@ export default async (bud) => { The preset label does not necessarily need to match one of the sharp encoder keys. For example, you might want to set up something a little more persnickity like: -```typescript title="bud.config.js" +```ts title=bud.config.ts export default async (bud) => { bud.imagemin.addPreset(`webp@50`, { options: { @@ -109,7 +109,7 @@ import image from "./images/image.jpg?width=500&height=500"; You may wish to customize the encoder settings. This is done with **bud.imagemin.encode**. -```typescript title="bud.config.js" +```ts title=bud.config.ts export default async (bud) => { bud.imagemin.encode(`jpeg`, { quality: 50 }); bud.imagemin.encode(`svg`, { multipass: false }); @@ -118,7 +118,7 @@ export default async (bud) => { ### Enable lossless compression -```typescript +```ts title=bud.config.ts export default async (bud) => { bud.imagemin.lossless(); }; diff --git a/sources/@roots/bud-imagemin/docs/01-request-query-parameters.md b/sources/@roots/bud-imagemin/docs/01-request-query-parameters.md index d6336f08bd..b66bda9ab2 100644 --- a/sources/@roots/bud-imagemin/docs/01-request-query-parameters.md +++ b/sources/@roots/bud-imagemin/docs/01-request-query-parameters.md @@ -24,7 +24,7 @@ In addition to the preconfigured `?as=webp` parameter, you may define additional For example, this custom generator will convert an asset to `png` at 80% quality when `?as=png` is appended to an image asset path. -```typescript title="bud.config.js" +```ts title=bud.config.ts export default async bud => { bud.imagemin.sharp.setGenerator(`png`, { options: { @@ -39,7 +39,7 @@ export default async bud => { The preset label does not necessarily need to match one of the sharp encoder keys. For example, you might want to set up something a little more persnickity like: -```typescript title="bud.config.js" +```ts title=bud.config.ts export default async bud => { bud.imagemin.addPreset(`webp@50`, { options: { diff --git a/sources/@roots/bud-imagemin/docs/02-setting-encoder-options.md b/sources/@roots/bud-imagemin/docs/02-setting-encoder-options.md index 3e6e7ca6bb..843329e457 100644 --- a/sources/@roots/bud-imagemin/docs/02-setting-encoder-options.md +++ b/sources/@roots/bud-imagemin/docs/02-setting-encoder-options.md @@ -4,7 +4,7 @@ title: Setting encoder options You may wish to customize the encoder settings. This is done with **bud.imagemin.encode**. -```typescript title="bud.config.js" +```ts title=bud.config.ts export default async bud => { bud.imagemin.encode(`jpeg`, {quality: 50}) bud.imagemin.encode(`svg`, {multipass: false}) @@ -13,7 +13,7 @@ export default async bud => { ### Enable lossless compression -```typescript +```ts title=bud.config.ts export default async bud => { bud.imagemin.lossless() } diff --git a/sources/@roots/bud-minify/src/extension/index.ts b/sources/@roots/bud-minify/src/extension/index.ts index e2e70698a8..97b8b86637 100644 --- a/sources/@roots/bud-minify/src/extension/index.ts +++ b/sources/@roots/bud-minify/src/extension/index.ts @@ -1,7 +1,7 @@ -import type {Bud} from '@roots/bud-framework' import type {BudMinimizeCSSPublicInterface} from '@roots/bud-minify/minify-css' import type {BudMinimizeJSPublicInterface} from '@roots/bud-minify/minify-js' +import {Bud} from '@roots/bud-framework' import {Extension} from '@roots/bud-framework/extension' import {dependsOn} from '@roots/bud-framework/extension/decorators/dependsOn' import {expose} from '@roots/bud-framework/extension/decorators/expose' @@ -13,7 +13,7 @@ import {bind} from '@roots/bud-support/decorators/bind' * Minimizer configuration */ @label(`@roots/bud-minify`) -@expose(`minify`) +@expose(`minimizers`) @dependsOn([`@roots/bud-minify/minify-css`, `@roots/bud-minify/minify-js`]) @production class BudMinimize extends Extension { @@ -32,7 +32,7 @@ class BudMinimize extends Extension { */ @bind public override async buildBefore(bud: Bud) { - bud.hooks.on(`build.optimization.minimize`, () => true) + bud.hooks.on(`build.optimization.minimize`, this.enabled) } /** @@ -40,6 +40,8 @@ class BudMinimize extends Extension { */ @bind public override async register(bud: Bud) { + bud.set(`minify`, this, false); + this.js = bud.extensions.get( `@roots/bud-minify/minify-js`, ) as BudMinimize[`js`] @@ -57,6 +59,16 @@ class BudMinimize extends Extension { bud.extensions.get(`@roots/bud-minify/minify-css`), ) } + + @bind + public override enable(value: boolean | Bud = true) { + this.enabled = value instanceof Bud ? true : value + this.logger.log(this.enabled ? `enabled` : `disabled`) + + this.app.hooks.on(`build.optimization.minimize`, this.enabled) + + return this + } } export {BudMinimize as default, type BudMinimize} diff --git a/sources/@roots/bud-minify/src/index.ts b/sources/@roots/bud-minify/src/index.ts index b119634f60..f86651bf52 100644 --- a/sources/@roots/bud-minify/src/index.ts +++ b/sources/@roots/bud-minify/src/index.ts @@ -21,14 +21,34 @@ import BudMinimize from '@roots/bud-minify/extension' declare module '@roots/bud-framework' { interface Bud { + /** + * ## bud.minify + * + * @deprecated Use {@link bud.minimizers} instead. + */ minify: BudMinimize /** - * @deprecated Use {@link bud.minify} instead. + * ## bud.minimizeCss + * + * @deprecated Use {@link bud.minimizers.css} instead. */ minimizeCss: BudMinimizeCSSPublicInterface + + /** + * ## bud.minimizers + * + * Configure minimizer options for CSS and JS modules + * + * @see {@link Bud.minimizers.js} + * @see {@link Bud.minimizers.css} + */ + minimizers: BudMinimize + /** - * @deprecated Use {@link bud.minify} instead. + * ## bud.terser + * + * @deprecated Use {@link bud.minimizers.js} instead. */ terser: BudMinimizeJSPublicInterface } diff --git a/sources/@roots/bud-minify/src/minify-css/extension.config.ts b/sources/@roots/bud-minify/src/minify-css/extension.config.ts index e70734eefe..b6c1be85c8 100644 --- a/sources/@roots/bud-minify/src/minify-css/extension.config.ts +++ b/sources/@roots/bud-minify/src/minify-css/extension.config.ts @@ -25,7 +25,6 @@ interface BudMinimizeCSSPublicInterface BudMinimizeCSSPublicApi, BudMinimizeCSSOptions > {} - @options({ exclude: undefined, include: undefined, diff --git a/sources/@roots/bud-minify/src/minify-js/extension.config.ts b/sources/@roots/bud-minify/src/minify-js/extension.config.ts index d8c584a196..7451b596b2 100644 --- a/sources/@roots/bud-minify/src/minify-js/extension.config.ts +++ b/sources/@roots/bud-minify/src/minify-js/extension.config.ts @@ -47,6 +47,15 @@ type BudMinimizeJSPublicInterface = StrictPublicExtensionApi< dropComments: (enable?: boolean) => BudMinimizeJSPublicApi dropConsole: (enable?: boolean) => BudMinimizeJSPublicApi dropDebugger: (enable?: boolean) => BudMinimizeJSPublicApi + /** + * Mangle output + * @deprecated Use {@link BudTerser.set} instead + * + * @example + * ```js + * bud.minimize.js.set(`terserOptions.mangle`, {}) + * ``` + */ mangle: ( mangle: OptionCallback< BudMinimizeJSPublicApi['terserOptions'], @@ -212,14 +221,18 @@ class BudMinimizeJSPublicApi /** * @deprecated Use {@link BudTerser.dropComments} instead */ - @deprecated(`bud.minify.js`, `Use bud.minify.js.dropComments instead`, [ - [`Drop comments`, `bud.minify.js.dropComments()`], - [`Preserve comments`, `bud.minify.js.dropComments(false)`], + @deprecated( + `bud.minimize.js`, + `Use bud.minimize.js.dropComments instead`, [ - `Alternative (using bud.minify.js.set)`, - `bud.minify.js.set('terserOptions.format.comments', true)`, + [`Drop comments`, `bud.minimize.js.dropComments()`], + [`Preserve comments`, `bud.minimize.js.dropComments(false)`], + [ + `Alternative (using bud.minimize.js.set)`, + `bud.minimize.js.set('terserOptions.format.comments', true)`, + ], ], - ]) + ) public comments(comments: boolean = true): this { this.set(`terserOptions.format.comments`, comments) return this @@ -228,14 +241,21 @@ class BudMinimizeJSPublicApi /** * @deprecated Use {@link BudTerser.dropDebugger} instead */ - @deprecated(`bud.minify.js`, `Use bud.minify.js.dropDebugger instead`, [ - [`Drop debugger statements`, `bud.minify.js.dropDebugger()`], - [`Preserve debugger statements`, `bud.minify.js.dropDebugger(false)`], + @deprecated( + `bud.minimize.js`, + `Use bud.minimize.js.dropDebugger instead`, [ - `Alternative (using bud.minify.js.set)`, - `bud.minify.js.set('terserOptions.compress.drop_debugger', true)`, + [`Drop debugger statements`, `bud.minimize.js.dropDebugger()`], + [ + `Preserve debugger statements`, + `bud.minimize.js.dropDebugger(false)`, + ], + [ + `Alternative (using bud.minimize.js.set)`, + `bud.minimize.js.set('terserOptions.compress.drop_debugger', true)`, + ], ], - ]) + ) public debugger(enable: boolean = true): this { this.set(`terserOptions.compress.drop_debugger`, enable) return this @@ -274,7 +294,7 @@ class BudMinimizeJSPublicApi * * @example * ```js - * bud.minify.js.set(`terserOptions.mangle`, {}) + * bud.minimize.js.set(`terserOptions.mangle`, {}) * ``` */ @bind @@ -291,13 +311,13 @@ class BudMinimizeJSPublicApi * * @example * ```js - * bud.minify.js.set(`terserOptions.minify`, () => {}) + * bud.minimize.js.set(`terserOptions.minify`, () => {}) * ``` */ - @deprecated(`bud.minify.js`, `Use bud.minify.js.setMinify instead`, [ + @deprecated(`bud.minimize.js`, `Use bud.minimize.js.setMinify instead`, [ [ `Set the minifier`, - `bud.minify.js.set('terserOptions.minify', () => minifier)`, + `bud.minimize.js.set('terserOptions.minify', () => minifier)`, ], ]) public setMinifier(minify: any): this { diff --git a/sources/@roots/bud-postcss/src/extension/index.ts b/sources/@roots/bud-postcss/src/extension/index.ts index 7209336d69..ce2e86dad2 100644 --- a/sources/@roots/bud-postcss/src/extension/index.ts +++ b/sources/@roots/bud-postcss/src/extension/index.ts @@ -9,7 +9,6 @@ import { expose, label, } from '@roots/bud-framework/extension/decorators' -import {InputError} from '@roots/bud-support/errors' import isFunction from '@roots/bud-support/lodash/isFunction' import isString from '@roots/bud-support/lodash/isString' import isUndefined from '@roots/bud-support/lodash/isUndefined' @@ -37,9 +36,7 @@ class BudPostCss extends BudPostCssOptionsApi { public getPlugin(name: string): PluginReference { name = this.normalizePluginName(name) if (!(name in this.plugins)) { - throw new InputError(`Plugin ${name} does not exist`, { - thrownBy: `bud.postcss.getPlugin`, - }) + throw new Error(`Plugin ${name} does not exist`) } return this.plugins[name] @@ -225,7 +222,7 @@ class BudPostCss extends BudPostCssOptionsApi { const plugin = this.getPlugin(this.normalizePluginName(name)) if (!plugin) { - throw new InputError(`${name} does not exist`) + throw new Error(`${name} does not exist`) } this.setPlugin(name, [ diff --git a/sources/@roots/bud-preset-wordpress/src/extension.ts b/sources/@roots/bud-preset-wordpress/src/extension.ts index 9eb31d4632..016a1c1681 100644 --- a/sources/@roots/bud-preset-wordpress/src/extension.ts +++ b/sources/@roots/bud-preset-wordpress/src/extension.ts @@ -181,6 +181,7 @@ export default class BudPresetWordPress /** * {@link Extension.boot} */ + @bind public override async boot(bud: Bud) { await this.compilerCheck(bud) @@ -191,6 +192,7 @@ export default class BudPresetWordPress /** * {@link Extension.buildBefore} */ + @bind public override async buildBefore(bud: Bud) { this.handleExclusions(bud) await this.handleHmr(bud) diff --git a/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx b/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx index 3861f0584d..1846e6b450 100644 --- a/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx +++ b/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx @@ -1,5 +1,6 @@ import BudCommand from '@roots/bud/cli/commands' import {Command, Option} from '@roots/bud-support/clipanion' +import {bind} from '@roots/bud-support/decorators/bind' /** * `bud prettier` command @@ -27,6 +28,7 @@ export class BudPrettierCommand extends BudCommand { /** * {@link Command.execute} */ + @bind public override async execute() { await this.makeBud() await this.bud.run() diff --git a/sources/@roots/bud-purgecss/src/extension/index.ts b/sources/@roots/bud-purgecss/src/extension/index.ts index d8bc282895..1fde3d6e0a 100644 --- a/sources/@roots/bud-purgecss/src/extension/index.ts +++ b/sources/@roots/bud-purgecss/src/extension/index.ts @@ -3,6 +3,7 @@ import type {Bud} from '@roots/bud-framework' import {DynamicOption, Extension} from '@roots/bud-framework/extension' import { + bind, dependsOn, expose, label, @@ -48,6 +49,7 @@ export default class BudPurgeCSS extends BudPurgeCSSPublicAPI { /** * {@link Extension.register} */ + @bind public override async register(bud: Bud) { bud.bindFacade(`purgecss`, purgecss) } @@ -55,6 +57,7 @@ export default class BudPurgeCSS extends BudPurgeCSSPublicAPI { /** * {@link Extension.buildBefore} */ + @bind public override async buildBefore(bud: Bud) { /** * Return early if purgecss is already setup diff --git a/sources/@roots/bud-react/src/react-refresh/index.ts b/sources/@roots/bud-react/src/react-refresh/index.ts index c141e15204..9eabd6e794 100644 --- a/sources/@roots/bud-react/src/react-refresh/index.ts +++ b/sources/@roots/bud-react/src/react-refresh/index.ts @@ -92,10 +92,10 @@ export default class BudReactRefresh extends Extension< const signifier = bud.swc ? `@roots/bud-react/swc-refresh` : bud.typescript && bud.typescript.babel === false - ? `@roots/bud-react/typescript-refresh` - : bud.babel || bud.typescript?.babel === true - ? `@roots/bud-react/babel-refresh` - : false + ? `@roots/bud-react/typescript-refresh` + : bud.babel || bud.typescript?.babel === true + ? `@roots/bud-react/babel-refresh` + : false if (signifier === false) { return diff --git a/sources/@roots/bud-sass/src/resolve-url/extension.ts b/sources/@roots/bud-sass/src/resolve-url/extension.ts index 8feec1f8a7..0959f298d8 100644 --- a/sources/@roots/bud-sass/src/resolve-url/extension.ts +++ b/sources/@roots/bud-sass/src/resolve-url/extension.ts @@ -1,7 +1,7 @@ import type {Bud} from '@roots/bud-framework' import {Extension} from '@roots/bud-framework/extension' -import {label} from '@roots/bud-framework/extension/decorators' +import {bind, label} from '@roots/bud-framework/extension/decorators' /** * resolve-url-loader configuration @@ -11,6 +11,7 @@ export class BudResolveUrl extends Extension { /** * {@link Extension.register} */ + @bind public override async register({build, hooks}: Bud) { /** Source loader */ const loader = await this.resolve( diff --git a/sources/@roots/bud-server/src/server/base.ts b/sources/@roots/bud-server/src/server/base.ts index 8358cae161..d2aa6adf8d 100644 --- a/sources/@roots/bud-server/src/server/base.ts +++ b/sources/@roots/bud-server/src/server/base.ts @@ -12,7 +12,6 @@ import type { import type {Server as HttpsServer} from 'node:https' import {bind} from '@roots/bud-support/decorators/bind' -import {BudError, ServerError} from '@roots/bud-support/errors' import logger from '@roots/bud-support/logger' /** @@ -39,15 +38,15 @@ export abstract class BaseServer implements Connection { */ @bind public onError(error: Error) { - const cause = BudError.normalize(error) - throw new ServerError(cause.message, {cause}) + this.logger.error(error) } + /** * Server listen event */ @bind public onListening(...param: any[]) { - this.logger.info(`listening`, ...param) + this.logger.info(...param) } /** * Server request event @@ -96,7 +95,7 @@ export abstract class BaseServer implements Connection { * Logger */ public get logger(): any { - return logger.scope(`server`, this.constructor.name.toLowerCase()) + return logger.scope(`server`) } /** diff --git a/sources/@roots/bud-server/src/service/__snapshots__/service.test.ts.snap b/sources/@roots/bud-server/src/service/__snapshots__/service.test.ts.snap deleted file mode 100644 index 81e336c1ce..0000000000 --- a/sources/@roots/bud-server/src/service/__snapshots__/service.test.ts.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`@roots/bud-server > should have availableMiddleware property 1`] = ` -{ - "cookie": "@roots/bud-server/middleware/cookie", - "dev": "@roots/bud-server/middleware/dev", - "hot": "@roots/bud-server/middleware/hot", - "proxy": "@roots/bud-server/middleware/proxy", -} -`; - -exports[`@roots/bud-server > should have enabledMiddleware property 1`] = ` -{ - "dev": "@roots/bud-server/middleware/dev", - "hot": "@roots/bud-server/middleware/hot", -} -`; diff --git a/sources/@roots/bud-server/src/service/index.ts b/sources/@roots/bud-server/src/service/index.ts index 309ace8f2c..bcf165c9fc 100644 --- a/sources/@roots/bud-server/src/service/index.ts +++ b/sources/@roots/bud-server/src/service/index.ts @@ -9,10 +9,10 @@ import type { import {Service} from '@roots/bud-framework/service' import {inject} from '@roots/bud-server/inject' import {bind} from '@roots/bud-support/decorators/bind' -import {BudError, ServerError} from '@roots/bud-support/errors' +import {BudError} from '@roots/bud-support/errors' /** - * Server service class + * {@link BudServer} */ export class Server extends Service implements BudServer { /** @@ -122,14 +122,7 @@ export class Server extends Service implements BudServer { this.application.use(this.appliedMiddleware[key]) }, ), - ).catch(error => { - this.catch( - new ServerError(`Error instantiating middleware`, { - origin: BudError.normalize(error), - thrownBy: `bud.server.applyMiddleware`, - }), - ) - }) + ).catch(this.catch) } /** @@ -137,10 +130,6 @@ export class Server extends Service implements BudServer { */ @bind public override catch(error: BudError | string): never { - if (typeof error === `string`) { - throw ServerError.normalize(error) - } - throw error } @@ -186,6 +175,7 @@ export class Server extends Service implements BudServer { /** * {@link BudServer.register} */ + @bind public override async register?(bud: Bud) { if (!bud.isDevelopment) return @@ -268,6 +258,7 @@ export class Server extends Service implements BudServer { import.meta.url, ) .then(({Server}) => new Server(this.app)) + .catch(this.catch) return this.connection } diff --git a/sources/@roots/bud-server/test/middleware-proxy.test.ts b/sources/@roots/bud-server/test/middleware-proxy.test.ts index d1205f26ff..0f085f5c14 100644 --- a/sources/@roots/bud-server/test/middleware-proxy.test.ts +++ b/sources/@roots/bud-server/test/middleware-proxy.test.ts @@ -41,7 +41,7 @@ describe(`proxy middleware`, () => { it(`should reflect changes made in bud.proxy`, async () => { bud = await factory({mode: `development`}) bud.proxy({changeOrigin: false}) - await bud.promise() + await bud.resolvePromises() expect(makeOptions(bud).changeOrigin).toBe(false) }) @@ -49,7 +49,7 @@ describe(`proxy middleware`, () => { it(`should reflect changes to URL made in bud.proxy`, async () => { bud = await factory({mode: `development`}) bud.proxy(`http://example.com`) - await bud.promise() + await bud.resolvePromises() const options = makeOptions(bud) const href = options.target && typeof options.target !== `string` && `href` in options.target ? options.target.href : undefined @@ -61,11 +61,11 @@ describe(`proxy middleware`, () => { it(`should reflect changes to URL made in bud.proxy`, async () => { bud = await factory({mode: `development`}) bud.proxy(`https://example.com`) - await bud.promise() + await bud.resolvePromises() expect(makeOptions(bud).protocolRewrite).toBe(`https`) bud.proxy(`http://example.com`) - await bud.promise() + await bud.resolvePromises() expect(makeOptions(bud).protocolRewrite).toBe(undefined) }) }) diff --git a/sources/@roots/bud-support/src/decorators/deprecated.ts b/sources/@roots/bud-support/src/decorators/deprecated.ts index 4dc58e116c..5675a6e033 100644 --- a/sources/@roots/bud-support/src/decorators/deprecated.ts +++ b/sources/@roots/bud-support/src/decorators/deprecated.ts @@ -17,6 +17,11 @@ export const deprecated = (target: any, key: string, descriptor: any) => { const originalMethod = descriptor.value descriptor.value = function (...args: any) { + const warn = + this.logger && typeof this.logger.warn === `function` + ? this.logger.warn + : logger.warn + const warning = [ chalk.yellow(`${method}.${key}`), `has been deprecated and will be removed in a future release.`, @@ -41,7 +46,7 @@ export const deprecated = ]), ) - logger.warn(...warning) + warn(...warning) return originalMethod.apply(this, args) } diff --git a/sources/@roots/bud-support/src/decorators/index.ts b/sources/@roots/bud-support/src/decorators/index.ts index 5f043fd13d..d6b596010b 100644 --- a/sources/@roots/bud-support/src/decorators/index.ts +++ b/sources/@roots/bud-support/src/decorators/index.ts @@ -1,2 +1,3 @@ export {bind} from './bind.js' export {deprecated} from './deprecated.js' +export {log} from './log.js' diff --git a/sources/@roots/bud-support/src/decorators/log.ts b/sources/@roots/bud-support/src/decorators/log.ts new file mode 100644 index 0000000000..f3dabd2e5e --- /dev/null +++ b/sources/@roots/bud-support/src/decorators/log.ts @@ -0,0 +1,17 @@ +export function log(...message: Array) { + return function (target: any, propertyKey: string, descriptor: any) { + const originalMethod = descriptor.value + + descriptor.value = function (...args: any[]) { + if (this.logger && typeof this.logger.log === `function`) { + const label = this.label ?? this.constructor.name + if (label) this.logger.scope(label) + this.logger.log(...message) + } + + return originalMethod.apply(this, args) + } + + return descriptor + } +} diff --git a/sources/@roots/bud-support/src/errors/index.ts b/sources/@roots/bud-support/src/errors/index.ts index 5533af4ead..9d14ec5893 100644 --- a/sources/@roots/bud-support/src/errors/index.ts +++ b/sources/@roots/bud-support/src/errors/index.ts @@ -1,205 +1,168 @@ -import {join} from 'node:path' +import isObject from '@roots/bud-support/lodash/isObject' -import args from '@roots/bud-support/utilities/args' -import cleanStack from 'clean-stack' +const cwd = `${global.process.cwd()}` -const cwd = `${ - global.process.env.PROJECT_CWD ?? - global.process.env.INIT_CWD ?? - global.process.cwd() -}` - -const basePath = args?.basedir - ? join(cwd, args.basedir) - : global.process.env.BUD_BASEDIR - ? join(cwd, `${global.process.env.BUD_BASEDIR}`) - : cwd +const clean = (message?: string) => { + return message + ?.replace(new RegExp(cwd, `g`), `.`) + .replace(/\s+/g, ` `) + .replace(/file:\/\//g, ``) +} -/** - * Props for Bud errors - */ -interface BudErrorProps extends Error { - details: string - docs: URL - file: { - module: any - name: string - path: string - sha1: string +interface BudErrorProps { + details?: string + docs?: URL + file?: { + module?: any + name?: string + path?: string + sha1?: string } - instance: string - isBudError: true - issue: URL - message: string - origin: BudError | Error | string - thrownBy: string + instance?: string + issue?: URL + origin?: BudError | Error + thrownBy?: string | URL } /** * Error base class */ class BudError extends Error { - /** - * Normalize error - */ - public static normalize(error: unknown) { - if (error instanceof BudError) return error - - if (error instanceof Error) { - const {message, ...rest} = error - return new BudError(message, rest) - } - - if (typeof error === `string`) { - return new BudError(error) - } - - return new BudError(`unknown error`) - } /** * Details */ - public declare details: false | string + public declare details?: BudErrorProps[`details`] /** * Documentation URL */ - public declare docs: false | URL + public declare docs?: BudErrorProps[`docs`] + /** * Information about file related to error */ - public declare file: { - module: any - name: string - path: string - sha1: string - } + public declare file?: BudErrorProps[`file`] /** * Instance name containing error */ - public declare instance: `default` | string + public declare instance?: BudErrorProps[`instance`] /** - * Used to identify Bud errors + * Issue tracker URL */ - public isBudError = true + public declare issue?: BudErrorProps[`issue`] /** - * Issue tracker URL + * Original error */ - public declare issues: false | URL + public declare origin?: BudErrorProps[`origin`] /** - * Error display name + * Name of method that threw error */ - public override name = `BudError` + public declare thrownBy?: BudErrorProps[`thrownBy`] /** - * Original error + * Is BudError */ - public declare origin: BudError | Error | string + public isBudError = true /** - * Name of method that threw error + * Normalize error */ - public declare thrownBy: false | string + public static normalize( + source: unknown, + options: BudErrorProps = {}, + ): BudError { + if (source instanceof BudError) { + Object.entries(options).map(([key, value]) => { + source[key as keyof BudErrorProps] = value + }) + return source + } + + if (source instanceof Error) { + return new BudError(source.message, options) + } + + if (typeof source === `string`) { + return new BudError(source, options) + } + + return new BudError(`An unknown error occured`, options) + } /** * Class constructor */ - public constructor( - message: string, - options: Partial = {}, - ) { + public constructor(message: string, options: BudErrorProps = {}) { super(message) - Object.assign(this, options) - Object.assign(this, message) + this.isBudError = true - if (!this.instance) this.instance = `default` + this.name = this.constructor.name + this.message = (clean(message) ?? message)?.replace(/.*Error:/g, ``) - if (this.message) { - this.message = this.message - .replaceAll(/file:\/\//g, ``) - .replaceAll(new RegExp(basePath, `g`), ``) + if (options.details) { + this.details = clean(options.details) } - if (this.stack) { - this.stack = cleanStack(this.stack, { - basePath, - pathFilter: path => - !path.includes(`react-reconciler`) && - !path.includes(`bud-support/lib/errors`), - pretty: true, - }).replaceAll(/file:\/\//g, ``) + if (options.docs) { + this.docs = options.docs + } + + if (options.file) { + this.file = { + ...options.file, + path: clean(options.file.path), + } + } + + if (options.instance) { + this.instance = options.instance } - if (this.message) { - this.message = cleanStack(this.message, { - basePath, - pathFilter: path => - !path.includes(`react-reconciler`) && - !path.includes(`bud-support/lib/errors`), - pretty: true, - }).replaceAll(/file:\/\//g, ``) + if (options.issue) { + this.issue = options.issue } - if (this.thrownBy) { - this.thrownBy = this.thrownBy - .replace(new RegExp(basePath, `g`), ``) - .replaceAll(/file:\/\//g, ``) + if (options.origin) { + this.origin = + isObject(options.origin) && `isBudError` in options.origin + ? options.origin + : BudError.normalize(options.origin) } - if (this.file) { - this.file.path = this.file.path - .replaceAll(new RegExp(basePath, `g`), ``) - .replaceAll(/file:\/\//g, ``) + if (options.thrownBy) { + this.thrownBy = + options.thrownBy instanceof URL + ? options.thrownBy.toString() + : options.thrownBy + } + + if (this.stack) { + this.stack = this.stack + .split(`\n`) + .filter((line, i) => i > 0 && !line.includes(`bud-support`)) + .map(clean) + .filter(Boolean) + .join(`\n`) } } } -/** - * ModuleError - */ -class ModuleError extends BudError { - public override name = `ModuleError` -} +class ModuleError extends BudError {} -/** - * ConfigError - */ -class ConfigError extends BudError { - public override name = `ConfigurationError` -} +class ConfigError extends BudError {} -/** - * InputError - */ -class InputError extends BudError { - public override name = `InputError` -} +class InputError extends BudError {} -/** - * CompilerError - */ -class CompilerError extends BudError { - public override name = `CompilerError` -} +class CompilerError extends BudError {} -/** - * ServerError - */ -class ServerError extends BudError { - public override name = `ServerError` -} +class ServerError extends BudError {} -/** - * ExtensionError - */ -class ExtensionError extends BudError { - public override name = `ExtensionError` -} +class ExtensionError extends BudError {} export { BudError, diff --git a/sources/@roots/bud-support/src/filesystem/index.ts b/sources/@roots/bud-support/src/filesystem/index.ts index 19d42f3744..21488c47ff 100644 --- a/sources/@roots/bud-support/src/filesystem/index.ts +++ b/sources/@roots/bud-support/src/filesystem/index.ts @@ -14,8 +14,9 @@ export const get = (basedir?: string) => { if (filesystem) return filesystem if (typeof basedir !== `string`) - throw new BudError( + throw BudError.normalize( `filesystem not initialized. basedir arg required for initialization.`, + {thrownBy: import.meta.url}, ) filesystem = new Filesystem(basedir) diff --git a/sources/@roots/bud-support/src/utilities/files.ts b/sources/@roots/bud-support/src/utilities/files.ts index 44ae3c062f..6c284570f3 100644 --- a/sources/@roots/bud-support/src/utilities/files.ts +++ b/sources/@roots/bud-support/src/utilities/files.ts @@ -250,8 +250,10 @@ function getFileType( } function getFileTarget(file: {name?: string}) { - if (file.name?.includes(`production`)) return `production` - if (file.name?.includes(`development`)) return `development` + if (file.name?.includes(`production`) || file.name?.includes(`prod`)) + return `production` + if (file.name?.includes(`development`) || file.name?.includes(`dev`)) + return `development` return `base` } diff --git a/sources/@roots/bud-support/src/utilities/paths.ts b/sources/@roots/bud-support/src/utilities/paths.ts index 1e09016e72..da3c6b70d5 100644 --- a/sources/@roots/bud-support/src/utilities/paths.ts +++ b/sources/@roots/bud-support/src/utilities/paths.ts @@ -60,7 +60,7 @@ const get = (basedir?: string): paths => { if (paths) return paths if (!basedir) - throw new BudError( + throw BudError.normalize( `directory is required if paths not already initialized`, { details: `\ diff --git a/sources/@roots/bud-support/src/value/index.ts b/sources/@roots/bud-support/src/value/index.ts index 9b9d34292d..b6070a19cb 100644 --- a/sources/@roots/bud-support/src/value/index.ts +++ b/sources/@roots/bud-support/src/value/index.ts @@ -56,6 +56,7 @@ class Value { public static typeOf(value: T | Value): string { return Value.isValue(value) ? typeof value.identity : typeof value } + /** * For type checking * diff --git a/sources/@roots/bud-support/src/which-pm/pmString.ts b/sources/@roots/bud-support/src/which-pm/pmString.ts index 9f3878b341..6a0e7df1cd 100644 --- a/sources/@roots/bud-support/src/which-pm/pmString.ts +++ b/sources/@roots/bud-support/src/which-pm/pmString.ts @@ -1,8 +1,6 @@ -import type { Responses } from "./index.js" +import type {Responses} from './index.js' -export const parse = ( - pmString?: string, -): Responses => { +export const parse = (pmString?: string): Responses => { if (!pmString) return false if (pmString.match(/yarn(\/|@)(3|4).*/)) return `yarn` diff --git a/sources/@roots/bud-swc/README.md b/sources/@roots/bud-swc/README.md index 5ad9afa319..9028accd93 100644 --- a/sources/@roots/bud-swc/README.md +++ b/sources/@roots/bud-swc/README.md @@ -44,13 +44,13 @@ This is not recommended if you want to use other extensions which manipulate swc You can configure `jsc` with the `bud.swc.setJsc` method: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setJsc({ baseUrl: `/base/url/`, }); ``` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setJsc((jsc = {}) => ({ ...jsc, baseUrl: `/base/url/`, @@ -63,7 +63,7 @@ Many `jsc` options have associated helper methods which don't require using `bud Use the `bud.swc.setBaseUrl` method to configure `jsc.baseUrl` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setBaseUrl(`/base/url/`); ``` @@ -71,7 +71,7 @@ bud.swc.setBaseUrl(`/base/url/`); Use the `bud.swc.setExternalHelpers` method to configure `jsc.externalHelpers` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setExternalHelpers(true); ``` @@ -79,7 +79,7 @@ bud.swc.setExternalHelpers(true); Use the `bud.swc.setExperimental` method to configure `jsc.experimental` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setExperimental({ plugins: [] }); ``` @@ -89,7 +89,7 @@ If you want to set `jsc.experimental.plugins` you may wish to use the [`bud.swc. Use the `bud.swc.setLoose` method to configure `jsc.loose` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setLoose(true); ``` @@ -97,7 +97,7 @@ bud.swc.setLoose(true); Use the `bud.swc.setMinify` method to configure `jsc.minify` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setMinify(true); ``` @@ -107,7 +107,7 @@ To configure the parser you can use [bud.swc.setParser]. Example: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setParser({ decorators: false }); ``` @@ -117,7 +117,7 @@ Note that `jsx.parser.syntax`, `jsc.parser.jsx` and `jsc.parser.tsx` will be ove Use the `bud.swc.preserveAllComments` method to oconfigure `jsc.preserveAllComments` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.preserveAllComments(false); ``` @@ -125,7 +125,7 @@ bud.swc.preserveAllComments(false); Use the `bud.swc.setTarget` method to configure `jsc.target` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setTarget(`es5`); ``` @@ -133,7 +133,7 @@ bud.swc.setTarget(`es5`); Use the `bud.swc.setTransform` method to configure `jsc.transform` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setTransform({}); ``` @@ -141,7 +141,7 @@ bud.swc.setTransform({}); SWC supports both `ecmascript` and `TypeScript`. If you want to make changes to the `jsc` config which are only applied to a specific syntax, you can make overrides using `bud.swc.ecmascript` and `bud.swc.typescript`, respectively. -```js title=bud.config.js +```ts title=bud.config.ts bud.swc.ecmascript.setKeepClassNames(true); bud.swc.typescript.setKeepClassNames(false); ``` @@ -152,11 +152,11 @@ All of the above `jsc.*` options work the same way as detailed above. Use the `bud.swc.setPlugins` method to configure `experimental.plugins`: -```js title=bud.config.js +```ts title=bud.config.ts bud.swc.setPlugins([["some-swc-plugin", {}]]); ``` -```js title=bud.config.js +```ts title=bud.config.ts bud.swc.setPlugins((plugins = []) => [...plugins, ["some-swc-plugin", {}]]); ``` @@ -164,7 +164,7 @@ bud.swc.setPlugins((plugins = []) => [...plugins, ["some-swc-plugin", {}]]); Use the `bud.swc.setEnv` method to configure swc `env` options: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setEnv({ targets: `Chrome >= 48`, }); @@ -174,7 +174,7 @@ bud.swc.setEnv({ Use the `bud.swc.setSourceMaps` method to configure the swc `sourceMaps` option: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setSourceMaps(`inline`); ``` diff --git a/sources/@roots/bud-swc/docs/01-configuration.md b/sources/@roots/bud-swc/docs/01-configuration.md index 988474f668..2d4ccbd4a9 100644 --- a/sources/@roots/bud-swc/docs/01-configuration.md +++ b/sources/@roots/bud-swc/docs/01-configuration.md @@ -14,13 +14,13 @@ This is not recommended if you want to use other extensions which manipulate swc You can configure `jsc` with the `bud.swc.setJsc` method: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setJsc({ baseUrl: `/base/url/`, }) ``` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setJsc((jsc = {}) => ({ ...jsc, baseUrl: `/base/url/`, @@ -33,7 +33,7 @@ Many `jsc` options have associated helper methods which don't require using `bud Use the `bud.swc.setBaseUrl` method to configure `jsc.baseUrl` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setBaseUrl(`/base/url/`) ``` @@ -41,7 +41,7 @@ bud.swc.setBaseUrl(`/base/url/`) Use the `bud.swc.setExternalHelpers` method to configure `jsc.externalHelpers` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setExternalHelpers(true) ``` @@ -49,7 +49,7 @@ bud.swc.setExternalHelpers(true) Use the `bud.swc.setExperimental` method to configure `jsc.experimental` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setExperimental({plugins: []}) ``` @@ -59,7 +59,7 @@ If you want to set `jsc.experimental.plugins` you may wish to use the [`bud.swc. Use the `bud.swc.setLoose` method to configure `jsc.loose` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setLoose(true) ``` @@ -67,7 +67,7 @@ bud.swc.setLoose(true) Use the `bud.swc.setMinify` method to configure `jsc.minify` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setMinify(true) ``` @@ -77,7 +77,7 @@ To configure the parser you can use [bud.swc.setParser]. Example: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setParser({decorators: false}) ``` @@ -87,7 +87,7 @@ Note that `jsx.parser.syntax`, `jsc.parser.jsx` and `jsc.parser.tsx` will be ove Use the `bud.swc.preserveAllComments` method to oconfigure `jsc.preserveAllComments` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.preserveAllComments(false) ``` @@ -95,7 +95,7 @@ bud.swc.preserveAllComments(false) Use the `bud.swc.setTarget` method to configure `jsc.target` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setTarget(`es5`) ``` @@ -103,7 +103,7 @@ bud.swc.setTarget(`es5`) Use the `bud.swc.setTransform` method to configure `jsc.transform` -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setTransform({}) ``` @@ -111,7 +111,7 @@ bud.swc.setTransform({}) SWC supports both `ecmascript` and `TypeScript`. If you want to make changes to the `jsc` config which are only applied to a specific syntax, you can make overrides using `bud.swc.ecmascript` and `bud.swc.typescript`, respectively. -```js title=bud.config.js +```ts title=bud.config.ts bud.swc.ecmascript.setKeepClassNames(true) bud.swc.typescript.setKeepClassNames(false) ``` @@ -122,11 +122,11 @@ All of the above `jsc.*` options work the same way as detailed above. Use the `bud.swc.setPlugins` method to configure `experimental.plugins`: -```js title=bud.config.js +```ts title=bud.config.ts bud.swc.setPlugins([['some-swc-plugin', {}]]) ``` -```js title=bud.config.js +```ts title=bud.config.ts bud.swc.setPlugins((plugins = []) => [...plugins, ['some-swc-plugin', {}]]) ``` @@ -134,7 +134,7 @@ bud.swc.setPlugins((plugins = []) => [...plugins, ['some-swc-plugin', {}]]) Use the `bud.swc.setEnv` method to configure swc `env` options: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setEnv({ targets: `Chrome >= 48`, }) @@ -144,6 +144,6 @@ bud.swc.setEnv({ Use the `bud.swc.setSourceMaps` method to configure the swc `sourceMaps` option: -```ts title=bud.config.js +```ts title=bud.config.ts bud.swc.setSourceMaps(`inline`) ``` diff --git a/sources/@roots/bud-tailwindcss/README.md b/sources/@roots/bud-tailwindcss/README.md index d0f2d77e95..645ce8187c 100644 --- a/sources/@roots/bud-tailwindcss/README.md +++ b/sources/@roots/bud-tailwindcss/README.md @@ -40,7 +40,7 @@ bud.js allows for you to write your tailwind config in CommonJS, ESM, TypeScript You can configure tailwind directly in your bud configuration file using `bud.tailwind.setConfig`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.setConfig({ content: [bud.path(`@src/**/*.{ts,php}`)], theme: {}, @@ -54,7 +54,7 @@ bud.tailwind.setConfig({ You can set the tailwindcss `content` option with `bud.tailwind.setContent`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.setContent([bud.path(`@src/**/*.{ts,php}`)]); ``` @@ -62,7 +62,7 @@ bud.tailwind.setContent([bud.path(`@src/**/*.{ts,php}`)]); You can set the tailwindcss `theme` option with `bud.tailwind.setTheme`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.setTheme({ colors: { primary: `#000000` }, }); @@ -72,7 +72,7 @@ bud.tailwind.setTheme({ You can extend the tailwindcss `theme` option with `bud.tailwind.extendTheme`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.extendTheme({ colors: { primary: `#000000` }, }); @@ -84,7 +84,7 @@ This is usually preferred over `bud.tailwind.setTheme` as it will merge your the You can set the tailwindcss `plugins` option with `bud.tailwind.setPlugins`. -```ts title=bud.config.js +```ts title=bud.config.ts import forms from "@tailwindcss/forms"; export default async (bud) => { @@ -96,7 +96,7 @@ export default async (bud) => { You can use resolved tailwind values in your bud config files by referencing `bud.tailwind.theme`: -```ts title=bud.config.js +```ts title=bud.config.ts export default async (bud) => { console.log(`colors`, bud.tailwind.theme.colors); }; @@ -104,7 +104,7 @@ export default async (bud) => { You can also use `bud.tailwind.getTheme`, which allows you to pass a key to get a specific value: -```ts title=bud.config.js +```ts title=bud.config.ts export default async (bud) => { console.log(`colors`, bud.tailwind.getTheme(`colors`)); }; diff --git a/sources/@roots/bud-tailwindcss/docs/01-configuration.md b/sources/@roots/bud-tailwindcss/docs/01-configuration.md index 1ebb37cbc5..d65bcd9d21 100644 --- a/sources/@roots/bud-tailwindcss/docs/01-configuration.md +++ b/sources/@roots/bud-tailwindcss/docs/01-configuration.md @@ -8,7 +8,7 @@ bud.js allows for you to write your tailwind config in CommonJS, ESM, TypeScript You can configure tailwind directly in your bud configuration file using `bud.tailwind.setConfig`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.setConfig({ content: [bud.path(`@src/**/*.{ts,php}`)], theme: {}, @@ -22,7 +22,7 @@ bud.tailwind.setConfig({ You can set the tailwindcss `content` option with `bud.tailwind.setContent`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.setContent([bud.path(`@src/**/*.{ts,php}`)]) ``` @@ -30,7 +30,7 @@ bud.tailwind.setContent([bud.path(`@src/**/*.{ts,php}`)]) You can set the tailwindcss `theme` option with `bud.tailwind.setTheme`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.setTheme({ colors: {primary: `#000000`}, }) @@ -40,7 +40,7 @@ bud.tailwind.setTheme({ You can extend the tailwindcss `theme` option with `bud.tailwind.extendTheme`. -```ts title=bud.config.js +```ts title=bud.config.ts bud.tailwind.extendTheme({ colors: {primary: `#000000`}, }) @@ -52,7 +52,7 @@ This is usually preferred over `bud.tailwind.setTheme` as it will merge your the You can set the tailwindcss `plugins` option with `bud.tailwind.setPlugins`. -```ts title=bud.config.js +```ts title=bud.config.ts import forms from '@tailwindcss/forms' export default async bud => { @@ -64,7 +64,7 @@ export default async bud => { You can use resolved tailwind values in your bud config files by referencing `bud.tailwind.theme`: -```ts title=bud.config.js +```ts title=bud.config.ts export default async bud => { console.log(`colors`, bud.tailwind.theme.colors) } @@ -72,7 +72,7 @@ export default async bud => { You can also use `bud.tailwind.getTheme`, which allows you to pass a key to get a specific value: -```ts title=bud.config.js +```ts title=bud.config.ts export default async bud => { console.log(`colors`, bud.tailwind.getTheme(`colors`)) } diff --git a/sources/@roots/bud-tailwindcss/src/extension/options.ts b/sources/@roots/bud-tailwindcss/src/extension/options.ts index d2955ffdf9..d301e91013 100644 --- a/sources/@roots/bud-tailwindcss/src/extension/options.ts +++ b/sources/@roots/bud-tailwindcss/src/extension/options.ts @@ -21,6 +21,7 @@ type BudTailwindOptionsPublicInterface = StrictPublicExtensionApi< BudTailwindOptionsApi, Options > & { + extend(theme: Partial): BudTailwindOptionsPublicInterface extendTheme( theme: Partial, ): BudTailwindOptionsPublicInterface @@ -112,10 +113,14 @@ class BudTailwindOptionsApi extend: {...(config?.theme?.extend ?? {}), [key]: value}, }, })) + this.resolveConfig() + return this } + public extend = this.extendTheme + /** * Generate import mapping * diff --git a/sources/@roots/bud-typescript/README.md b/sources/@roots/bud-typescript/README.md index 64b7acf6af..4e6a00d2b7 100644 --- a/sources/@roots/bud-typescript/README.md +++ b/sources/@roots/bud-typescript/README.md @@ -36,7 +36,7 @@ General ts configuration is handled using a standard **tsconfig.json** in your p There is a base tsconfig available for you to extend: -```json title="tsconfig.json" +```json title=tsconfig.json { "extends": "@roots/bud-typescript/tsconfig/tsconfig.json" } @@ -49,14 +49,14 @@ If you are authoring your config file in TypeScript you must use the `ts-bud` co By default TypeScript files will only be compiled to JS during builds. If you also want typechecking, you can enable it in your bud configuration: -```js title="bud.config.mjs" +```ts title=bud.config.ts bud.typescript.typecheck.enable(); ``` You may wish to configure typechecking only in production so that your development experience stays snappy: -```js title="bud.config.mjs" +```ts title=bud.config.ts bud.isProduction && bud.typescript.typecheck.enable(); ``` @@ -66,7 +66,7 @@ By default, `@roots/bud-typescript` will pass code to `@roots/bud-babel` for fur To disable babel and only use tsc: -```ts +```ts title=bud.config.ts bud.typescript.useBabel(false); ``` diff --git a/sources/@roots/bud-typescript/docs/01-config.mdx b/sources/@roots/bud-typescript/docs/01-config.mdx index 9e0fbfb326..02610b0118 100644 --- a/sources/@roots/bud-typescript/docs/01-config.mdx +++ b/sources/@roots/bud-typescript/docs/01-config.mdx @@ -6,7 +6,7 @@ General ts configuration is handled using a standard **tsconfig.json** in your p There is a base tsconfig available for you to extend: -```json title="tsconfig.json" +```json title=tsconfig.json { "extends": "@roots/bud-typescript/tsconfig/tsconfig.json" } diff --git a/sources/@roots/bud-typescript/docs/02-typechecking.mdx b/sources/@roots/bud-typescript/docs/02-typechecking.mdx index 271460c9d4..d0cf2f3cdd 100644 --- a/sources/@roots/bud-typescript/docs/02-typechecking.mdx +++ b/sources/@roots/bud-typescript/docs/02-typechecking.mdx @@ -5,13 +5,14 @@ title: Typechecking By default TypeScript files will only be compiled to JS during builds. If you also want typechecking, you can enable it in your bud configuration: -```js title="bud.config.mjs" + +```ts title=bud.config.ts bud.typescript.typecheck.enable() ``` You may wish to configure typechecking only in production so that your development experience stays snappy: -```js title="bud.config.mjs" +```ts title=bud.config.ts bud.isProduction && bud.typescript.typecheck.enable() ``` diff --git a/sources/@roots/bud-typescript/docs/03-babel.mdx b/sources/@roots/bud-typescript/docs/03-babel.mdx index 0871e67208..dea47c4b99 100644 --- a/sources/@roots/bud-typescript/docs/03-babel.mdx +++ b/sources/@roots/bud-typescript/docs/03-babel.mdx @@ -6,7 +6,7 @@ By default, `@roots/bud-typescript` will pass code to `@roots/bud-babel` for fur To disable babel and only use tsc: -```ts +```ts title=bud.config.ts bud.typescript.useBabel(false) ``` diff --git a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx index 4b3a1b7f6d..f84e627e99 100644 --- a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx +++ b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx @@ -1,5 +1,6 @@ import BudCommand from '@roots/bud/cli/commands' import {Command, Option} from '@roots/bud-support/clipanion' +import {bind} from '@roots/bud-support/decorators/bind' /** * Bud tsc command @@ -27,6 +28,7 @@ export class BudTSCCommand extends BudCommand { /** * {@link BudCommand.execute} */ + @bind public override async execute() { await this.makeBud() await this.bud.run() diff --git a/sources/@roots/bud/src/cli/app.tsx b/sources/@roots/bud/src/cli/app.tsx index 9d66d64b4a..7eae6a5d4a 100644 --- a/sources/@roots/bud/src/cli/app.tsx +++ b/sources/@roots/bud/src/cli/app.tsx @@ -22,6 +22,7 @@ import {BudError} from '@roots/bud-support/errors' import {render} from '@roots/bud-support/ink' import isFunction from '@roots/bud-support/lodash/isFunction' import isUndefined from '@roots/bud-support/lodash/isUndefined' +import logger from '@roots/bud-support/logger' import * as args from '@roots/bud-support/utilities/args' /** @@ -54,6 +55,11 @@ const application = new Cli({ enableColors: context.env?.color !== `false`, }) +/** + * Command finder + */ +const finder = new Finder(context, application) + /** * Register built-ins */ @@ -86,67 +92,75 @@ const forceFlag = !isUndefined(context.force) ? context.force : false * @param force - force rebuilding of extension cache */ const registerFoundCommands = async (force: boolean = forceFlag) => { - try { - const finder = new Finder(context, application) - if (!force) await finder.init() - else { - await finder.getModules() + if (!force) { + logger.scope(`cli`).log(`Loading commands from cache...`) + await finder.init() + } else { + logger.scope(`cli`).log(`Searching for commands...`) + await finder.getModules() + if (finder.paths.length > 0) { + logger.scope(`cli`).log(`Found ${finder.paths.length} commands.`) + logger.scope(`cli`).log(`Updating command cache...`) await finder.cacheWrite() } + } - const extensions = await finder.importCommands() - - return await Promise.all( - extensions.map( - async ([path, registerCommand]: [ - string, - (application: Cli) => Promise, - ]) => { - if (!isFunction(registerCommand)) - throw `${path} does not export a module exporting a registration function.` + if (!finder.paths.length) { + logger.scope(`cli`).log(`No commands found.`) + return + } - await registerCommand(application).catch(error => { - throw new BudError(error, {thrownBy: path}) + const extensions = await finder.importCommands() + + return await Promise.all( + extensions.map( + async ([path, registerCommand]: [ + string, + (application: Cli) => Promise, + ]) => { + logger.scope(`cli`).log(`Registering ${path}...`) + + if (!isFunction(registerCommand)) { + logger.scope(`cli`).error(`Error registering ${path}`) + + throw BudError.normalize( + `${path} does not export a module exporting a registration function.`, + { + details: `Error registering ${path}`, + thrownBy: import.meta.url, + }, + ) + } + + await registerCommand(application).catch(error => { + logger.scope(`cli`).error(`Error registering ${path}`, error) + throw BudError.normalize(error, { + details: `Error registering ${path}`, + thrownBy: import.meta.url, }) - }, - ), - ) - } catch (error) { + }) + }, + ), + ).catch(error => { throw error - } + }) } -try { - // first round will attempt to register extensions from cache - // if cache does not exist, it will be created - await registerFoundCommands() -} catch (error) { - try { - // first round failed to load extensions for some reason - // maybe a cached extension no longer exists - // or maybe a cached extension has been updated and the old path is resolvable but no longer valid - // so we'll try searching again - await registerFoundCommands(true) - } catch (innerError) { - // at this point we know that the error is not related to a suspect cache - // so we'll present it to the user - // and bail on the application - const initializeExtensionsError = - innerError instanceof BudError - ? innerError - : BudError.normalize(innerError) - - initializeExtensionsError.origin = - error instanceof BudError ? error : BudError.normalize(error) - - onError(initializeExtensionsError) - } -} +// first round will attempt to register extensions from cache +// if cache does not exist, it will be created +await registerFoundCommands().catch(async error => { + // first round failed to load extensions for some reason + // maybe a cached extension no longer exists + // or maybe a cached extension has been updated and the old path is resolvable but no longer valid + // so we'll try searching again and force a rebuild of the cache + // if it still fails we'll throw and exit the process + await registerFoundCommands(true).catch(onError) +}) /** * Run application and exit when process is complete */ -application.runExit(args.raw, context).catch(onError) +await application.runExit(args.raw, context) export {application, Builtins, Cli} export type {CommandClass} diff --git a/sources/@roots/bud/src/cli/commands/build/index.tsx b/sources/@roots/bud/src/cli/commands/build/index.tsx index 7cdd5690b5..34dc61806a 100644 --- a/sources/@roots/bud/src/cli/commands/build/index.tsx +++ b/sources/@roots/bud/src/cli/commands/build/index.tsx @@ -118,7 +118,7 @@ export default class BudBuildCommand extends BudCommand { public override async execute() { await this.makeBud() - if (isBoolean(this[`entrypoints.html`])) { + if (isBoolean(this[`entrypoints.html`]) && `entrypoints` in this.bud) { this.bud.entrypoints.set(`emitHtml`, this[`entrypoints.html`]) } diff --git a/sources/@roots/bud/src/cli/commands/doctor/buildInfo.tsx b/sources/@roots/bud/src/cli/commands/doctor/buildInfo.tsx index bf85fff647..6f962ef412 100644 --- a/sources/@roots/bud/src/cli/commands/doctor/buildInfo.tsx +++ b/sources/@roots/bud/src/cli/commands/doctor/buildInfo.tsx @@ -24,7 +24,8 @@ export const BuildInfo = ({ return ( - Completed a dry run of your project's build (executed in {time}{` `} + Completed a dry run of your project's build (executed in {time} + {` `} seconds). diff --git a/sources/@roots/bud/src/cli/commands/doctor/index.tsx b/sources/@roots/bud/src/cli/commands/doctor/index.tsx index c9058c18af..e5402a1e8d 100644 --- a/sources/@roots/bud/src/cli/commands/doctor/index.tsx +++ b/sources/@roots/bud/src/cli/commands/doctor/index.tsx @@ -70,11 +70,12 @@ The \`bud doctor\` command will: await this.bud?.build.make().catch(this.catch) - Object.entries(this.bud?.extensions.repository).map(([name, extension]) => - (extension.isEnabled() - ? enabledExtensions - : disabledExtensions - ).push([name, extension]), + Object.entries(this.bud?.extensions.repository).map( + ([name, extension]) => + (extension.isEnabled() + ? enabledExtensions + : disabledExtensions + ).push([name, extension]), ) const packages = await Promise.all( @@ -93,7 +94,11 @@ The \`bud doctor\` command will: - + diff --git a/sources/@roots/bud/src/cli/commands/doctor/server.tsx b/sources/@roots/bud/src/cli/commands/doctor/server.tsx index 13b9414944..06c384396e 100644 --- a/sources/@roots/bud/src/cli/commands/doctor/server.tsx +++ b/sources/@roots/bud/src/cli/commands/doctor/server.tsx @@ -7,14 +7,21 @@ import {LabelBox} from '../../components/LabelBox.js' export const Server = ({bud}: {bud: Bud}) => { if (!bud) return null - if (!bud.isDevelopment) return ( - - - Not available in `production` mode. - Run this command with --mode=development for server information. - - - ) + if (!bud.isDevelopment) + return ( + + + + Not available in `production` mode. + + + Run this command with{` `} + --mode=development for server + information. + + + + ) const showProxy = bud.server?.enabledMiddleware && diff --git a/sources/@roots/bud/src/cli/commands/env/displayEnv.tsx b/sources/@roots/bud/src/cli/commands/env/displayEnv.tsx index 6e4bef0356..a0534feb39 100644 --- a/sources/@roots/bud/src/cli/commands/env/displayEnv.tsx +++ b/sources/@roots/bud/src/cli/commands/env/displayEnv.tsx @@ -5,9 +5,7 @@ import {Box, Text} from '@roots/bud-support/ink' const DisplayEnv = ({bud}: {bud: Bud}) => { return ( - - Environment variables{`\n`} - + Environment variables{`\n`} {bud.env .getEntries() diff --git a/sources/@roots/bud/src/cli/commands/index.tsx b/sources/@roots/bud/src/cli/commands/index.tsx index 55794291ef..aef54c9785 100644 --- a/sources/@roots/bud/src/cli/commands/index.tsx +++ b/sources/@roots/bud/src/cli/commands/index.tsx @@ -34,7 +34,7 @@ import isNumber from '@roots/bud-support/lodash/isNumber' import noop from '@roots/bud-support/lodash/noop' import {Menu} from '../components/Menu.js' -import override from '../helpers/override.js' +import override, {type Override} from '../helpers/override.js' export type {BaseContext, Context} export {Option} @@ -109,7 +109,7 @@ export default class BudCommand extends Command { public storage = storage - public use = use + public use: Array = use public verbose = verbose @@ -221,9 +221,9 @@ export default class BudCommand extends Command { }) } - this.renderStatic() + this.render() - if (!this.bud || this.bud?.isProduction || this.ignoreErrors === true) + if ((!this.bud || this.bud.isProduction) && this.ignoreErrors !== true) exit(1) } @@ -245,13 +245,13 @@ export default class BudCommand extends Command { [ this.cache, `BUD_CACHE`, - bud => async value => bud.persist(value), - ], + b => async v => b.persist(v), + ] satisfies Override<`filesystem` | `memory` | boolean>, [ this.use, `BUD_USE`, - bud => async value => await bud.extensions.add(value), - ], + b => async v => await b.extensions.add(v as any), + ] satisfies Override>, ].map(this.override), ) @@ -265,13 +265,13 @@ export default class BudCommand extends Command { this.bud = instance.get() - await this.bud.initialize(this.context).catch(this.catch) + await this.bud.initialize(this.context) - await applyCliOptionsCallback(this.bud).catch(this.catch) + await applyCliOptionsCallback(this.bud) - await this.bud.processConfigs().catch(this.catch) + await this.bud.processConfigs() - await applyCliOptionsCallback(this.bud).catch(this.catch) + await applyCliOptionsCallback(this.bud) this.bud.hooks.action(`build.before`, applyCliOptionsCallback) @@ -320,7 +320,8 @@ export default class BudCommand extends Command { process.exitCode = 2 throw new Error( [ - `Could not find ${signifier} binary\n`, + `Could not find ${signifier} binary`, + ``, `Checked:`, `- ${binary}`, ...checkedPaths diff --git a/sources/@roots/bud/src/cli/commands/repl/Repl.tsx b/sources/@roots/bud/src/cli/commands/repl/Repl.tsx index 41f1a1f691..3ab7439a0f 100644 --- a/sources/@roots/bud/src/cli/commands/repl/Repl.tsx +++ b/sources/@roots/bud/src/cli/commands/repl/Repl.tsx @@ -26,7 +26,7 @@ export const Repl = ({app, depth, indent}: ReplProps) => { const [page, setPage] = useState(0) const [action, setAction] = useState(``) - const pageSize = 10 + const pageSize = Math.max(process.stdout.rows - 7, 10) useInput((input, key) => { if (key.escape) { @@ -105,9 +105,8 @@ export const Repl = ({app, depth, indent}: ReplProps) => { }), ) setResult(result) - } catch (e) { - const error = BudError.normalize(e) - setResult(error.message) + } catch (error) { + setResult(BudError.normalize(error).message) } } @@ -116,8 +115,15 @@ export const Repl = ({app, depth, indent}: ReplProps) => { try { const fn = makeFn(value) const results = await fn(app) + + await app.build.make() + if (app.hasChildren) + await Promise.all( + Object.entries(app.children).map( + async ([_, child]) => await child.build.make(), + ), + ) processResults(results) - await app.promise() } catch (error) { setResult(BudError.normalize(error).message) } diff --git a/sources/@roots/bud/src/cli/commands/view/index.tsx b/sources/@roots/bud/src/cli/commands/view/index.tsx index bae2216777..5ab96e2a41 100644 --- a/sources/@roots/bud/src/cli/commands/view/index.tsx +++ b/sources/@roots/bud/src/cli/commands/view/index.tsx @@ -4,7 +4,6 @@ import {Command, Option} from '@roots/bud-support/clipanion' import {highlight} from '@roots/bud-support/highlight' import {Box, Text} from '@roots/bud-support/ink' import get from '@roots/bud-support/lodash/get' -import format from '@roots/bud-support/pretty-format' /** * `bud view` command @@ -22,6 +21,8 @@ export default class BudViewCommand extends BudCommand { public subject = Option.String({name: `subject`, required: false}) public override async execute() { + const format = await import(`@roots/bud-support/pretty-format`).then((module) => module.default) + await this.makeBud() await this.bud.run() diff --git a/sources/@roots/bud/src/cli/components/LabelBox.tsx b/sources/@roots/bud/src/cli/components/LabelBox.tsx index 7077547df9..5073b343f5 100644 --- a/sources/@roots/bud/src/cli/components/LabelBox.tsx +++ b/sources/@roots/bud/src/cli/components/LabelBox.tsx @@ -8,7 +8,13 @@ interface Props { value?: string } -export const LabelBox = ({children, color, flexDirection, label, value}: Props) => { +export const LabelBox = ({ + children, + color, + flexDirection, + label, + value, +}: Props) => { if (!label && !children && !value) return null return ( @@ -17,7 +23,9 @@ export const LabelBox = ({children, color, flexDirection, label, value}: Props) {children ? ( {children} - ) : value ? {value} : ( + ) : value ? ( + {value} + ) : ( No results to display diff --git a/sources/@roots/bud/src/cli/finder.ts b/sources/@roots/bud/src/cli/finder.ts index aeb8685cbd..d4c30aea36 100644 --- a/sources/@roots/bud/src/cli/finder.ts +++ b/sources/@roots/bud/src/cli/finder.ts @@ -128,11 +128,7 @@ export class Finder { } /** - * Get commands - * - * @remarks - * Returns cached commands if they exist, otherwise - * resolves and caches commands from project dependencies. + * Initialize */ @bind public async init() { @@ -141,12 +137,13 @@ export class Finder { if ((await fs.exists(path)) && this.cacheable) { this.paths = await fs.read(path) if (Array.isArray(this.paths)) return this - else throw new Error() + else throw new Error(`Invalid cache`) } } catch (error) {} await this.getModules() await this.cacheWrite() + return this } diff --git a/sources/@roots/bud/src/cli/flags/publicPath.ts b/sources/@roots/bud/src/cli/flags/publicPath.ts index 36daa77239..c49d065ce3 100644 --- a/sources/@roots/bud/src/cli/flags/publicPath.ts +++ b/sources/@roots/bud/src/cli/flags/publicPath.ts @@ -2,5 +2,4 @@ import {Option} from '@roots/bud-support/clipanion' export default Option.String(`--publicPath`, undefined, { description: `Public path to serve assets from`, - env: `APP_PATH_OUTPUT`, }) diff --git a/sources/@roots/bud/src/cli/flags/use.ts b/sources/@roots/bud/src/cli/flags/use.ts index 094bb2523b..fa95b0eba0 100644 --- a/sources/@roots/bud/src/cli/flags/use.ts +++ b/sources/@roots/bud/src/cli/flags/use.ts @@ -1,5 +1,7 @@ import {Option} from '@roots/bud-support/clipanion' +import {isArray, isString} from '@roots/bud-support/typanion' export default Option.Array(`--use`, undefined, { description: `Enable an extension`, + validator: isArray(isString()), }) diff --git a/sources/@roots/bud/src/cli/helpers/browserslistUpdate.tsx b/sources/@roots/bud/src/cli/helpers/browserslistUpdate.tsx index a4dadf1025..d552e47f4a 100644 --- a/sources/@roots/bud/src/cli/helpers/browserslistUpdate.tsx +++ b/sources/@roots/bud/src/cli/helpers/browserslistUpdate.tsx @@ -140,9 +140,9 @@ const updateBrowserslist = async (bud: Bud) => { chalk.yellow( ` --> ${ figures.warning - } Browserslist update failed. Try running ${chalk.blue(`${bin} ${subcommand.join( - ` `, - )}`)} manually.\n --> ${ + } Browserslist update failed. Try running ${chalk.blue( + `${bin} ${subcommand.join(` `)}`, + )} manually.\n --> ${ figures.warning } This check will not be performed again for another week. The ${chalk.blue( `--browserslist-update`, diff --git a/sources/@roots/bud/src/cli/helpers/override.ts b/sources/@roots/bud/src/cli/helpers/override.ts index f52c5a991c..f62e1b59f1 100644 --- a/sources/@roots/bud/src/cli/helpers/override.ts +++ b/sources/@roots/bud/src/cli/helpers/override.ts @@ -3,7 +3,11 @@ import type {Bud} from '@roots/bud' import {isset} from '@roots/bud/cli/helpers/isset' import noop from '@roots/bud-support/lodash/noop' -export type Override = [T, string, (bud: Bud) => (value: T) => Promise] +export type Override = [ + T, + string, + (bud: Bud) => (value: T) => Promise, +] export default async function override( bud: Bud, @@ -26,7 +30,11 @@ export default async function override( * - when children: all children but not the parent * - when no children: the parent; */ -export const withChildren = async (bud: Bud, value: any, makeFn: (bud: Bud) => (value: any) => Promise) => { +export const withChildren = async ( + bud: Bud, + value: any, + makeFn: (bud: Bud) => (value: any) => Promise, +) => { await makeFn(bud)(value).catch(noop) bud.hasChildren && diff --git a/sources/@roots/entrypoints-webpack-plugin/src/html.emitter.ts b/sources/@roots/entrypoints-webpack-plugin/src/html.emitter.ts index 7f4a3e7259..c736f524ec 100644 --- a/sources/@roots/entrypoints-webpack-plugin/src/html.emitter.ts +++ b/sources/@roots/entrypoints-webpack-plugin/src/html.emitter.ts @@ -7,15 +7,14 @@ import Webpack from 'webpack' /** * Emits inline html for each entrypoint - * - * @param compilation - Webpack compilation instance - * @param publicPath - public path for entrypoints */ export class HtmlEmitter { /** * Class constructor * * @param compilation - webpack compilation + * @param assets - webpack compilation assets + * @param entrypoints - {@link Entrypoints} * @param publicPath - asset publicPath */ public constructor( @@ -27,6 +26,9 @@ export class HtmlEmitter { /** * Reduce entrypoint entrypoints to markup + * + * @returns void + * @decorator bind - {@link bind} */ @bind public emit(): void { @@ -34,36 +36,29 @@ export class HtmlEmitter { this.compilation.emitAsset( `${name}.html`, new Webpack.sources.RawSource( - [...entrypoint.entries()].reduce(this.entrypointsReducer, ``), + [...entrypoint.entries()].reduce((html, [extension, files]) => { + if ([`js`, `mjs`].includes(extension)) + return [...files].reduce(this.scriptReducer, html) + + if (extension === `css`) + return [...files].reduce(this.styleReducer, html) + + return html + }, ``), ), ) }) } - /** - * Reduce an entrypoint entry to markup - */ - @bind - public entrypointsReducer( - acc: string, - [type, files]: [string, Set], - ): string { - if ([`js`, `mjs`].includes(type)) - return [...files].reduce(this.scriptReducer, acc) - if (type === `css`) return [...files].reduce(this.styleReducer, acc) - - return acc - } - /** * Get compiled asset file contents * - * @param file - asset file + * @param ident - asset module name * @returns - asset file contents */ @bind - public getCompiledAsset(file: string) { - const raw = this.assets[file.replace(this.publicPath, ``)]?.source() + public getCompiledAsset(ident: string) { + const raw = this.assets[ident.replace(this.publicPath, ``)]?.source() return raw instanceof Buffer ? raw.toString() : raw } @@ -72,22 +67,24 @@ export class HtmlEmitter { */ @bind public makeScript( - attributes: Record, + attributeRecords: Record, inner: null | string = ``, ): string | undefined { - if (typeof inner !== `string`) return inner = inner ? `\n\t${inner}\n` : `` - const stringyAttributes = attributes - ? Object.entries(attributes) - .filter(([, v]) => typeof v !== `undefined` && v !== false) - .map(([k, v]) => (v === true ? k : `${k}=${v}`)) - .reduce((acc: Array, v: string) => [...acc, v], []) + const attributes = attributeRecords + ? Object.entries(attributeRecords) + .filter(([_, v]) => v !== undefined) + .map(([k, v]) => { + // html5 specification allows for omitting value for boolean attributes + if (v === true) return k + return `${k}=${v}` + }) .filter(Boolean) .join(` `) : `` - return `` + return `` } /** @@ -95,18 +92,18 @@ export class HtmlEmitter { */ @bind public scriptReducer(acc: string, src: string): string { - const attributes: Record = { + const attributes: Record = { async: true, defer: true, - src: !src.includes(`runtime`) ? src : false, - type: src.endsWith(`.mjs`) ? `module` : false, + src: !src.includes(`runtime`) ? src : undefined, + type: src.endsWith(`.mjs`) ? `module` : undefined, } return [ acc, this.makeScript( attributes, - src.includes(`runtime`) ? this.getCompiledAsset(src) : null, + src.includes(`runtime`) ? this.getCompiledAsset(src) : undefined, ), ].join(`\n`) } @@ -115,7 +112,7 @@ export class HtmlEmitter { * Reduce a stylesheet from entry item to markup */ @bind - public styleReducer(acc: string, file: string): string { - return [acc, ``].join(`\n`) + public styleReducer(acc: string, href: string): string { + return [acc, ``].join(`\n`) } } diff --git a/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts b/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts index f1e3c54750..eebc655354 100644 --- a/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts +++ b/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts @@ -3,6 +3,9 @@ import type { Entrypoints, Options, } from '@roots/entrypoints-webpack-plugin' +import type {WebpackPluginInstance} from 'webpack' + +import {join} from 'node:path' import {SyncHook, SyncWaterfallHook} from 'tapable' import Webpack from 'webpack' @@ -18,6 +21,8 @@ const hookMap = new WeakMap() * Produces `entrypoints.json` artifact with compiled assets broken down * by entrypoint and then filetype. * + * {@link WebpackPluginInstance} + * * @example * ```js * import {EntrypointsWebpackPlugin} from '@roots/entrypoints-webpack-plugin' @@ -27,9 +32,9 @@ const hookMap = new WeakMap() * } * ``` */ -export class EntrypointsWebpackPlugin { +export class EntrypointsWebpackPlugin implements WebpackPluginInstance { /** - * Compilation hooks + * Get compilation hooks * * @param compilation * @returns @@ -81,7 +86,7 @@ export class EntrypointsWebpackPlugin { this.entrypoints = new Map() this.addToManifest = this.addToManifest.bind(this) - this.getChunkedFiles = this.getChunkedFiles.bind(this) + this.getFilesFromChunks = this.getFilesFromChunks.bind(this) this.apply = this.apply.bind(this) } @@ -104,7 +109,7 @@ export class EntrypointsWebpackPlugin { } /** - * Webpack plugin API's `apply` hook + * {@link WebpackPluginInstance.apply} */ public apply(compiler: Webpack.Compiler): void { compiler.hooks.thisCompilation.tap( @@ -123,14 +128,13 @@ export class EntrypointsWebpackPlugin { this.entrypoints = new Map() for (const entry of compilation.entrypoints.values()) { - this.getChunkedFiles(entry.chunks).map(({file}) => { - const ident = entry.name as string - const path = (this.options.publicPath as string).concat( - file, - ) + this.getFilesFromChunks(entry.chunks).map(({file}) => { + if (!entry.name) return + + const path = join(this.options.publicPath ?? ``, file) const type = path.split(`.`).pop() ?? `default` - this.addToManifest({ident, path, type}) + this.addToManifest({ident: entry.name, path, type}) }) } @@ -171,7 +175,7 @@ export class EntrypointsWebpackPlugin { /** * Get assets from an entrypoint */ - public getChunkedFiles( + public getFilesFromChunks( chunks: Webpack.Chunk[], ): Array<{file: string; ident: string}> { const files: Array<{file: string; ident: string}> = [] diff --git a/sources/@roots/entrypoints-webpack-plugin/test/plugin.test.ts b/sources/@roots/entrypoints-webpack-plugin/test/plugin.test.ts index fbc7be6095..fdce34da3d 100644 --- a/sources/@roots/entrypoints-webpack-plugin/test/plugin.test.ts +++ b/sources/@roots/entrypoints-webpack-plugin/test/plugin.test.ts @@ -10,7 +10,7 @@ describe(`entrypoints.json`, () => { const chonk = new Webpack.Chunk() chonk.files = new Set([`foo.js`, `bar.js`]) const files = entrypoints - .getChunkedFiles([chonk]) + .getFilesFromChunks([chonk]) // @ts-ignore .map(file => file.file) diff --git a/sources/@roots/sage/README.md b/sources/@roots/sage/README.md index 3f299b47d6..67d7189a25 100644 --- a/sources/@roots/sage/README.md +++ b/sources/@roots/sage/README.md @@ -36,12 +36,12 @@ The @roots/sage extension depends on [@roots/bud-preset-wordpress](https://bud.j These are the packages which are installed as peers and registered by the **@roots/sage** main extension: -| Extension | Description | -| --------------------------------------------------------------------------------- | ------------------ | -| [@roots/bud-babel](https://bud.js.org/extensions/bud-babel) | Babel transpiler | -| [@roots/bud-postcss](https://bud.js.org/extensions/bud-postcss) | PostCSS transpiler | -| [@roots/bud-react](https://bud.js.org/extensions/bud-react) | React support | -| [@roots/bud-preset-wordpress](https://bud.js.org/extensions/bud-preset-wordpress) | WordPress preset | +| Extension | Description | +| --------------------------------------------------------------------------------- | ---------------- | +| [@roots/bud-swc](https://bud.js.org/extensions/bud-swc) | SWC | +| [@roots/bud-postcss](https://bud.js.org/extensions/bud-postcss) | PostCSS | +| [@roots/bud-react](https://bud.js.org/extensions/bud-react) | React support | +| [@roots/bud-preset-wordpress](https://bud.js.org/extensions/bud-preset-wordpress) | WordPress preset | ## Using with eslint @@ -169,6 +169,16 @@ Current supported extensions: `js`, `ts`, `css`, `scss`, `vue`. Note that in order to use `ts`, `scss` or `vue` you will need to have installed a bud extension that supports that language or framework. +## Disable processing of blade templates + +If you are not using this feature and wish to disable it, you can do so: + +```ts +export default async (bud: Bud) => { + bud.sage.processBladeTemplates(false); +}; +``` + ## Contributing Contributions are welcome from everyone. diff --git a/sources/@roots/sage/docs/01-included-extensions.md b/sources/@roots/sage/docs/01-included-extensions.md index f392a35173..71c2afadc4 100644 --- a/sources/@roots/sage/docs/01-included-extensions.md +++ b/sources/@roots/sage/docs/01-included-extensions.md @@ -6,9 +6,9 @@ The @roots/sage extension depends on [@roots/bud-preset-wordpress](https://bud.j These are the packages which are installed as peers and registered by the **@roots/sage** main extension: -| Extension | Description | -| --------------------------------------------------------------------------------- | ------------------ | -| [@roots/bud-babel](https://bud.js.org/extensions/bud-babel) | Babel transpiler | -| [@roots/bud-postcss](https://bud.js.org/extensions/bud-postcss) | PostCSS transpiler | -| [@roots/bud-react](https://bud.js.org/extensions/bud-react) | React support | -| [@roots/bud-preset-wordpress](https://bud.js.org/extensions/bud-preset-wordpress) | WordPress preset | +| Extension | Description | +| --------------------------------------------------------------------------------- | ---------------- | +| [@roots/bud-swc](https://bud.js.org/extensions/bud-swc) | SWC | +| [@roots/bud-postcss](https://bud.js.org/extensions/bud-postcss) | PostCSS | +| [@roots/bud-react](https://bud.js.org/extensions/bud-react) | React support | +| [@roots/bud-preset-wordpress](https://bud.js.org/extensions/bud-preset-wordpress) | WordPress preset | diff --git a/sources/@roots/sage/docs/05-blade-assets.md b/sources/@roots/sage/docs/05-blade-assets.md index 14b8f4e0a4..5049f14781 100644 --- a/sources/@roots/sage/docs/05-blade-assets.md +++ b/sources/@roots/sage/docs/05-blade-assets.md @@ -67,3 +67,13 @@ body { Current supported extensions: `js`, `ts`, `css`, `scss`, `vue`. Note that in order to use `ts`, `scss` or `vue` you will need to have installed a bud extension that supports that language or framework. + +## Disable processing of blade templates + +If you are not using this feature and wish to disable it, you can do so: + +```ts +export default async (bud: Bud) => { + bud.sage.processBladeTemplates(false) +} +``` diff --git a/sources/@roots/sage/src/acorn-v2-public-path/index.ts b/sources/@roots/sage/src/acorn-v2-public-path/index.ts index 237d2613ac..4be35da083 100644 --- a/sources/@roots/sage/src/acorn-v2-public-path/index.ts +++ b/sources/@roots/sage/src/acorn-v2-public-path/index.ts @@ -10,7 +10,7 @@ import {bind, label} from '@roots/bud-framework/extension/decorators' @label(`@roots/sage/acorn-v2-public-path`) export default class AcornV2PublicPath extends Extension { /** - * `register` callback + * {@link Extension.register} * @deprecated */ @bind diff --git a/sources/@roots/sage/src/acorn/index.ts b/sources/@roots/sage/src/acorn/index.ts index ace40390e4..20eba1ee28 100644 --- a/sources/@roots/sage/src/acorn/index.ts +++ b/sources/@roots/sage/src/acorn/index.ts @@ -34,6 +34,7 @@ export default class Acorn if (this.app.root?.server?.publicUrl) data.dev = urlToHttpOptions(this.app.root.server.publicUrl) + if (this.app.root?.server?.publicProxyUrl) data.proxy = urlToHttpOptions(this.app.root.server.publicProxyUrl) diff --git a/sources/@roots/sage/src/blade-loader/extension.ts b/sources/@roots/sage/src/blade-loader/extension.ts index cfb0b9485f..a6838ca4fd 100644 --- a/sources/@roots/sage/src/blade-loader/extension.ts +++ b/sources/@roots/sage/src/blade-loader/extension.ts @@ -2,7 +2,11 @@ import type {Bud} from '@roots/bud-framework' import BladeLoaderPlugin from '@roots/blade-loader' import {Extension} from '@roots/bud-framework/extension' -import {label, plugin} from '@roots/bud-framework/extension/decorators' +import { + bind, + label, + plugin, +} from '@roots/bud-framework/extension/decorators' /** * Blade loader extension @@ -13,6 +17,7 @@ export class BladeLoaderExtension extends Extension { /** * {@link Extension.register} */ + @bind public override async register({hooks}: Bud) { hooks.on(`build.resolve.extensions`, (extensions = new Set([])) => extensions.add(`.blade.php`), diff --git a/sources/@roots/sage/src/index.ts b/sources/@roots/sage/src/index.ts index 51a3f0ae82..7e3e48e4f6 100644 --- a/sources/@roots/sage/src/index.ts +++ b/sources/@roots/sage/src/index.ts @@ -18,12 +18,64 @@ import type BladeLoader from '@roots/sage/blade-loader' import Sage from '@roots/sage/sage' interface SagePublicAPI extends PublicExtensionApi { + /** + * ## Configure Acorn concerns + * + * @see {@link Acorn} + * @see {@link https://bud.js.org/extensions/sage#acorn-compatibility} + */ acorn: PublicExtensionApi + + /** + * ## Configure handling of Blade template modules + * + * @see {@link BladeLoader} + * @see {@link https://bud.js.org/extensions/sage/blade-assets} + */ + blade: PublicExtensionApi + + /** + * ## Enable or disable Blade template processing + * + * @remarks + * This method is a convenience wrapper for the {@link BladeLoader.enable} method. + * + * @example + * ```js + * bud.sage.processBladeTemplates() + * ``` + * + * @example + * ```js + * bud.sage.processBladeTemplates(false) + * ``` + * + * @example + * ```js + * bud.when(bud.isProduction, bud.sage.processBladeTemplates) + * ``` + * + * @see {@link https://bud.js.org/extensions/sage/blade-assets} + */ + processBladeTemplates: Sage[`processBladeTemplates`] + + /** + * This function should be removed from your configuration file. + * It doesn't do anything and will be removed in a future release. + * + * @deprecated + */ setAcornVersion: Sage[`setAcornVersion`] } declare module '@roots/bud-framework' { interface Bud { + /** + * ## Sage configuration + * + * @see {@link https://bud.js.org/extensions/sage} + * @see {@link https://docs.roots.io/sage/10.x/compiling-assets} + */ sage: SagePublicAPI } @@ -59,3 +111,4 @@ declare module '@roots/bud-framework' { } export {Sage as default} +export type {SagePublicAPI} diff --git a/sources/@roots/sage/src/sage/index.ts b/sources/@roots/sage/src/sage/index.ts index b65268e490..a7d31d596f 100644 --- a/sources/@roots/sage/src/sage/index.ts +++ b/sources/@roots/sage/src/sage/index.ts @@ -2,6 +2,7 @@ import type {Bud} from '@roots/bud-framework' import {Extension} from '@roots/bud-framework/extension' import { + bind, dependsOn, expose, label, @@ -9,6 +10,7 @@ import { import {deprecated} from '@roots/bud-support/decorators' import type Acorn from '../acorn/index.js' +import type {BladeLoaderExtension} from '../blade-loader/extension.js' /** * roots/sage @@ -31,9 +33,36 @@ class Sage extends Extension { return this.app.extensions.get(`@roots/sage/acorn`) } + /** + * {@link BladeLoaderExtension} + */ + public get blade(): BladeLoaderExtension { + return this.app.extensions.get(`@roots/sage/blade-loader`) + } + + /** + * Get unique name for project + */ + @bind + public getUniqueName(): string { + return this.app.label !== `sage` + ? `@roots/bud/sage/${this.app.label}` + : `@roots/bud/sage` + } + + /** + * {@link BladeLoaderExtension.enable} + */ + @bind + public processBladeTemplates(value: boolean | Bud = true) { + this.blade.enable(value) + return this + } + /** * {@link Extension.register} */ + @bind public override async register(bud: Bud) { bud .setPath({ @@ -45,17 +74,9 @@ class Sage extends Extension { '@styles': `@src/styles`, '@views': `@src/views`, }) - .when( - bud.isProduction, - () => bud.hash(), - () => bud.devtool(), - ) - .hooks.on( - `build.output.uniqueName`, - bud.label !== `sage` - ? `@roots/bud/sage/${bud.label}` - : `@roots/bud/sage`, - ) + .when(bud.isProduction, bud.hash, bud.devtool) + + bud.hooks.on(`build.output.uniqueName`, this.getUniqueName) } /** diff --git a/sources/@roots/sage/test/acorn/extension.test.ts b/sources/@roots/sage/test/acorn/extension.test.ts index b8e819c34d..1c9ff7fe6b 100644 --- a/sources/@roots/sage/test/acorn/extension.test.ts +++ b/sources/@roots/sage/test/acorn/extension.test.ts @@ -17,15 +17,13 @@ describe(`@roots/sage/acorn`, async () => { let result = await acorn.buildBefore(bud) expect(result).toBe(undefined) - expect(bud.entrypoints.get(`publicPath`)).toBe(``) expect(bud.manifest.get(`publicPath`)).toBe(``) }) it(`should execute webpack apply in development`, async () => { const bud = await factory({mode: `development`}) - const mockCompiler = {hooks: {thisCompilation: {tap: vi.fn((...args) => {})}}} - // @ts-ignore + const mockCompiler = {hooks: {thisCompilation: {tap: vi.fn((...args) => {})}}} as any const result = new Acorn(bud).apply(mockCompiler) expect(mockCompiler.hooks.thisCompilation.tap).toHaveBeenCalled() diff --git a/sources/@roots/sage/test/sage/extension.test.ts b/sources/@roots/sage/test/sage/extension.test.ts index 10f9d4db45..f6c7f98d4b 100644 --- a/sources/@roots/sage/test/sage/extension.test.ts +++ b/sources/@roots/sage/test/sage/extension.test.ts @@ -1,4 +1,3 @@ -import {path} from '@repo/constants' import {Bud, factory} from '@repo/test-kit' import Sage from '@roots/sage' import {beforeEach, describe, expect, it, vi} from 'vitest' @@ -19,20 +18,60 @@ describe(`@roots/sage`, async () => { it(`should register '@roots/sage/blade-loader'`, async () => { expect(bud.extensions.has(`@roots/sage/blade-loader`)).toBeFalsy() + await bud.extensions.add(`@roots/sage`) + expect(bud.extensions.has(`@roots/sage/blade-loader`)).toBeTruthy() }) it(`should register '@roots/bud-preset-wordpress'`, async () => { expect(bud.extensions.has(`@roots/bud-preset-wordpress`)).toBeFalsy() + await bud.extensions.add(`@roots/sage`) + expect(bud.extensions.has(`@roots/bud-preset-wordpress`)).toBeTruthy() + expect( + bud.extensions.get(`@roots/bud-preset-wordpress`).enabled, + ).toBeTruthy() }) - it(`should register '@roots/sage/acorn'`, async () => { + it(`sage.acorn`, async () => { expect(bud.extensions.has(`@roots/sage/acorn`)).toBeFalsy() + await bud.extensions.add(`@roots/sage`) + expect(bud.extensions.has(`@roots/sage/acorn`)).toBeTruthy() + expect(bud.extensions.get(`@roots/sage/acorn`).enabled).toBeTruthy() + }) + + it(`sage.acorn.enable`, async () => { + expect(bud.extensions.has(`@roots/sage/acorn`)).toBeFalsy() + + await bud.extensions.add(`@roots/sage`) + + bud.sage.acorn.enable(false).done() + + expect(bud.extensions.get(`@roots/sage/acorn`).enabled).toBeFalsy() + }) + + it(`sage.blade`, async () => { + expect(bud.extensions.has(`@roots/sage/blade`)).toBeFalsy() + + await bud.extensions.add(`@roots/sage`) + + expect(bud.extensions.has(`@roots/sage/blade-loader`)).toBeTruthy() + expect(bud.extensions.get(`@roots/sage/blade-loader`).enabled).toBeTruthy() + }) + + + it(`sage.blade.enable`, async () => { + expect(bud.extensions.has(`@roots/sage/blade-loader`)).toBeFalsy() + + await bud.extensions.add(`@roots/sage`) + + bud.sage.blade.enable(false).done() + + expect(bud.extensions.get(`@roots/sage/blade-loader`).enabled).toBeFalsy() }) it(`should register errything`, async () => { @@ -53,7 +92,13 @@ describe(`@roots/sage`, async () => { expect(hooksSpy).toHaveBeenCalledWith( `build.output.uniqueName`, - `@roots/bud/sage/${bud.label}`, + // @ts-ignore + bud.sage.getUniqueName, + ) + + expect(bud.hooks.filter(`build.output.uniqueName`)).toEqual( + // @ts-ignore + bud.sage.getUniqueName() ) }) @@ -72,6 +117,7 @@ describe(`@roots/sage`, async () => { const devtoolSpy = vi.spyOn(bud, `devtool`) const hashSpy = vi.spyOn(bud, `hash`) await sage.register(bud) + expect(whenSpy).toHaveBeenCalledWith( true, expect.any(Function), @@ -84,10 +130,12 @@ describe(`@roots/sage`, async () => { it(`should call bud.devtool in development`, async () => { const bud = await factory({mode: `development`}) expect(bud.isProduction).toBe(false) + const whenSpy = vi.spyOn(bud, `when`) const devtoolSpy = vi.spyOn(bud, `devtool`) const hashSpy = vi.spyOn(bud, `hash`) await sage.register(bud) + expect(whenSpy).toHaveBeenCalledWith( false, expect.any(Function), diff --git a/tests/integration/__snapshots__/sass.test.ts.snap b/tests/integration/__snapshots__/sass.test.ts.snap deleted file mode 100644 index f546dbf7a8..0000000000 --- a/tests/integration/__snapshots__/sass.test.ts.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`examples/sass > should compile js and css as expected 1`] = `"body,html{margin:0;padding:0}body{background:blue;background-image:url(\\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 81 77'%3E%3Cg fill='none' stroke='%23fff'%3E%3Cpath d='m79.9 38.1-7.2-22-12.8-4.2L52 1H28.8l-7.9 10.9L8.2 16 1 38.1 8.9 49v13.4L27.6 76l12.8-4.2L53.2 76 72 62.4V48.9z'/%3E%3Cpath d='m40.4 52.4-.5-.4-12.5-9.1 4.8-14.7.2-.6h16l5 15.3L42.3 51zM73.5 37.1 68 44.6l-8.9-2.9-5.4-16.5 5.5-7.6 8.9 2.9 5.4 16.6zM31.8 6.7h17.3l5.5 7.6-.8 1.1-4.7 6.5H31.7L27 15.3l-.7-1zM12.7 20.6l8.9-2.9 5.5 7.6-.1.2-5.3 16.2-8.9 2.9L7.3 37zM14.6 59.5v-9.4l1.1-.4 7.8-2.5 12.7 9.2 1.4 1v9.4l-8.9 2.9zM52.2 69.6l-8.9-2.9v-9.4l1.2-.9 12.8-9.3 6.3 2.1 2.6.8v9.5z'/%3E%3C/g%3E%3C/svg%3E\\")}body div{border:#fff}"`; - -exports[`examples/sass > should compile js and css as expected 2`] = ` -{ - "entrypoints.json": "entrypoints.json", - "main.css": "css/main.css", - "runtime.js": "js/runtime.js", -} -`; diff --git a/tests/integration/sass.test.ts b/tests/integration/sass.test.ts index 3dd0da2329..c3f6ed237b 100644 --- a/tests/integration/sass.test.ts +++ b/tests/integration/sass.test.ts @@ -11,7 +11,13 @@ describe(`examples/sass`, () => { expect(test.assets[`main.css`].length).toBeGreaterThan(10) expect(test.assets[`main.css`].includes(`import`)).toBeFalsy() - expect(test.assets[`main.css`]).toMatchSnapshot() - expect(test.manifest).toMatchSnapshot() + expect(test.assets[`main.css`]).toMatch(/body,html{margin:0;padding:0}body{background:blue;background-image:url\("data:image\/svg\+xml;charset=utf-8,\%3Csvg xmlns='http:\/\/www\.w3\.org\/2000\/svg' viewBox='0 0 81 77'\%3E\%3Cg fill='none' stroke='\%23fff'\%3E\%3Cpath d='.*'\/%3E%3C\/g%3E%3C\/svg%3E"\)}body div{border:#fff}/) + expect(test.manifest).toMatchInlineSnapshot(` + { + "entrypoints.json": "entrypoints.json", + "main.css": "css/main.css", + "runtime.js": "js/runtime.js", + } + `) }) }, 100000) diff --git a/tests/util/project/package.json b/tests/util/project/package.json index 6e0bb5860b..b873d0a7e6 100644 --- a/tests/util/project/package.json +++ b/tests/util/project/package.json @@ -3,19 +3,19 @@ "private": true, "$schema": "https://bud.js.org/bud.package.json", "type": "module", + "browserslist": [ + "extends @roots/browserslist-config" + ], "devDependencies": { "@roots/bud": "workspace:*", "@roots/bud-react": "workspace:*", "@roots/bud-swc": "workspace:*", "@roots/bud-tailwindcss": "workspace:*" }, - "volta": { - "node": "18.12.1" - }, - "browserslist": [ - "extends @roots/browserslist-config" - ], "dependencies": { "@tailwindcss/forms": "0.5.7" + }, + "volta": { + "node": "18.12.1" } } diff --git a/yarn.lock b/yarn.lock index c8d639a536..3091adca0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6094,8 +6094,10 @@ __metadata: dependencies: "@roots/bud": "workspace:*" "@roots/bud-eslint": "workspace:*" + "@roots/bud-postcss": "workspace:*" "@roots/bud-preset-wordpress": "workspace:*" "@roots/bud-swc": "workspace:*" + "@roots/bud-tailwindcss": "workspace:*" languageName: unknown linkType: soft