@badisi/agadoo 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/bin/bagadoo +3 -0
- package/cli.mjs +145 -0
- package/index.d.mts +10 -0
- package/index.mjs +27 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Badisi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
@badisi/agadoo
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
🍃 <i>Check whether a package is tree-shakeable.</i><br/>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@badisi/agadoo">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/@badisi/agadoo.svg?color=blue&logo=npm" alt="npm version" /></a>
|
|
12
|
+
<a href="https://npmcharts.com/compare/@badisi/agadoo?minimal=true">
|
|
13
|
+
<img src="https://img.shields.io/npm/dw/@badisi/agadoo.svg?color=7986CB&logo=npm" alt="npm donwloads" /></a>
|
|
14
|
+
<a href="https://github.com/Badisi/agadoo/blob/main/LICENSE">
|
|
15
|
+
<img src="https://img.shields.io/npm/l/@badisi/agadoo.svg?color=ff69b4" alt="license" /></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://github.com/Badisi/agadoo/actions/workflows/ci_tests.yml">
|
|
20
|
+
<img src="https://img.shields.io/github/actions/workflow/status/badisi/agadoo/ci_tests.yml?logo=github" alt="build status" /></a>
|
|
21
|
+
<a href="https://github.com/Badisi/agadoo/blob/main/CONTRIBUTING.md#-submitting-a-pull-request-pr">
|
|
22
|
+
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome" /></a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<hr/>
|
|
26
|
+
|
|
27
|
+
#### Tree-shaking
|
|
28
|
+
|
|
29
|
+
> It is a form of `dead-code elimination` that optimizes your production bundle size by removing unused code.
|
|
30
|
+
>
|
|
31
|
+
> With the advent of JavaScript modules (import and export), it is now possible to build libraries that are `tree-shakeable`. This means that a user of your library can import just the bits they need, without burdening their users with all the code they are *not* using. Modern bundlers use static analysis to track your export dependency graph, automatically pruning the "dead leaves" (unused exports) so that only active code ships to the browser.
|
|
32
|
+
|
|
33
|
+
<hr/>
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Getting started
|
|
37
|
+
|
|
38
|
+
This tool checks whether your JavaScript library is `tree-shakeable` by analyzing it with [Rollup](https://rollupjs.org).
|
|
39
|
+
|
|
40
|
+
> [!NOTE]
|
|
41
|
+
> This project is a revamp of the original [agadoo](https://github.com/Rich-Harris/agadoo) by [Rich Harris](https://github.com/Rich-Harris).
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npm install -g @badisi/agadoo
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
yarn add @badisi/agadoo
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
bagadoo [path] [options]
|
|
59
|
+
|
|
60
|
+
# or without installation, using `npx` directly:
|
|
61
|
+
npx @badisi/agadoo [path] [options]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#### Arguments
|
|
66
|
+
|
|
67
|
+
| Argument | Description |
|
|
68
|
+
| :--- | :--- |
|
|
69
|
+
| `path` | Path to an entry file or package directory.</br>*Defaults to reading the `module` or `main` field from `package.json` in the current directory*. |
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
#### Options
|
|
73
|
+
|
|
74
|
+
| Option | Description |
|
|
75
|
+
| :--- | :--- |
|
|
76
|
+
| `-c, --config <file>` | Path to a Rollup config file for custom plugins, externals, etc. |
|
|
77
|
+
| `-v, --version` | Print version. |
|
|
78
|
+
| `-h, --help` | Show help message. |
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
#### Examples
|
|
82
|
+
|
|
83
|
+
* **Check the package in the current directory:**
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
bagadoo
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
* **Check a specific file:**
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
bagadoo dist/my-library.js
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
* **Check a package directory:**
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
bagadoo packages/my-lib
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
* **Use a custom Rollup config file to handle plugins or mark external dependencies:**
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
bagadoo --config rollup.config.js
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
* **Add it to your `prepublishOnly` script to prevent publishing if tree-shaking fails:**
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"scripts": {
|
|
112
|
+
"prepublishOnly": "bagadoo"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## API
|
|
119
|
+
|
|
120
|
+
You can also use agadoo programmatically:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { check } from '@badisi/agadoo';
|
|
124
|
+
|
|
125
|
+
const result = await check('./dist/my-library.js');
|
|
126
|
+
console.log(result.isShaken); // true or false
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Pass custom Rollup options:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { check } from '@badisi/agadoo';
|
|
133
|
+
|
|
134
|
+
const result = await check('./dist/my-library.js', {
|
|
135
|
+
external: ['some-external-dep'],
|
|
136
|
+
plugins: [myPlugin()]
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
## Help! my library isn't tree-shakeable
|
|
142
|
+
|
|
143
|
+
If tree-shaking fails, it means `Rollup` found side-effects in your code.
|
|
144
|
+
|
|
145
|
+
Here are common guidelines:
|
|
146
|
+
|
|
147
|
+
* Avoid transpilers like `Babel` and `Bublé` (if you're using TypeScript, target a modern JS version)
|
|
148
|
+
* Export plain functions — don't create instances of things on initial evaluation
|
|
149
|
+
* The output will show the code that remains after tree-shaking to help you diagnose the issue
|
|
150
|
+
|
|
151
|
+
If your library needs custom Rollup plugins (e.g., to handle non-standard file types), use the `--config` option.
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
See the [developer docs][developer].
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
## Contributing
|
|
160
|
+
|
|
161
|
+
#### > Want to Help ?
|
|
162
|
+
|
|
163
|
+
Want to file a bug, contribute some code or improve documentation ? Excellent!
|
|
164
|
+
|
|
165
|
+
But please read up first on the guidelines for [contributing][contributing], and learn about submission process, coding rules and more.
|
|
166
|
+
|
|
167
|
+
#### > Code of Conduct
|
|
168
|
+
|
|
169
|
+
Please read and follow the [Code of Conduct][codeofconduct] and help us keep this project open and inclusive.
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
[developer]: https://github.com/Badisi/agadoo/blob/main/DEVELOPER.md
|
|
174
|
+
[contributing]: https://github.com/Badisi/agadoo/blob/main/CONTRIBUTING.md
|
|
175
|
+
[codeofconduct]: https://github.com/Badisi/agadoo/blob/main/CODE_OF_CONDUCT.md
|
package/bin/bagadoo
ADDED
package/cli.mjs
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { access, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { relative, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { styleText } from "node:util";
|
|
5
|
+
import { rollup } from "rollup";
|
|
6
|
+
import virtual from "@rollup/plugin-virtual";
|
|
7
|
+
//#region package.json
|
|
8
|
+
var version = "4.0.0";
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/index.ts
|
|
11
|
+
const check = async (path, rollupOptions) => {
|
|
12
|
+
const { plugins, onwarn, ...restOptions } = rollupOptions ?? {};
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const { output } = await (await rollup({
|
|
15
|
+
...restOptions,
|
|
16
|
+
input: "__badisi_agadoo__",
|
|
17
|
+
plugins: [virtual({ __badisi_agadoo__: `import * as __agadoo__ from ${JSON.stringify(path)}` }), ...Array.isArray(plugins) ? plugins : plugins ? [plugins] : []],
|
|
18
|
+
onwarn: (warning, handle) => {
|
|
19
|
+
if (onwarn) onwarn(warning, handle);
|
|
20
|
+
else if (warning.code !== "EMPTY_BUNDLE") warnings.push(warning.message);
|
|
21
|
+
}
|
|
22
|
+
})).generate({ format: "esm" });
|
|
23
|
+
const code = output[0].code.trim();
|
|
24
|
+
return {
|
|
25
|
+
isShaken: !code.split("\n").some((line) => {
|
|
26
|
+
const t = line.trim();
|
|
27
|
+
return t && !t.startsWith("import ") && !t.startsWith("//") && !t.startsWith("/*") && !t.startsWith("*") && !t.startsWith("*/");
|
|
28
|
+
}),
|
|
29
|
+
code,
|
|
30
|
+
warnings
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/cli.ts
|
|
35
|
+
const exitWithError = (message) => {
|
|
36
|
+
console.error(message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
};
|
|
39
|
+
const ifExists = async (path) => {
|
|
40
|
+
try {
|
|
41
|
+
await access(path);
|
|
42
|
+
return path;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const isDirectory = async (path) => {
|
|
48
|
+
try {
|
|
49
|
+
return (await stat(path)).isDirectory();
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const resolveEntry = async (value) => {
|
|
55
|
+
if (await isDirectory(value)) return await ifExists(`${value}/index.mjs`) ?? await ifExists(`${value}/index.js`);
|
|
56
|
+
return await ifExists(value) ?? await ifExists(`${value}.mjs`) ?? await ifExists(`${value}.js`);
|
|
57
|
+
};
|
|
58
|
+
const getInput = async () => {
|
|
59
|
+
let pkgJson;
|
|
60
|
+
try {
|
|
61
|
+
pkgJson = JSON.parse(await readFile("package.json", "utf-8"));
|
|
62
|
+
} catch {
|
|
63
|
+
exitWithError(`Could not find or parse 'package.json'`);
|
|
64
|
+
}
|
|
65
|
+
const name = pkgJson.module ?? pkgJson.main ?? "index";
|
|
66
|
+
const entryPath = await resolveEntry(name);
|
|
67
|
+
if (!entryPath) exitWithError(`Could not resolve entry point '${name}' from package.json`);
|
|
68
|
+
return entryPath;
|
|
69
|
+
};
|
|
70
|
+
const displayHelp = () => {
|
|
71
|
+
console.log([
|
|
72
|
+
`🍃 ${styleText(["bgGreen", "black"], " bagadoo ")}`,
|
|
73
|
+
"",
|
|
74
|
+
styleText("bold", "VERSION:"),
|
|
75
|
+
` ${styleText("green", version)}`,
|
|
76
|
+
"",
|
|
77
|
+
styleText("bold", "DESCRIPTION:"),
|
|
78
|
+
" Check whether a package is tree-shakeable.",
|
|
79
|
+
"",
|
|
80
|
+
styleText("bold", "USAGE:"),
|
|
81
|
+
" $ bagadoo [path] [options]",
|
|
82
|
+
"",
|
|
83
|
+
styleText("bold", "ARGUMENTS:"),
|
|
84
|
+
` ${styleText("cyan", "[path]")} Path to an entry file or package directory`,
|
|
85
|
+
` ${styleText(["gray", "italic"], " (default: read module or main from package.json)")}`,
|
|
86
|
+
"",
|
|
87
|
+
styleText("bold", "OPTIONS:"),
|
|
88
|
+
` ${styleText("cyan", "-c, --config <file>")} Path to a Rollup config file`,
|
|
89
|
+
` ${styleText("cyan", "-v, --version")} Print version`,
|
|
90
|
+
` ${styleText("cyan", "-h, --help")} Show this help message`
|
|
91
|
+
].join("\n"));
|
|
92
|
+
};
|
|
93
|
+
const getOptions = async () => {
|
|
94
|
+
const args = process.argv.slice(2);
|
|
95
|
+
let configPath;
|
|
96
|
+
const configIndex = args.findIndex((a) => a.startsWith("--config=") || a.startsWith("-c="));
|
|
97
|
+
if (configIndex !== -1) {
|
|
98
|
+
const match = args[configIndex];
|
|
99
|
+
configPath = match.slice(match.indexOf("=") + 1);
|
|
100
|
+
args.splice(configIndex, 1);
|
|
101
|
+
}
|
|
102
|
+
for (const flag of ["--config", "-c"]) {
|
|
103
|
+
const idx = args.indexOf(flag);
|
|
104
|
+
if (idx !== -1) {
|
|
105
|
+
configPath = args[idx + 1];
|
|
106
|
+
args.splice(idx, 2);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
let rollupOptions;
|
|
111
|
+
if (configPath) {
|
|
112
|
+
const configModule = await import(pathToFileURL(resolve(process.cwd(), configPath)).href);
|
|
113
|
+
const config = configModule.default ?? configModule;
|
|
114
|
+
rollupOptions = config?.rollup ?? config;
|
|
115
|
+
}
|
|
116
|
+
const input = args[0];
|
|
117
|
+
const absolutePath = input ? await resolveEntry(resolve(process.cwd(), input)) ?? resolve(process.cwd(), input) : resolve(process.cwd(), await getInput());
|
|
118
|
+
const relativePath = relative(process.cwd(), absolutePath);
|
|
119
|
+
return {
|
|
120
|
+
rollupOptions,
|
|
121
|
+
absolutePath,
|
|
122
|
+
relativePath
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
(async () => {
|
|
126
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
127
|
+
console.log(version);
|
|
128
|
+
process.exit();
|
|
129
|
+
} else if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
130
|
+
displayHelp();
|
|
131
|
+
process.exit();
|
|
132
|
+
} else {
|
|
133
|
+
const options = await getOptions();
|
|
134
|
+
const result = await check(options.absolutePath, options.rollupOptions);
|
|
135
|
+
result.warnings.forEach((w) => console.error(styleText("yellow", w)));
|
|
136
|
+
result.code.split("\n").forEach((line) => {
|
|
137
|
+
const t = line.trim();
|
|
138
|
+
console.error(t.startsWith("//#region") || t.startsWith("//#endregion") ? styleText("gray", line) : line);
|
|
139
|
+
});
|
|
140
|
+
if (result.isShaken) console.log(`\n${styleText("green", "✓ Success:")} ${styleText("cyan", options.relativePath)} ${styleText("green", "is fully tree-shakeable.")}`);
|
|
141
|
+
else exitWithError(`\n${styleText("red", "✗ Failed:")} ${styleText("cyan", options.relativePath)} ${styleText("red", "is not fully tree-shakeable.")}`);
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
//#endregion
|
|
145
|
+
export {};
|
package/index.d.mts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RollupOptions } from "rollup";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
interface CheckResult {
|
|
4
|
+
isShaken: boolean;
|
|
5
|
+
code: string;
|
|
6
|
+
warnings: string[];
|
|
7
|
+
}
|
|
8
|
+
declare const check: (path: string, rollupOptions?: Partial<RollupOptions>) => Promise<CheckResult>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { CheckResult, check };
|
package/index.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { rollup } from "rollup";
|
|
2
|
+
import virtual from "@rollup/plugin-virtual";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const check = async (path, rollupOptions) => {
|
|
5
|
+
const { plugins, onwarn, ...restOptions } = rollupOptions ?? {};
|
|
6
|
+
const warnings = [];
|
|
7
|
+
const { output } = await (await rollup({
|
|
8
|
+
...restOptions,
|
|
9
|
+
input: "__badisi_agadoo__",
|
|
10
|
+
plugins: [virtual({ __badisi_agadoo__: `import * as __agadoo__ from ${JSON.stringify(path)}` }), ...Array.isArray(plugins) ? plugins : plugins ? [plugins] : []],
|
|
11
|
+
onwarn: (warning, handle) => {
|
|
12
|
+
if (onwarn) onwarn(warning, handle);
|
|
13
|
+
else if (warning.code !== "EMPTY_BUNDLE") warnings.push(warning.message);
|
|
14
|
+
}
|
|
15
|
+
})).generate({ format: "esm" });
|
|
16
|
+
const code = output[0].code.trim();
|
|
17
|
+
return {
|
|
18
|
+
isShaken: !code.split("\n").some((line) => {
|
|
19
|
+
const t = line.trim();
|
|
20
|
+
return t && !t.startsWith("import ") && !t.startsWith("//") && !t.startsWith("/*") && !t.startsWith("*") && !t.startsWith("*/");
|
|
21
|
+
}),
|
|
22
|
+
code,
|
|
23
|
+
warnings
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { check };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@badisi/agadoo",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Check whether a package is tree-shakeable.",
|
|
5
|
+
"homepage": "https://github.com/Badisi/agadoo",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Badisi"
|
|
9
|
+
},
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./index.mjs",
|
|
13
|
+
"module": "./index.mjs",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./index.d.mts",
|
|
17
|
+
"default": "./index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"bagadoo": "bin/bagadoo"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Badisi/agadoo.git"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"agadoo",
|
|
30
|
+
"rollup",
|
|
31
|
+
"analyzer",
|
|
32
|
+
"checker",
|
|
33
|
+
"tree-shaking",
|
|
34
|
+
"tree-shakeable",
|
|
35
|
+
"esm"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">= 20.12.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@rollup/plugin-virtual": "^3.0.2",
|
|
42
|
+
"rollup": "^4.62.2"
|
|
43
|
+
},
|
|
44
|
+
"allowScripts": {
|
|
45
|
+
"esbuild": true,
|
|
46
|
+
"fsevents": true
|
|
47
|
+
}
|
|
48
|
+
}
|