Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions src/content/docs/docs/guides/writing-tasks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Run from "@hh/Run.astro";

At its core, Hardhat is a task runner that lets you automate your development workflow. It comes with built-in tasks like `compile` and `test`, but you can also add your own custom tasks.

This guide will show you how to extend Hardhat's functionality using tasks. It assumes you've initialized a sample project. If you haven't, please read the [getting started guide](/docs/getting-started) first.
In this guide, we'll explore how to extend Hardhat's functionality using tasks. It assumes you've initialized a sample project. If you haven't, read the [getting started guide](/docs/getting-started) first.

## Writing a task

Expand Down Expand Up @@ -61,8 +61,60 @@ We're using the `task` function to define our new task. Its first argument is th

The `task` function returns a task builder object that lets you further configure the task. In this case, we use the `setAction` method to define the task's behavior by providing a function that lazy loads another module.

That module exports the action function itself. You can do anything you want in this function. In this case, we send a request to the network provider to get all the configured accounts and print their addresses.
That module exports the action function itself, which implements your custom logic. In this case, we send a request to the network provider to get all the configured accounts and print their addresses.

You can add parameters to your tasks, and Hardhat will handle their parsing and validation for you. You can also override existing tasks, which lets you customize how different parts of Hardhat work.
Add parameters to your tasks, and Hardhat handles their parsing and validation for you. Override existing tasks to customize how different parts of Hardhat work.

## Using inline actions

As an alternative to defining task actions in separate files, define them directly inline using `setInlineAction`. This is convenient for simple tasks where a separate file would be overkill.

Here's how the accounts task would look using an inline action:

```ts
// hardhat.config.ts
import { defineConfig, task } from "hardhat/config";

const printAccounts = task("accounts", "Print the accounts")
.setInlineAction(async (taskArguments, hre) => {
const { provider } = await hre.network.connect();
console.log(await provider.request({ method: "eth_accounts" }));
})
.build();

export default defineConfig({
// ... rest of the config
tasks: [printAccounts],
});
```

With `setInlineAction`, the task logic is defined directly as a function parameter, eliminating the need for a separate file.

### Choosing between `setAction` and `setInlineAction`

Use `setAction` when you're building a plugin or a complex task with a lot of code, or if it needs to import dependencies. This keeps the code organized, improves Hardhat's load time (as they're loaded on demand), and makes your setup or plugin more resilient to installation errors.

On the other hand, if you're building a simple task that only uses the Hardhat Runtime Environment, use `setInlineAction` to define the task's behavior inline, without the boilerplate of a separate file.

Here's a comparison of the two approaches:

| | `setAction()` (Lazy-loaded) | `setInlineAction()` |
| -------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------- |
| **Use cases** | Complex tasks <br /> Plugin tasks <br /> Tasks that import dependencies | Simple user tasks |
| **Available for plugins** | ✅ Yes (required) | ❌ No |
| **Available for users** | ✅ Yes | ✅ Yes |
| **Performance** | Lazy-loaded on demand | Loaded every time you run Hardhat |
| **File organization** | Separate action files | Defined inline in your config |
| **Can use `import { ... } from 'hardhat'`?** | ✅ Yes, because they are loaded after Hardhat's initialization | ❌ No, because they are evaluated during Hardhat's initialization |

Each task must define exactly one action: call either `setAction()` or `setInlineAction()`, but not both.

You can also use `setInlineAction` with `overrideTask` to customize existing tasks directly in your config file.

:::caution[Plugin developers]
If you're developing a plugin, you must use `setAction()` with lazy-loaded modules. The `setInlineAction()` method is only available for user-defined tasks in configuration files.
:::

To learn more about how task actions are loaded, see the [Task Actions' lifecycle](/docs/plugin-development/explanations/lifecycle#task-actions-lifecycle) documentation.

{/* To learn more about tasks, please read [this guide](../advanced/create-task). */}
35 changes: 34 additions & 1 deletion src/content/docs/docs/migrate-from-hardhat2/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,40 @@ This section assumes that your Hardhat 2 project uses Mocha as its tests runner,

<Run command="hardhat test test/some-test.ts" />

{/* A section was removed here until we have setInlineAction. Take a look at the git history to restore it. */}
## Migrating custom tasks

In Hardhat 2, tasks were defined imperatively in the config file, and the action function was passed directly:

```ts
// Hardhat 2
task("accounts", "Prints the accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
```

In Hardhat 3, the equivalent uses `setInlineAction` and requires calling `.build()` and adding the task to the `tasks` array in your config:

```ts ins={4-5,8-9,13}
// hardhat.config.ts
import { defineConfig, task } from "hardhat/config";

const printAccounts = task("accounts", "Print the accounts")
.setInlineAction(async (taskArguments, hre) => {
const { provider } = await hre.network.connect();
console.log(await provider.request({ method: "eth_accounts" }));
})
.build();

export default defineConfig({
// ... rest of the config
tasks: [printAccounts],
});
```

For more complex tasks, you can use `setAction()` with a separate file for lazy loading. To learn more, see [Writing Hardhat tasks](/docs/guides/writing-tasks).

## Migrating `extendConfig` and `extendEnvironment`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,14 @@ The lifecycle of a Dynamic Hook Handler is simpler, as it's manually registered

## Task Actions' lifecycle

You define Task Actions using the `setAction` method of the `TaskDefinitionBuilder`s APIs.
Task actions define the behavior that executes when a task runs. There are two ways to define task actions in Hardhat 3:

1. **Lazy-loaded actions** using `setAction()` - Required for plugins
2. **Inline actions** using `setInlineAction()` - Only for user tasks

### Lazy-loaded actions (plugins must use this)

Plugins must always use the `setAction` method with lazy-loaded modules. This method is also available for user-defined tasks in configuration files.

It looks like this:

Expand Down Expand Up @@ -226,6 +233,32 @@ export default async function (
}
```

**Why plugins must use lazy-loaded actions:**

- **Performance**: Lazy loading ensures plugin code is only loaded when needed, keeping Hardhat initialization fast
- **Robustness**: Lazy loading also makes Hardhat more robust to plugin errors (e.g. installation issues), as a failure in one part of your plugin won't affect the rest of the system

### Inline actions (user tasks only)

User-defined tasks in configuration files can optionally use `setInlineAction()` to define task behavior directly inline. This is convenient for simple tasks but is **not available for plugins**.

Example of an inline action:

```ts
task("accounts", "Print the accounts")
.setInlineAction(async (taskArguments, hre) => {
const { provider } = await hre.network.connect();
console.log(await provider.request({ method: "eth_accounts" }));
})
.build();
```

Inline actions are loaded immediately when the configuration file is evaluated, so they don't benefit from lazy loading. For a practical guide on choosing between action types, see [Writing Hardhat tasks](/docs/guides/writing-tasks#choosing-between-setaction-and-setinlineaction).

:::caution[Plugin restriction]
Plugins cannot use `setInlineAction()`. Attempting to do so will result in an error. Plugins must always use `setAction()` with lazy-loaded modules.
:::

## Configuration Variables' lifecycle

There are two things that plugins can do with Configuration Variables:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ An array of Global Option definitions. You can create them using the `globalOpti
### `tasks`

An array of Task definitions. Define them using the `task` and `overrideTask` functions exported by `hardhat/config`.

**Important:** Plugin tasks must use `setAction()` with lazy-loaded modules. The `setInlineAction()` method is not available for plugin tasks. To learn more about task action lifecycle and the differences between these methods, see the [Task Actions' lifecycle](/docs/plugin-development/explanations/lifecycle#task-actions-lifecycle) documentation.
6 changes: 6 additions & 0 deletions src/content/docs/docs/plugin-development/tutorial/task.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ task("my-account", "Prints your account.")

You'll get an error in the highlighted line because a file is missing. We'll create it in the next section.

:::caution[Plugin tasks must use setAction]
Plugins must always use `setAction()` with lazy-loaded modules. The `setInlineAction()` method is only available for user-defined tasks in configuration files.

To learn more, see the [Task Actions' lifecycle](/docs/plugin-development/explanations/lifecycle#task-actions-lifecycle) documentation.
:::

## Creating a task action file

The task action is defined in a separate file that's only imported when the task runs.
Expand Down