Skip to content

Releases: roots/bud

v6.4.0

20 Sep 23:11
Compare
Choose a tag to compare

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 of package.json
  • 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:

[bud@6.4.0] [my-project] › importing @roots/bud-terser/css-minimizer

ℹ️ Release information

For more information review the diff to see what's changed.

v6.3.5

28 Jul 21:13
Compare
Choose a tag to compare

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

For more information review the diff to see what's changed.

v6.3.4

26 Jul 12:13
Compare
Choose a tag to compare

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

19 Jul 08:21
Compare
Choose a tag to compare

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

15 Jul 22:59
Compare
Choose a tag to compare

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

11 Jul 19:41
Compare
Choose a tag to compare

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 becomes import
  • postcss-nesting becomes nesting
  • postcss-preset-env becomes env

More information added to docs

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:

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

28 Jun 09:27
Compare
Choose a tag to compare

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()

⚠️ If you are currently configuring fast-refresh using 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.

Review the diff to see what's changed.

v6.1.0

28 Jun 09:18
Compare
Choose a tag to compare

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

⚠️ If you were already using bud.js to generate 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

15 Jun 18:29
Compare
Choose a tag to compare

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:

  1. Keep your config file the same and use the .cjs extension
  2. Update your config file to use ESM syntax and use the .mjs extension
  3. 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 with bud.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 and eslint 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 and types 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...
Read more

v5.8.7

24 May 10:02
Compare
Choose a tag to compare

Some minor improvements to the @roots/bud-terser extension and a fix for projects with development and production in the project path. Recommended for all users of 5.8.x.

This release also updates many dependencies. Compare v5.8.6 and v5.8.7 to see the full list of what got bumped.