@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 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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../cli.mjs';
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
+ }