Skip to content

Commit 9a2de8f

Browse files
vdusekB4nan
andauthored
refactor!: convert to native ESM (#1015)
Mirrors [apify/crawlee@cc01b18](apify/crawlee@cc01b18) and [apify/apify-sdk-js@a381728](apify/apify-sdk-js@a381728), which did the same for Crawlee v4 and for the SDK on its `v4` branch. ## What changed - `"type": "module"`, and the dual CJS+ESM build is gone: no more `dist/index.mjs`, no `gen-esm-wrapper` postbuild step, and the `import`/`require` export conditions collapse into `types` + `browser` + `default`. - Every relative import in `src/` and `test/` gets its `.js` extension, which is the bulk of the diff. It follows from `"type": "module"` alone: the files now emit as ESM, and ESM has no extension guessing or directory-index lookup. `tsc` never rewrites specifiers, so dropping them type-checks clean and then fails at runtime with `ERR_MODULE_NOT_FOUND`. crawlee v4 and apify-sdk-js `v4` do the same. - `require('../package.json')` in `getVersionData()` becomes a JSON import attribute, with `resolveJsonModule` off so the emit doesn't sink into `dist/src`. - `__dirname` becomes `import.meta.dirname` in the test helpers and the Vitest config. - tsconfig pins `module`/`moduleResolution` to `NodeNext` and `target` to `ESNext`, matching crawlee v4 and the SDK. - `test/tsconfig.json` maps `apify-client` to `../src/index.ts` rather than the `../src` directory. `nodenext` has no directory-index lookup, so the directory form type-checked the tests against `dist`. ## The browser bundle is now an ES module `dist/bundle.js` keeps its name and path, but rsbuild now emits it as an ES module with named exports instead of UMD. The `Apify` global is gone: load it with `<script type="module">` and `import { ApifyClient } from '.../bundle.js'`. Under `"type": "module"` Node parses a `.js` file as ESM anyway, and the UMD file in that spot broke `pnpm test:bundling` on all four bundlers. The `apify-client/browser` subpath is unchanged. ## Breaking change The client is pure ESM and ships no CommonJS build. `require('apify-client')` still resolves on Node.js 22.12 and newer, which loads an ES module through `require()` directly. Anywhere else, CommonJS consumers have to switch to `import` or a dynamic `import()`. Documented in the v3 upgrading guide, along with the bundle format change. *✍️ Drafted by Claude Code* --------- Co-authored-by: Martin Adámek <banan23@gmail.com>
1 parent 6114d84 commit 9a2de8f

87 files changed

Lines changed: 411 additions & 418 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/scripts/before-beta-release.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
/* eslint-disable no-console */
2-
const { execSync } = require('node:child_process');
3-
const fs = require('node:fs');
4-
const path = require('node:path');
2+
import { execSync } from 'node:child_process';
3+
import fs from 'node:fs';
4+
import path from 'node:path';
55

6-
const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json');
6+
const PKG_JSON_PATH = path.join(import.meta.dirname, '..', '..', 'package.json');
77

8-
// eslint-disable-next-line import/no-dynamic-require
9-
const pkgJson = require(PKG_JSON_PATH);
8+
const pkgJson = JSON.parse(fs.readFileSync(PKG_JSON_PATH, 'utf8'));
109

1110
const PACKAGE_NAME = pkgJson.name;
1211

CONTRIBUTING.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ scripts/
100100
# Build
101101
pnpm build # Full build (Node + browser bundle)
102102
pnpm build:node # TypeScript compilation only
103-
pnpm build:browser # RSBuild browser/UMD bundle
103+
pnpm build:browser # RSBuild browser bundle (ESM)
104104
pnpm clean # Remove dist directory
105105
106106
# Testing
@@ -123,12 +123,11 @@ pnpm format:check # oxfmt check
123123

124124
### Build Output
125125

126-
The build produces multiple formats:
126+
The build produces:
127127

128-
- CommonJS: `dist/index.js`
129-
- ES Module: `dist/index.mjs`
128+
- ES module: `dist/index.js`
130129
- TypeScript definitions: `dist/index.d.ts`
131-
- Browser bundle (UMD): `dist/bundle.js`
130+
- Browser bundle (ESM): `dist/bundle.js`
132131

133132
## Code Style
134133

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ automatic retries and convenience functions that improve the experience of using
2626
## Quick Start
2727

2828
```js
29-
const { ApifyClient } = require('apify-client');
29+
import { ApifyClient } from 'apify-client';
3030

3131
const client = new ApifyClient({
3232
token: 'MY-APIFY-TOKEN',
@@ -72,7 +72,7 @@ resources are created. There are two types of those clients. A client for manage
7272
resource and a client for a collection of resources.
7373

7474
```js
75-
const { ApifyClient } = require('apify-client');
75+
import { ApifyClient } from 'apify-client';
7676
const apifyClient = new ApifyClient({ token: 'my-token' });
7777

7878
// Collection clients do not require a parameter.

docs/04_upgrading/upgrading_v3.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ import ApiLink from '@theme/ApiLink';
99

1010
This page summarizes the breaking changes when upgrading from v2 to v3 of `apify-client`.
1111

12+
## The package is now pure ESM
13+
14+
`apify-client` ships as an ES module. The CommonJS build is gone, along with the `dist/index.mjs` wrapper and the `require` condition in `exports`, so `import` is the supported way to load the client.
15+
16+
```diff
17+
- const { ApifyClient } = require('apify-client'); // v2
18+
+ import { ApifyClient } from 'apify-client'; // v3
19+
```
20+
21+
A CommonJS project can keep calling `require('apify-client')`: the client has no top-level `await`, and Node.js 22.12 and newer load an ES module through `require()` directly. On Node.js 22.0 to 22.11, `require()` of an ES module is still behind the `--experimental-require-module` flag, so use `import` there.
22+
23+
The browser bundle at `dist/bundle.js` is now an ES module instead of UMD, so it no longer defines an `Apify` global. Importing it, whether through a bundler or the `apify-client/browser` subpath, is unchanged. A classic `<script>` tag that read `Apify.ApifyClient` off the global has to become a `<script type="module">` that imports it instead. For details, see [Bundled environments](../02_concepts/05_bundled-environments.md).
24+
1225
## Argument validation switched from `ow` to `zod`
1326

1427
The client now validates the arguments you pass with [zod](https://zod.dev) instead of `ow`. This changes what gets thrown for invalid arguments, and tightens a few gaps `ow` used to let through silently.

package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@
55
"engines": {
66
"node": ">=22.0.0"
77
},
8+
"type": "module",
89
"main": "dist/index.js",
9-
"module": "dist/index.mjs",
1010
"types": "dist/index.d.ts",
1111
"browser": "dist/bundle.js",
1212
"unpkg": "dist/bundle.js",
1313
"exports": {
1414
"./package.json": "./package.json",
1515
"./browser": "./dist/bundle.js",
1616
".": {
17-
"import": "./dist/index.mjs",
18-
"require": "./dist/index.js",
1917
"types": "./dist/index.d.ts",
20-
"browser": "./dist/bundle.js"
18+
"browser": "./dist/bundle.js",
19+
"default": "./dist/index.js"
2120
}
2221
},
2322
"keywords": [
@@ -54,7 +53,6 @@
5453
],
5554
"scripts": {
5655
"build": "pnpm clean && pnpm build:node && pnpm build:browser",
57-
"postbuild": "gen-esm-wrapper dist/index.js dist/index.mjs",
5856
"prepublishOnly": "(test $CI || (echo \"Publishing is reserved to CI!\"; exit 1))",
5957
"clean": "rimraf dist tsconfig.tsbuildinfo",
6058
"test": "pnpm build && vitest run --project unit",
@@ -111,7 +109,6 @@
111109
"compression": "^1.7.4",
112110
"esbuild": "0.28.2",
113111
"express": "^5.0.0",
114-
"gen-esm-wrapper": "^1.1.2",
115112
"openapi-typescript": "7.13.0",
116113
"oxfmt": "0.65.0",
117114
"oxlint": "1.79.0",

pnpm-lock.yaml

Lines changed: 0 additions & 38 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rsbuild.config.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ export default defineConfig({
4949
rspack(config) {
5050
config.output = {
5151
...config.output,
52-
library: {
53-
type: 'umd', // or 'umd', 'commonjs', etc.
54-
name: 'Apify',
55-
},
56-
globalObject: 'globalThis',
52+
module: true,
53+
library: { type: 'module' },
5754
asyncChunks: false,
5855
};
56+
config.experiments = {
57+
...config.experiments,
58+
outputModule: true,
59+
};
5960
config.optimization = {
6061
...config.optimization,
6162
splitChunks: false,

src/apify_api_error.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AxiosResponse } from 'axios';
22

3-
import { isomorphicBufferToString } from './body_parser';
4-
import { isBuffer } from './utils';
3+
import { isomorphicBufferToString } from './body_parser.js';
4+
import { isBuffer } from './utils.js';
55

66
/**
77
* Examples of capturing groups for "...at ActorCollectionClient._list (/Users/..."

src/apify_client.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,34 @@ import { ACTOR_ENV_VARS, ME_USER_NAME_PLACEHOLDER } from '@apify/consts';
55
import type { Log } from '@apify/log';
66
import logger from '@apify/log';
77

8-
import { HttpClient } from './http_client';
9-
import type { RequestInterceptorFunction } from './interceptors';
10-
import { ActorClient } from './resource_clients/actor';
11-
import { ActorCollectionClient } from './resource_clients/actor_collection';
12-
import { BuildClient } from './resource_clients/build';
13-
import { BuildCollectionClient } from './resource_clients/build_collection';
14-
import { DatasetClient } from './resource_clients/dataset';
15-
import { DatasetCollectionClient } from './resource_clients/dataset_collection';
16-
import { KeyValueStoreClient } from './resource_clients/key_value_store';
17-
import { KeyValueStoreCollectionClient } from './resource_clients/key_value_store_collection';
18-
import { LogClient } from './resource_clients/log';
19-
import type { RequestQueueUserOptions } from './resource_clients/request_queue';
20-
import { RequestQueueClient } from './resource_clients/request_queue';
21-
import { RequestQueueCollectionClient } from './resource_clients/request_queue_collection';
22-
import { RunClient } from './resource_clients/run';
23-
import { RunCollectionClient } from './resource_clients/run_collection';
24-
import { ScheduleClient } from './resource_clients/schedule';
25-
import { ScheduleCollectionClient } from './resource_clients/schedule_collection';
26-
import { StoreCollectionClient } from './resource_clients/store_collection';
27-
import { TaskClient } from './resource_clients/task';
28-
import { TaskCollectionClient } from './resource_clients/task_collection';
29-
import { UserClient } from './resource_clients/user';
30-
import { WebhookClient } from './resource_clients/webhook';
31-
import { WebhookCollectionClient } from './resource_clients/webhook_collection';
32-
import { WebhookDispatchClient } from './resource_clients/webhook_dispatch';
33-
import { WebhookDispatchCollectionClient } from './resource_clients/webhook_dispatch_collection';
34-
import { Statistics } from './statistics';
35-
import { parseArgument } from './utils';
8+
import { HttpClient } from './http_client.js';
9+
import type { RequestInterceptorFunction } from './interceptors.js';
10+
import { ActorClient } from './resource_clients/actor.js';
11+
import { ActorCollectionClient } from './resource_clients/actor_collection.js';
12+
import { BuildClient } from './resource_clients/build.js';
13+
import { BuildCollectionClient } from './resource_clients/build_collection.js';
14+
import { DatasetClient } from './resource_clients/dataset.js';
15+
import { DatasetCollectionClient } from './resource_clients/dataset_collection.js';
16+
import { KeyValueStoreClient } from './resource_clients/key_value_store.js';
17+
import { KeyValueStoreCollectionClient } from './resource_clients/key_value_store_collection.js';
18+
import { LogClient } from './resource_clients/log.js';
19+
import type { RequestQueueUserOptions } from './resource_clients/request_queue.js';
20+
import { RequestQueueClient } from './resource_clients/request_queue.js';
21+
import { RequestQueueCollectionClient } from './resource_clients/request_queue_collection.js';
22+
import { RunClient } from './resource_clients/run.js';
23+
import { RunCollectionClient } from './resource_clients/run_collection.js';
24+
import { ScheduleClient } from './resource_clients/schedule.js';
25+
import { ScheduleCollectionClient } from './resource_clients/schedule_collection.js';
26+
import { StoreCollectionClient } from './resource_clients/store_collection.js';
27+
import { TaskClient } from './resource_clients/task.js';
28+
import { TaskCollectionClient } from './resource_clients/task_collection.js';
29+
import { UserClient } from './resource_clients/user.js';
30+
import { WebhookClient } from './resource_clients/webhook.js';
31+
import { WebhookCollectionClient } from './resource_clients/webhook_collection.js';
32+
import { WebhookDispatchClient } from './resource_clients/webhook_dispatch.js';
33+
import { WebhookDispatchCollectionClient } from './resource_clients/webhook_dispatch_collection.js';
34+
import { Statistics } from './statistics.js';
35+
import { parseArgument } from './utils.js';
3636

3737
const DEFAULT_TIMEOUT_SECS = 360;
3838

src/base/api_client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { ApifyClient } from '../apify_client';
2-
import type { HttpClient } from '../http_client';
3-
import type { PaginatedResponse, PaginationOptions } from '../utils';
4-
import { toPath, toPathSegment } from '../utils';
1+
import type { ApifyClient } from '../apify_client.js';
2+
import type { HttpClient } from '../http_client.js';
3+
import type { PaginatedResponse, PaginationOptions } from '../utils.js';
4+
import { toPath, toPathSegment } from '../utils.js';
55

66
/** @private */
77
export interface ApiClientOptions {

0 commit comments

Comments
 (0)