Releases: roots/bud
v6.4.2
Release notes also available on bud.js.org
👉🏼 known issue: [@roots/sage] errors logged when not using tailwindcss
If you are not using @roots/bud-tailwindcss
you will see errors in the logs when running bud build
or bud dev
.
This is a known issue and will be resolved in the next release. It isn't a sign that anything is wrong in your project.
✨ improve: [@roots/sage] bud.wpjson
now fully resolves tailwind configs
Previously, bud.wpjson
could only generate a theme.json
based on a fully static tailwind config file. This excluded tailwind configs that
defined theme properties using functions and also meant that plugins would not be applied.
Now, bud.wpjson
functions related to tailwindcss will fully resolve the tailwind config file and generate a theme.json
based on the
processed config. This should mean that any tailwind config can be used in conjunction with bud.wpjson.useTailwindColors()
and similar functions.
🩹 fix: build errors return non-zero exit code
For production builds, build errors will now result in a non-zero exit code. This regression was introduced in 6.4.0. It is now fixed.
🩹 fix: eslint.config.js support
eslint.config.js
configurations are now supported.
🩹 fix: bud repl
The bud repl
command has seen significant improvements since 6.4.0. It fixes a bug in 6.4.1 related to syntax highlighting.
ℹ️ Release information
For more information review the diff to see what's changed.
v6.4.0
Lots of fixes, features, and performance improvements. As always, you can read enhanced release notes at bud.js.org.
Client
hot reload middleware
Internally, bud.js has fully replaced webpack-hot-middleware
and its associated client scripts.
What you can expect:
- If you have an error in your code that fully breaks hot module reloading the client
will automatically perform a full page reload. - You should have far fewer duplicate log messages about module updates.
proxy url replacement
If you are using bud.proxy you don't need to do anything. But, if you wanted to use this script directly it is now a lot more flexible.
There are two ways to utilize it:
1. add to your entry imports array
@roots/bud-client/lib/proxy-client-interceptor.js
accepts URL parameters for search
and replace
strings.
bud.entry({
app: [
// ... app scripts and styles
`@roots/bud-client/lib/proxy-client-interceptor.js?search=http://example.com&replace=/`,
],
})
2. import it in a client script
@roots/bud-client/lib/intercept.js
can be imported directly and called from application code.
import intercept from '@roots/bud-client/lib/intercept.js'
intercept('http://example.com', '/')
improved: bud.config.local
support
If you are on a team and team members have different needs for their local development environment, you can now use bud.config.local.mjs
to override bud.config.mjs
settings.
.local
configs will always be applied after the base config.
improved: bud.assets
You can now specify copy options to apply to all copy patterns as a second parameter.
So the second parameter will be merged onto each pattern:
bud.assets(
[
{from: 'images', to: 'images'},
{from: 'fonts', to: 'fonts'},
],
{context: app.path('@assets')},
)
This is the same as:
bud.assets([
{
from: 'images',
to: 'images',
context: app.path('@assets'),
},
{
from: 'fonts',
to: 'fonts',
context: app.path(`assets`),
},
])
This also works with string
or [from, to]
tuples, as well.
new: bud.sh
bud.sh is a new config function that allows for executing arbitrary shell commands. It is a wrapper around the execa package.
It's async and returns the ExecaChildProcess
object. It will pipe the process stdout/stderr to the console automatically.
await bud.sh('echo "hello world"')
Ideal for a hook like compiler.done
, if you wanted to fire off a command when finalizing a build.
CLI
Enhanced build summary
Here's what you can expect:
◉ @tests/project ./dist [6c3fa82de0a7a70e5e45]
│
├─ entrypoints
│ ├─ app
│ │ └─ js/app.js 28.34 kB
│ ├─ app2
│ │ └─ js/app2.js 26.73 kB
│ └─ dev-client
│ └─ js/dev-client.js 84.61 kB
│
├─ assets
│ ├─ images/image.jpeg 761.41 kB
│ ├─ images/nested/image.jpeg 761.41 kB
│ └─ images/.gitkeep
│
└─ compiled 41 modules in 709ms
◉ HtmlWebpackCompiler ./dist [bce8eedbb4952eca25f3]
└─ compiled 6 modules in 262ms
ℹ server
╷
├─ internal: http://localhost:3015 (http://localhost:3015)
└─ external: http://192.168.194.103:3015 (http://192.168.194.103:3015)
… watching project sources
Some of the changes you may notice:
- Displays information for multiple compilations.
- Labeled (can be useful if running builds in parallel with yarn/npm workspaces).
- tip: set your compilation label using the
name
field ofpackage.json
- tip: set your compilation label using the
- Indicates the directory being emitted to (also handy for identification purposes).
- Includes the oft-requested "external IP" for use over LAN.
- Visually grouped by entrypoint
- Improved display of compiler error messages
- Improved display of compiler warning messages
bud repl
Start a repl to play around with bud: $ bud repl
option | description | default |
---|---|---|
--indent,-i |
indentation level | 1 |
--depth,-d |
recursion depth | 2 |
Some example queries you may wish to try:
$ bud.hooks.events.store
$ bud.extensions.get('webpack:define-plugin')
$ bud.make('test').then(bud => bud.get('test').label)
$ bud.extensions.make()
$ bud.hooks.filter('build.optimization')
bud view
Explore the bud object with $ yarn bud view
. You can use dot notation to dive through properties.
option | description | default |
---|---|---|
--indent,-i |
indentation level | 2 |
--color,-c |
color | true |
Interesting examples:
See what functions were called: $ yarn bud view api.trace
View the generated config: $ yarn bud view build.config
You can go as deep as you need but if you are accessing an array by index you'll probably need to escape the brackets with quotes:
$ yarn bud view 'build.config.module.rules[1].oneOf[0]'
bud webpack
This is a passthrough command for the webpack cli. Get webpack usage help $ yarn bud webpack -- --help
To run a build with webpack you'd probably want to create a new file named webpack.config.js
in your project and do something like this:
// webpack.config.mjs
import {get} from '@roots/bud/factory'
export default async () => {
const bud = await get()
return await bud.build.make()
}
Run it with $ yarn bud webpack
.
Flags
--basedir
Set the application base directory. Default: process.cwd()
--browser
Now accepts an (optional) string to open the project in a specific browser. The exact string is system dependent. Check the sindresorhus/open docs
--dry
Will run bud.js all the way up until it's time to actually instantiate the compiler or the dev server, and then it bails.
-v,-vv,-vvv,-vvvv
Set logging level.
Flag | Level |
---|---|
-v |
error |
-vv |
warning |
-vvv |
log |
-vvvv |
info (verbose) |
default: -vv
.
--debug
Emit artifacts to storage directory for debugging bud.js context or the emitted webpack configuration. In earlier versions of bud.js a snapshot of the finalized webpack configuration and a snapshot of bud.js state was always saved to storage prior to compilation. If your project is working correctly this is just needless fs overhead.
default: false
--reload
allow bud.js to hard reload the browser window when an error is encountered that breaks hot module reloading.
default: true
Extensions can register commands
Extensions can now register commands.
So far a few commands have been implemented:
yarn bud ts check
will typecheck source assets (registered by@roots/bud-typescript
).yarn bud lint
(alias:yarn bud eslint
) will lint source assets (registered by@roots/bud-eslint
).yarn bud tailwindcss
will transpile tailwindcss (registered by@roots/bud-tailwindcss
).yarn bud format
(alias:yarn bud prettier
) will format source assets (registered by@roots/bud-prettier
).
Hooks
bud.hooks.fromMap
bud.hooks.fromMap
can be used to set multiple hooks in one call.
bud.hooks.fromMap({
'build.node': false,
'build.resolve.extensions': ext => ext.add('.mjml'),
})
bud.hooks.fromAsyncMap
bud.hooks.fromAsyncMap
can be used in the same way to set hooks with async callbacks.
bud.hooks.fromAsyncMap({
'build.plugins': async () => await app.extensions.make(),
'build.resolve.modules': async () => [
app.hooks.filter(`location.@src`),
app.hooks.filter(`location.@modules`),
],
})
Services
Services have access to some new methods that are called when the associated lifecycle event takes place.
In the order they are called:
method name | associated lifecycle event |
---|---|
configAfter |
config.after |
compileBefore |
compile.before |
buildBefore |
build.before |
buildAfter |
build.after |
compileAfter |
compile.after |
Extensions
Extensions have access to some new methods that are called when the associated lifecycle event takes place.
In the order they are called:
method name | associated lifecycle event |
---|---|
configAfter |
config.after |
buildBefore |
build.before |
buildAfter |
build.after |
✨ Improve: builds now labeled by package.json name
field
In earlier versions of bud.js the bud.label
property was always set to bud
(for the parent compiler). Now, it will be set to the name
field
of your project (as set in package.json
). If you don't have a name
set the new default is default
.
In addition to the clearer output summary labeling (see above), you should also find that logs are better labeled now:
[[email protected]] [my-project] › importing @roots/bud-terser/css-minimizer
ℹ️ Release information
For more information review the diff to see what's changed.
v6.3.5
Read the release notes on bud.js.org
🩹 Fix: resolved paths containing spaces
Paths resolved by bud.js's module resolve utility had spaces replaced with %20
in the path. This replacement was being made
by import-module-resolve
's resolve
function, which treats paths as browser-compatible URIs.
This small patch replaces %20
with
(U+0020) before returning resolved paths.
ℹ️ Release information
- 📦 deps: update @swc/core to v1.2.220 (#1610) [@swc-project]
- 📦 deps: update netlify-cli to v10.12.1 (#1612) [@netlify]
- 🩹 fix(patch): remove fn from allowed uris array (#1609) [@kellymears]
- 🩹 fix(patch): directory containing spaces (#1608) [@kellymears]
For more information review the diff to see what's changed.
v6.3.4
As always, release notes available at bud.js.org.
🩹 Fix: conditionally add/remove bud-error
component
The bud-error
component is now only added to the DOM only when there is an error and is removed when there is not.
✨ Improve: public env values
The bud.env docs have been updated to reflect the actual intended use of public env variables:
PUBLIC_APP_NAME="My App"
console.log(APP_NAME)
Previously, the envvars would have needed to include quotations in order to be output as a string (as in: PUBLIC_APP_NAME="'My App'"
).
Now, they are passed through JSON.stringify
automatically.
✨ Improve: prevent repeated attempts to import optional dependencies
A number of extensions will attempt to use dependencies if they are available, falling back to more common
dependencies if they are not. Previously, bud.js would attempt to import the dependencies repeatedly, in cases
where more than one extension tried for the same optional, uninstalled dependency.
Not sure that this matters much in terms of performance because of the way that esmodules are cached. But, at the very least,
it cleans up the logs a bit.
🩹 Fix: @roots/bud-tailwindcss/stylelint-config/scss
error
Hattip to @joshuafredrickson for the PR fixing this module import error.
ℹ️ Release information
- ✨ improve(patch): stringify env values (#1604)
- 🩹 fix(patch): conditionally add/remove bud-error (#1603)
- 📦 deps: update html-loader to v4 (#1599)
- 📦 deps: update npm to v8.15.0 (#1602)
- 📦 deps: update @yarnpkg (#1600)
- 📦 deps: update Yarn to v3.2.2 (#1601)
- ✨ improve(patch): prevent repeated attempts to import optional deps (#1598)
- 🩹 fix: missing module error during build (#1596)
- 📦 deps: update typedoc to v0.23.8
- 📦 deps: update wordpress monorepo (#1593)
- 📦 deps: update react
For more information review the diff to see what's changed.
v6.3.3
Release notes also available on bud.js.org.
🩹 Fix: extensions api inconsistencies
This is only relevant if you are writing an extension for bud.js.
All extension lifecycle methods now have a single signature:
interface Method {
(bud: Bud, options: Options): Promise<unknown>
}
This applies to: init
, register
, boot
, afterConfig
, beforeBuild
, make
, apply
and when
.
✨ Improve: @roots/bud-sass
configuration options
The documentation for @roots/bud-sass has been updated.
Global Imports
Use the bud.sass.importGlobal
function to make a scss module available throughout your stylesheets, regardless of scope.
bud.sass.importGlobal('@src/styles/variables')
If you have more than one stylesheet to import, you may use an array:
bud.sass.importGlobal([
'@src/styles/variables',
'@src/styles/mixins',
'@src/styles/functions',
])
Global Values
Use the bud.sass.registerGlobal
function to ensure global styles are made available throughout your sass stylesheets, regardless of scope.
This function differs from bud.sass.importGlobal
in that it can be passed arbitrary values.
bud.sass.registerGlobal('$foo: rgba(0, 0, 0, 1);')
If you want to divide these values up using an array, you may do so.
bud.sass.registerGlobal([
'$foo: rgba(0, 0, 0, 1);',
'$bar: rgba(255, 255, 255, 1);',
])
✨ Improve: bud.alias
Resolved some inconsistencies between the function typings, documentation and implementation.
You are now able to use an array of values for an alias.The first resolvable module found will be used, checking in the order of the supplied array.
Example:
bud.alias('@app', [
bud.path('@src/scripts/app'),
bud.path('@src/scripts/utils'),
])
Finally, you may now also pass false
to ignore a specific module.
You can do this with a signifier or a path:
bud.alias({
'ignored-module': false,
[bud.path('./ignored-module')]: false,
})
For those who remember: this basically replaces null-loader
.
✨ Improve: overlay and indicator web components
The hmr status indicator and client overlay are now using the shadow dom to more or less fully separate their styles from page styles.
All of the @roots/bud-client scripts remain dependency free.
ℹ️ Release information
- 🩹 fix(patch): correct extensions api inconsistencies (#1582)
- ✨ improve(patch): improve sass configuration api (#1580)
- ✨ improve(patch): bud.alias (#1581)
- ✨ improve(patch): use shadowdom for client (#1578)
For more information review the diff to see what's changed.
v6.3.2
Read the release on bud.js.org
🩹 Fix: HMR failures when there are semantic errors in application code
An internal misconfiguration could have resulted in some app code errors hanging the HMR middleware. This has been addressed.
🩹 Fix: bud clean
command
This command has been fixed. The problem stemmed from a change made for the benefit of multi-compilers in v6.
🩹 Fix: @roots/sage public path (when using acorn v2)
I didn't realize the project I was using to test bud in the context of Trellis/Bedrock/Acorn/Sage was using acorn v3 (which is still in alpha).
This caused some problems with public path in roots/sage projects using acorn@v2. Whoops!
Moving forward, I have set up an explicit e2e testing environment which includes acorn@v2 and acorn@v3 projects. Right now it is testing hmr compilation works in the browser.
In the future, there will be additional tests for:
- proxied wordpress uploads
- sage
@asset
directive
⏪ Revert: chunk loading and formatting for es modules
This change from v6.3.1 has been reverted. Still, it is our current hope that users who enable esm
and have set a compatible build target will output es modules. What we're reverting is an enforcement of esm as an output format.
It turns out webpack also configures the output.chunkFormat
and output.chunkLoading
values based on the application's build target
option, which is usually set in a browserslist config.
In the case of conflict, the behavior has been varied but largely undesirable. Additional testing is required in order to have any confidence in the behavior that will result from settings these values.
Release information
For more information [review the diff to see what's changed(https://github.com/roots/bud/compare/v6.3.1...v6.3.2).
v6.3.0
For a better reading experience, read on bud.js.org.
✨ Roots browserslist
@wordpress/browserslist-config is licensed in a way that is not compatible with bud's overall licensing ideals. Accordingly, we're offering a drop-in replacement: @roots/browserslist-config.
There are three configurations available, pick the one most suited to your requirements:
Export | Target | Description |
---|---|---|
@roots/browserslist-config | >1% usage | Similar to @wordpress/browserslist-config |
@roots/browserslist-config/current | >.5% usage | Similar to @facebook/docusaurus |
@roots/browserslist-config/broad | last 3 versions | Similar to @shopify/browserslist-config |
✨ Simpler postcss config
🚨If you are addressing the default plugins by their handle you will need to update it
postcss-import
becomesimport
postcss-nesting
becomesnesting
postcss-preset-env
becomesenv
Essentially 100% of postcss users want three things from postcss: @import
, nesting, and the arcane magic that is postcss-preset-env
. Some want more, but none want less. And out of that 100%, pretty much 100% of them want the plugins in that order.
That 100% figure is absolutely a fabrication, but I do think there is something to it.
Nobody wants nesting before import because it just doesn't work. Everything goes sideways immediately. You probably have an @import
right at the top and postcss has no way to know what to do with it.
So, that basic ordering is locked down now (at least by default — check the docs for information on how to override). The result is that its much simpler to play with the baseline config without breaking everything.
Example: add some-example-plugin
{`bud.postcss.setPlugin('example-postcss-plugin')`}Example: modify postcss-preset-env options
{`bud.postcss.setPluginOptions('env', { features: { customProperties: false, }, })`}✨ Better bundles
It's pretty passe to just go throwing the entirety of node_modules
into a vendor.js
file with bud.splitChunks.
Why not be cool and target only the dependencies that benefit from it?
Introducing a better, simpler way to code split: bud.bundle.
bud.bundle('react')
Or, group some dependencies you always use together:
bud.bundle('react', ['react', 'react-dom'])
Nice chunk!
You're not restricted to node_modules
, use your own code with it too, if you want. The only rule is that the chunk members are being addressed by directory. So, to match your own code you need to make a directory for it.
You can always use bud.splitChunks
if you need more control than that.
🩹 Safer env expansion
bud.js still sources envvars from process and its path, but will no longer attempt to expand values outside of the .env
found in the project root directory.
This could cause issues if ${
and other such character strings were used in bud's path without being escaped.
💭 Extension specific notes
✨ @roots/sage: dynamic imports
If you're using @roots/sage, you should try doing a dynamic import with @roots/sage/client/lazy
:
import lazy from '@roots/sage/client/lazy'
const makeConfetti = async () => {
await lazy(import('canvas-confetti'), confetti => confetti())
}
🩹 @roots/bud-vue: scoped styles
A small configuration issue in the @roots/bud-vue extension has been fixed and scoped stylesheets should no longer make total destroy.
💭 Notes on public path
I'm just going to come out and say it: if you are not serving from web root you should specify a public path.
This is what I'm imagining you saying right now:
- "But, I shouldn't have to set a public path!"
- "Setting a public path is unncessary"
- "A base URL once robbed me on the L train and now I only take ubers"
Here are my responses:
- vue cli requires publicPath outside of web root
- next.js requires publicPath outside of web root
- vite requires publicPath outside of web root
- gatsby requires publicPath outside of web root
- create react app requires publicPath outside of web root
In all seriousness we'll get more specific & explicit about this in the documentation but I can say that in the future bud will expect users not serving assets from web root to define a public path. Right now we characterize
it as "if you want to use dynamic imports", but it will be simplifying to just assume it is set.
I think it is a reasonable requirement and certainly a common one. And, for what it's worth, I'm afraid to take the L train, too.
v6.2.0
If you prefer, read the v6.2.0 release notes on bud.js.org. If only for the table of contents.
🎉 SWC support
This release introduces a new extension: @roots/bud-swc.
The extension registers support for swc.
You can try it out by installing @roots/bud-swc
. Hopefully it "just works" out of the box for you, but check out the docs for details on how to configure swc for your project.
If you have the extension installed @roots/bud
will privilege it over @roots/bud-babel
when it is available. So, you really can give it a go by just adding @roots/bud-swc
to your project dependencies when upgrading.
The swc project claims upwards of a 70x performance improvement over babel. I don't see that in testing (MacOS ARM arch), but it is definitely faster (~20%).
I hear the difference is particularly pronounced in WSL2, although my means to test that is limited. Let me know how it goes!
Better dev experience using @roots/bud-react
For projects using a combination of @roots/bud-react
and either @roots/bud-typescript
, @roots/bud-swc
and/or @roots/bud-babel
, react fast refresh is now enabled by default.
If you don't want react fast refresh then you can disable it with:
bud.react.refresh.disable()
bud.reactRefresh()
you will need to update your call to use the new API.
Change this:
bud.reactRefresh(true)
to this:
bud.react.refresh.enable()
babel is now optional when using @roots/bud-typescript
Depending on your project you may be able to use typescript without babel. Why not try it and find out?
bud.typescript.useBabel(false)
If you disable babel react-refresh will be enabled using the react-refresh-typescript
compiler plugin instead.
@roots/bud-terser is now a core extension
If you have installed this extension directly to your project you can remove it.
If you are using the new SWC extension terser will automatically be configured to use the swc minifier.
Fix: zero config hmr client script injection
There was an issue with client scripts not being injected in zero configuration setups (more specifically: any setup that didn't explicitly set entrypoints with bud.entry).
It's been corrected; see #1506 for details
New command: bud typecheck
TypeScript users can call bud typecheck
to run typechecking outside of compilation. There are a couple requirements, at least for this initial effort:
- You must have
typescript
installed - You must have a
tsconfig.json
in your project root
This is honestly not much more than a tsc --noEmit
alias right now.
But, it might be useful for you as-is and it will get better and more useful in future releases.
More information
Thanks to @robrecord for a nice set of documentation fixes.
v6.1.0
For more information refer to the the v6.1.0 release notes on bud.js.org.
Fix: bud.assets
This release makes some changes to bud.assets in response to feedback from the roots/sage team and members of the Roots Discourse community. Specifically, you will find that directories listed in the bud.assets
call will have their structure preserved in the @dist
directory.
accounting for the path change
After upgrading, it is possible that assets will be emitted to a different directory than you expect. Likely you are anticipating that copied files will be available in the root of @dist
and are finding them now in a subdirectory.
You can specify the dist root as a target to compensate for the change:
// from this (will copy from images dir to dist/images)
bud.assets(['images'])
// to this (will copy from images dir to dist)
bud.assets([['images', './']])
bud.assets api change
bud.assets is no longer variadic. If you are specifying copy jobs using multiple parameters just wrap them in an array:
// from this
bud.assets('images', 'fonts')
// to this
bud.assets(['images', 'fonts'])
Feature: bud.denylist
Bud extensions are automatically utilized in a build if they are listed under the devDependencies
or dependencies
field of package.json
.
They are also automatically utilized when marked as a dep of another extension.
You can add a bud.denylist
field to package.json
to prevent certain extensions from being registered, regardless.
{
"bud": {
"denylist": ["@roots/bud-criticalcss"]
}
}
There is also a bud.allowlist
to whitelist extensions. You should be aware that there are many internal extensions, and things may break if they are not available. If you run a build with the --log
flag the tapped extensions will be summarized at the bottom, but it's probably better to use bud.denylist
.
Improve: @roots/bud-preset-recommend
Use @roots/bud-esbuild
over @roots/bud-babel
when possible
The @roots/bud-preset-recommend
extension will check to see if @roots/bud-esbuild
is available before registering @roots/bud-babel
.
This didn't cause errors prior to 6.1.0, but it did have a cost in terms of build time for users of @roots/bud-preset-recommend
who wanted to use @roots/bud-esbuild
iorf @roots/bud-typescript
instead of @roots/bud-babel
.
Improve: @roots/sage
A few changes here:
domReady
now async compatible
The domReady
named export of @roots/sage/client
can now take an async callback:
import {domReady} from '@roots/sage/client'
domReady(async () => {
// do async stuff
})
New theme.json
features
theme.json
for WordPress, you will need to update your call.
All the methods are now housed under bud.wpjson
. You must also call bud.wpjson.enable
in order to actually emit the file.
bud.wpjson
.useTailwindColors()
.settings(theme => {
theme.set('prop')
})
.enable()
Use tailwind font sizes and families in theme.json
In addition to the theme color palette, you can now generate font sizes and font families from tailwind.config.js
using bud.wpjson.useTailwindFontSize
and bud.wpjson.useTailwindFontFamily
.
app.wpjson
.useTailwindColors()
.useTailwindFontSize()
.useTailwindFontFamily()
.enable()
You can also modify fields which are not under the settings
key using the standard extension API's setOptions
method:
// callback
app.wpjson.setOptions(opts => ({
...options,
customTemplates: [],
}))
// literal
app.wpjson.setOptions({
// using without a callback
// will fully override theme.json
...app.wpjson.options,
})
Check the updated @roots/sage docs for additional usage guidance.
Fix: ts-bud incompatible with bud.typescript.typecheck
The fix for this issue is pretty in the weeds but you can read about it in #1492.
Fix: @roots/bud-eslint
incompatible with filesystem caching
Previously users of @roots/bud-eslint
needed to use bud.persist('memory')
in order to guarantee linting worked as expected. This is no longer the case thanks to #1492.
More information
For more information review the diff to see what's changed.
v6.0.0
This major release transitions bud.js to ESM. It also provides some cool new features (like importing from remote sources), but we'll mainly be talking about the new ESM syntax.
Read the official announcement on bud.js.org.
Introduction
The transition to EcmaScript modules is causing a lot of division and drama in the JS world, at the moment.
Having just finished transitioning all of the nearly 50 packages that make up the bud.js monorepo to use
ESM I can say in all honesty that I totally get it. It was a very frustrating experience.
Which is to say, whatever problems come up with this release, let's work together as a community to help one another
get through it. There will be problems. I hope not a lot, but this is the type of transition that happens every twenty
years, maybe. And I can assure you, on the other side of throwing require
out the window, it feels good to be on the
future-facing side of a great schism like this one.
I hope the appeal for solidarity wasn't too spooky because the good news is that, for the vast majority of
bud.js users, not much is required here.
See "that sindresorhus README" for more context.
Upgrade guide
There are three upgrade paths available to JS users:
- Keep your config file the same and use the
.cjs
extension - Update your config file to use ESM syntax and use the
.mjs
extension - Transition your project to ESM
Things are a litle different for users with configuration files authored in TypeScript, and we'll get to that.
Keep your config file the same and use the .cjs
extension
This is the simplest possible path forward. Update bud.config.js
to bud.config.cjs
.
Caveat: you cannot require
code from @roots/bud
or any first-party extension. Mostly bud.js is structured so
this isn't a normal use case, but if you are using a require statement you'll want to read up on
how to import esm from commonjs.
Update your config file to use ESM syntax and use the extension .mjs
.
This is probably my recommended approach. Update bud.config.js
to bud.config.mjs
.
Update your config function to use the ESM export
syntax:
module.exports = async bud => bud.entry('app', 'index')
becomes:
export default async bud => bud.entry('app', 'index')
Transition your project to ESM
This is the most involved approach. There is no way I can cover it fully in this release post. But, I will try to sketch it out:
First, add a type
field to package.json
indicating your project is opting in to ESM:
{
"name": "project",
"type": "module"
}
Then update bud.config.js
to use export
syntax as described above.
You will also need to update all other files in your project accordingly. Some build tools allow for using export
syntax. Others do not.
For example, stylelint
does not support export
. So you will update stylelint.config.js
to stylelint.config.cjs
.
Jest, on the other hand, does support export
. You will either want to update your jest config file to use ESM export
syntax or rename it to jest.config.cjs
.
Check the documentation for each tool. Read up on ESM.
TypeScript guide
Update config to either bud.config.mts
or bud.config.cts
TypeScript 4.7.2 offers two new TypeScript extensions to deal with CJS/ESM compatibility issues: .cts
and .mts
(they have their own declaration file extensions as well: .d.cts
and .d.mts
).
In general, I'd imagine most TS users already author their bud.config.ts
file with export
syntax. If that's the case, you will likely just want to update the file name to bud.config.mts
.
Use ts-bud
instead of bud
Due to the way ESM modules are loaded you'll need to use ts-bud
instead of bud
when running cli commands. Explanation follows:
A problem with TypeScript and ESM: it is not possible to hack import
at runtime the way we can hack require
at runtime.
bud.js uses ts-node to import TS configs when it is available, but with ESM we also need to register an import
loader so that the config file can be parsed. This can't be done at runtime.
ts-node offers a flag to set this up:
ts-node --esm --transpileOnly
And bud.js offers a bin
that wraps the standard bud
command accordingly: ts-bud
. Use it instead of bud
.
If this doesn't work for you, or you need to adjust other ts-node
flags, you may do this yourself:
ts-node --esm --transpileOnly ./node_modules/.bin/bud build
Known issue: bud.typescript.typecheck.enable()
will die when using ts-bud
It is unclear what the problem is as of right now (see #1480). In order to enable typechecking you must author your config file in JS until this is resolved.
Notes on import vs. require in the context of bud.js
For the most part this shouldn't be an issue. It isn't typical to import or require bud.js code from a project config.
There are exceptions however. For example, if you are using the bud.js node api to generate a config for use with the webpack-cli
.
Updating require
statements to import
If you are writing a config file with .mjs
or you have opted in with "type": "module"
, you will no longer
be able to require
modules or packages in your config file.
The great news is that it's totally possible import CommonJS from an ES Module, so you can convert require
statements to use import
without worrying about it too much:
const value = require('browsersync-webpack-plugin')
becomes:
import value from 'browsersync-webpack-plugin'
Importing ESM from CommonJS
:::danger
You absolutely cannot require
bud.js core. No CommonJS exports are offered.
:::
Importing ESM from CommonJS is a little less straight forward than the other way around. This is one of the reasons we recommend converting your config to .mjs
.
The easiest way is to use a dynamic import
statement. The biggest difference is that a dynamic import is asynchronous, whereas require is sync.
export default async bud => {
await import('browsersync-webpack-plugin')
}
In these cases you may find that the code returned from a dynamic import
is set inside of a property called default
.
Using the bud.module
helper utility
If you wish you may use bud.module.import
instead of import
(which will automatically return the value of default
, if it is set):
export default async bud => {
await bud.module.import('browsersync-webpack-plugin')
}
The node:module
interface provides a function createRequire
that will let you directly require CommonJS
code like you may be used to. See the nodejs docs on createRequire
.
bud.js has an instance of the Require
function available at bud.module.require
(it's context is the directory containing the project package.json
):
export default async bud => {
bud.module.require('browser-sync-webpack-plugin')
// require.resolve works too
bud.module.require.resolve('browser-sync-webpack-plugin')
}
If you just want the path to a module and aren't sure if it is CommonJS or ESM, you may use bud.module.resolve
:
export default async bud => {
await bud.module.resolve('browser-sync-webpack-plugin')
}
This functionality is provided using the import-meta-resolve
package. In the future we'll use the import.meta.resolve
API directly but right now it is labeled as experimental
and requires a flag.
Additional details
Features
- New utility package:
@roots/wordpress-hmr
. Greatly simplifies block editor development for WordPress friends. I'm not sure where this fits into the docs yet, but there is usage information available on this page for now. - You can now use remote modules as if they were local. Check the documentation on remote sources for more information. This works, but it should be considered experimental. Let us know how it goes.
- You can now transpile to
esm
. This feature is not yet documented and should be considered experimental. You can try it out withbud.esm.enable()
, but roots/sage users in particular should be aware that this will
require additional setup. Acorn support for this feature is being worked out. - Critical CSS extension got an update and better documentation to go along with it.
Fixes
- The error overlay got a little janked in 5.8. It's fixed now.
Notes
- All extensions now provide a predictable export of
./extension
(eg:@roots/bud-react/extension
). - All
stylelint
andeslint
presets are exported with the.cjs
extension. If your eslint or stylelint config is already using the preset without an extension (as documented) you don't have to do anything. If you are specifying.js
you will need to update it. - The
lib
andtypes
directories for all packages have been merged. - All published code targets
es2021
. Update to node 16 if you are running an outdated version of node. @roots/bud-support
is deprecated@roots/bud-library
is deprecated (the default...