@benjavicente/router-utils 1.161.6
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/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/ast.cjs +104 -0
- package/dist/cjs/ast.cjs.map +1 -0
- package/dist/cjs/ast.d.cts +51 -0
- package/dist/cjs/copy-files-plugin.cjs +24 -0
- package/dist/cjs/copy-files-plugin.cjs.map +1 -0
- package/dist/cjs/copy-files-plugin.d.cts +6 -0
- package/dist/cjs/index.cjs +13 -0
- package/dist/cjs/index.d.cts +4 -0
- package/dist/cjs/logger.cjs +50 -0
- package/dist/cjs/logger.cjs.map +1 -0
- package/dist/cjs/logger.d.cts +1 -0
- package/dist/esm/ast.d.ts +51 -0
- package/dist/esm/ast.js +98 -0
- package/dist/esm/ast.js.map +1 -0
- package/dist/esm/copy-files-plugin.d.ts +6 -0
- package/dist/esm/copy-files-plugin.js +23 -0
- package/dist/esm/copy-files-plugin.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/logger.d.ts +1 -0
- package/dist/esm/logger.js +48 -0
- package/dist/esm/logger.js.map +1 -0
- package/package.json +83 -0
- package/src/ast.ts +164 -0
- package/src/copy-files-plugin.ts +34 -0
- package/src/index.ts +11 -0
- package/src/logger.ts +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-present Tanner Linsley
|
|
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.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
exports.__toESM = __toESM;
|
package/dist/cjs/ast.cjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let _babel_parser = require("@babel/parser");
|
|
3
|
+
let _babel_generator = require("@babel/generator");
|
|
4
|
+
_babel_generator = require_runtime.__toESM(_babel_generator);
|
|
5
|
+
let _babel_types = require("@babel/types");
|
|
6
|
+
_babel_types = require_runtime.__toESM(_babel_types);
|
|
7
|
+
let babel_dead_code_elimination = require("babel-dead-code-elimination");
|
|
8
|
+
//#region src/ast.ts
|
|
9
|
+
function parseAst({ code, ...opts }) {
|
|
10
|
+
return (0, _babel_parser.parse)(code, {
|
|
11
|
+
plugins: [
|
|
12
|
+
"jsx",
|
|
13
|
+
"typescript",
|
|
14
|
+
"explicitResourceManagement",
|
|
15
|
+
"decorators"
|
|
16
|
+
],
|
|
17
|
+
sourceType: "module",
|
|
18
|
+
...opts
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
var generate = _babel_generator.default;
|
|
22
|
+
if ("default" in generate) generate = generate.default;
|
|
23
|
+
function generateFromAst(ast, opts) {
|
|
24
|
+
return generate(ast, opts ? {
|
|
25
|
+
importAttributesKeyword: "with",
|
|
26
|
+
sourceMaps: true,
|
|
27
|
+
...opts
|
|
28
|
+
} : void 0);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Strips TypeScript type-only exports and imports from an AST.
|
|
32
|
+
*
|
|
33
|
+
* This is necessary because babel-dead-code-elimination doesn't handle
|
|
34
|
+
* TypeScript type exports/imports. When a type export references an import
|
|
35
|
+
* that pulls in server-only code, the dead code elimination won't remove
|
|
36
|
+
* that import because it sees the type as still referencing it.
|
|
37
|
+
*
|
|
38
|
+
* This function removes:
|
|
39
|
+
* - `export type Foo = ...`
|
|
40
|
+
* - `export interface Foo { ... }`
|
|
41
|
+
* - `export type { Foo } from './module'`
|
|
42
|
+
* - `export type * from './module'`
|
|
43
|
+
* - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`
|
|
44
|
+
* - `import type { Foo } from './module'`
|
|
45
|
+
* - Type specifiers in mixed imports: `import { value, type Foo } from './module'` -> `import { value }`
|
|
46
|
+
*
|
|
47
|
+
* Note: Non-exported type/interface declarations are preserved as they may be
|
|
48
|
+
* used as type annotations within the code.
|
|
49
|
+
*
|
|
50
|
+
* @param ast - The Babel AST (or ParseResult) to mutate
|
|
51
|
+
*/
|
|
52
|
+
function stripTypeExports(ast) {
|
|
53
|
+
ast.program.body = ast.program.body.filter((node) => {
|
|
54
|
+
if (_babel_types.isExportNamedDeclaration(node)) {
|
|
55
|
+
if (node.exportKind === "type") return false;
|
|
56
|
+
if (node.specifiers.length > 0) {
|
|
57
|
+
node.specifiers = node.specifiers.filter((specifier) => {
|
|
58
|
+
if (_babel_types.isExportSpecifier(specifier)) return specifier.exportKind !== "type";
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
if (node.specifiers.length === 0 && !node.declaration) return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (_babel_types.isExportAllDeclaration(node)) {
|
|
65
|
+
if (node.exportKind === "type") return false;
|
|
66
|
+
}
|
|
67
|
+
if (_babel_types.isImportDeclaration(node)) {
|
|
68
|
+
if (node.importKind === "type") return false;
|
|
69
|
+
if (node.specifiers.length > 0) {
|
|
70
|
+
node.specifiers = node.specifiers.filter((specifier) => {
|
|
71
|
+
if (_babel_types.isImportSpecifier(specifier)) return specifier.importKind !== "type";
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
if (node.specifiers.length === 0) return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Performs dead code elimination on the AST, with TypeScript type stripping.
|
|
82
|
+
*
|
|
83
|
+
* This is a wrapper around babel-dead-code-elimination that first strips
|
|
84
|
+
* TypeScript type-only exports and imports. This is necessary because
|
|
85
|
+
* babel-dead-code-elimination doesn't handle type exports, which can cause
|
|
86
|
+
* imports to be retained when they're only referenced by type exports.
|
|
87
|
+
*
|
|
88
|
+
* @param ast - The Babel AST to mutate
|
|
89
|
+
* @param candidates - Optional set of identifier paths to consider for removal.
|
|
90
|
+
* If provided, only these identifiers will be candidates for removal.
|
|
91
|
+
* This should be the result of `findReferencedIdentifiers(ast)` called
|
|
92
|
+
* before any AST transformations.
|
|
93
|
+
*/
|
|
94
|
+
function deadCodeElimination(ast, candidates) {
|
|
95
|
+
stripTypeExports(ast);
|
|
96
|
+
(0, babel_dead_code_elimination.deadCodeElimination)(ast, candidates);
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
exports.deadCodeElimination = deadCodeElimination;
|
|
100
|
+
exports.generateFromAst = generateFromAst;
|
|
101
|
+
exports.parseAst = parseAst;
|
|
102
|
+
exports.stripTypeExports = stripTypeExports;
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=ast.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.cjs","names":[],"sources":["../../src/ast.ts"],"sourcesContent":["import { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nimport * as t from '@babel/types'\nimport {\n deadCodeElimination as _deadCodeElimination,\n findReferencedIdentifiers,\n} from 'babel-dead-code-elimination'\nimport type { GeneratorOptions, GeneratorResult } from '@babel/generator'\nimport type { ParseResult, ParserOptions } from '@babel/parser'\nimport type * as _babel_types from '@babel/types'\n\nexport type ParseAstOptions = ParserOptions & {\n code: string\n}\n\nexport type ParseAstResult = ParseResult<_babel_types.File>\nexport function parseAst({ code, ...opts }: ParseAstOptions): ParseAstResult {\n return parse(code, {\n plugins: [\n 'jsx',\n 'typescript',\n 'explicitResourceManagement',\n 'decorators', // required for Angular and other decorator-based code\n ],\n sourceType: 'module',\n ...opts,\n })\n}\n\nlet generate = _generate\n\nif ('default' in generate) {\n generate = generate.default as typeof generate\n}\ntype GenerateFromAstOptions = GeneratorOptions &\n Required<Pick<GeneratorOptions, 'sourceFileName' | 'filename'>>\nexport function generateFromAst(\n ast: _babel_types.Node,\n opts?: GenerateFromAstOptions,\n): GeneratorResult {\n return generate(\n ast,\n opts\n ? { importAttributesKeyword: 'with', sourceMaps: true, ...opts }\n : undefined,\n )\n}\nexport type { GeneratorResult } from '@babel/generator'\n\n/**\n * Strips TypeScript type-only exports and imports from an AST.\n *\n * This is necessary because babel-dead-code-elimination doesn't handle\n * TypeScript type exports/imports. When a type export references an import\n * that pulls in server-only code, the dead code elimination won't remove\n * that import because it sees the type as still referencing it.\n *\n * This function removes:\n * - `export type Foo = ...`\n * - `export interface Foo { ... }`\n * - `export type { Foo } from './module'`\n * - `export type * from './module'`\n * - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`\n * - `import type { Foo } from './module'`\n * - Type specifiers in mixed imports: `import { value, type Foo } from './module'` -> `import { value }`\n *\n * Note: Non-exported type/interface declarations are preserved as they may be\n * used as type annotations within the code.\n *\n * @param ast - The Babel AST (or ParseResult) to mutate\n */\nexport function stripTypeExports(ast: ParseResult<_babel_types.File>): void {\n // Filter the program body to remove type-only nodes\n ast.program.body = ast.program.body.filter((node) => {\n // Handle export declarations\n if (t.isExportNamedDeclaration(node)) {\n // Remove entire export if it's a type-only export\n // e.g., `export type Foo = string`, `export interface Bar {}`, `export type { X } from './y'`\n if (node.exportKind === 'type') {\n return false\n }\n\n // For value exports with mixed specifiers, filter out type-only specifiers\n // e.g., `export { value, type TypeOnly }` -> `export { value }`\n if (node.specifiers.length > 0) {\n node.specifiers = node.specifiers.filter((specifier) => {\n if (t.isExportSpecifier(specifier)) {\n return specifier.exportKind !== 'type'\n }\n return true\n })\n\n // If all specifiers were removed, remove the entire export declaration\n // (unless it has a declaration like `export const x = 1`)\n if (node.specifiers.length === 0 && !node.declaration) {\n return false\n }\n }\n }\n\n // Handle type-only export-all declarations\n // e.g., `export type * from './module'`\n if (t.isExportAllDeclaration(node)) {\n if (node.exportKind === 'type') {\n return false\n }\n }\n\n // Handle import declarations\n if (t.isImportDeclaration(node)) {\n // Remove entire import if it's a type-only import\n // e.g., `import type { Foo } from './module'`\n if (node.importKind === 'type') {\n return false\n }\n\n // For value imports with mixed specifiers, filter out type-only specifiers\n // e.g., `import { value, type TypeOnly } from './module'` -> `import { value }`\n if (node.specifiers.length > 0) {\n node.specifiers = node.specifiers.filter((specifier) => {\n if (t.isImportSpecifier(specifier)) {\n return specifier.importKind !== 'type'\n }\n return true\n })\n\n // If all specifiers were removed, remove the entire import declaration\n if (node.specifiers.length === 0) {\n return false\n }\n }\n }\n\n return true\n })\n}\n\n// Re-export findReferencedIdentifiers from babel-dead-code-elimination\nexport { findReferencedIdentifiers }\n\n/**\n * Performs dead code elimination on the AST, with TypeScript type stripping.\n *\n * This is a wrapper around babel-dead-code-elimination that first strips\n * TypeScript type-only exports and imports. This is necessary because\n * babel-dead-code-elimination doesn't handle type exports, which can cause\n * imports to be retained when they're only referenced by type exports.\n *\n * @param ast - The Babel AST to mutate\n * @param candidates - Optional set of identifier paths to consider for removal.\n * If provided, only these identifiers will be candidates for removal.\n * This should be the result of `findReferencedIdentifiers(ast)` called\n * before any AST transformations.\n */\nexport function deadCodeElimination(\n ast: ParseResult<_babel_types.File>,\n candidates?: ReturnType<typeof findReferencedIdentifiers>,\n): void {\n // First strip TypeScript type-only exports and imports\n stripTypeExports(ast)\n\n // Then run the original dead code elimination\n _deadCodeElimination(ast, candidates)\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,SAAS,EAAE,MAAM,GAAG,QAAyC;AAC3E,SAAA,GAAA,cAAA,OAAa,MAAM;EACjB,SAAS;GACP;GACA;GACA;GACA;GACD;EACD,YAAY;EACZ,GAAG;EACJ,CAAC;;AAGJ,IAAI,WAAW,iBAAA;AAEf,IAAI,aAAa,SACf,YAAW,SAAS;AAItB,SAAgB,gBACd,KACA,MACiB;AACjB,QAAO,SACL,KACA,OACI;EAAE,yBAAyB;EAAQ,YAAY;EAAM,GAAG;EAAM,GAC9D,KAAA,EACL;;;;;;;;;;;;;;;;;;;;;;;;AA0BH,SAAgB,iBAAiB,KAA2C;AAE1E,KAAI,QAAQ,OAAO,IAAI,QAAQ,KAAK,QAAQ,SAAS;AAEnD,MAAI,aAAE,yBAAyB,KAAK,EAAE;AAGpC,OAAI,KAAK,eAAe,OACtB,QAAO;AAKT,OAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,SAAK,aAAa,KAAK,WAAW,QAAQ,cAAc;AACtD,SAAI,aAAE,kBAAkB,UAAU,CAChC,QAAO,UAAU,eAAe;AAElC,YAAO;MACP;AAIF,QAAI,KAAK,WAAW,WAAW,KAAK,CAAC,KAAK,YACxC,QAAO;;;AAOb,MAAI,aAAE,uBAAuB,KAAK;OAC5B,KAAK,eAAe,OACtB,QAAO;;AAKX,MAAI,aAAE,oBAAoB,KAAK,EAAE;AAG/B,OAAI,KAAK,eAAe,OACtB,QAAO;AAKT,OAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,SAAK,aAAa,KAAK,WAAW,QAAQ,cAAc;AACtD,SAAI,aAAE,kBAAkB,UAAU,CAChC,QAAO,UAAU,eAAe;AAElC,YAAO;MACP;AAGF,QAAI,KAAK,WAAW,WAAW,EAC7B,QAAO;;;AAKb,SAAO;GACP;;;;;;;;;;;;;;;;AAoBJ,SAAgB,oBACd,KACA,YACM;AAEN,kBAAiB,IAAI;AAGrB,EAAA,GAAA,4BAAA,qBAAqB,KAAK,WAAW"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { findReferencedIdentifiers } from 'babel-dead-code-elimination';
|
|
2
|
+
import { GeneratorOptions, GeneratorResult } from '@babel/generator';
|
|
3
|
+
import { ParseResult, ParserOptions } from '@babel/parser';
|
|
4
|
+
import type * as _babel_types from '@babel/types';
|
|
5
|
+
export type ParseAstOptions = ParserOptions & {
|
|
6
|
+
code: string;
|
|
7
|
+
};
|
|
8
|
+
export type ParseAstResult = ParseResult<_babel_types.File>;
|
|
9
|
+
export declare function parseAst({ code, ...opts }: ParseAstOptions): ParseAstResult;
|
|
10
|
+
type GenerateFromAstOptions = GeneratorOptions & Required<Pick<GeneratorOptions, 'sourceFileName' | 'filename'>>;
|
|
11
|
+
export declare function generateFromAst(ast: _babel_types.Node, opts?: GenerateFromAstOptions): GeneratorResult;
|
|
12
|
+
export type { GeneratorResult } from '@babel/generator';
|
|
13
|
+
/**
|
|
14
|
+
* Strips TypeScript type-only exports and imports from an AST.
|
|
15
|
+
*
|
|
16
|
+
* This is necessary because babel-dead-code-elimination doesn't handle
|
|
17
|
+
* TypeScript type exports/imports. When a type export references an import
|
|
18
|
+
* that pulls in server-only code, the dead code elimination won't remove
|
|
19
|
+
* that import because it sees the type as still referencing it.
|
|
20
|
+
*
|
|
21
|
+
* This function removes:
|
|
22
|
+
* - `export type Foo = ...`
|
|
23
|
+
* - `export interface Foo { ... }`
|
|
24
|
+
* - `export type { Foo } from './module.cjs'`
|
|
25
|
+
* - `export type * from './module.cjs'`
|
|
26
|
+
* - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`
|
|
27
|
+
* - `import type { Foo } from './module.cjs'`
|
|
28
|
+
* - Type specifiers in mixed imports: `import { value, type Foo } from './module.cjs'` -> `import { value }`
|
|
29
|
+
*
|
|
30
|
+
* Note: Non-exported type/interface declarations are preserved as they may be
|
|
31
|
+
* used as type annotations within the code.
|
|
32
|
+
*
|
|
33
|
+
* @param ast - The Babel AST (or ParseResult) to mutate
|
|
34
|
+
*/
|
|
35
|
+
export declare function stripTypeExports(ast: ParseResult<_babel_types.File>): void;
|
|
36
|
+
export { findReferencedIdentifiers };
|
|
37
|
+
/**
|
|
38
|
+
* Performs dead code elimination on the AST, with TypeScript type stripping.
|
|
39
|
+
*
|
|
40
|
+
* This is a wrapper around babel-dead-code-elimination that first strips
|
|
41
|
+
* TypeScript type-only exports and imports. This is necessary because
|
|
42
|
+
* babel-dead-code-elimination doesn't handle type exports, which can cause
|
|
43
|
+
* imports to be retained when they're only referenced by type exports.
|
|
44
|
+
*
|
|
45
|
+
* @param ast - The Babel AST to mutate
|
|
46
|
+
* @param candidates - Optional set of identifier paths to consider for removal.
|
|
47
|
+
* If provided, only these identifiers will be candidates for removal.
|
|
48
|
+
* This should be the result of `findReferencedIdentifiers(ast)` called
|
|
49
|
+
* before any AST transformations.
|
|
50
|
+
*/
|
|
51
|
+
export declare function deadCodeElimination(ast: ParseResult<_babel_types.File>, candidates?: ReturnType<typeof findReferencedIdentifiers>): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let node_fs_promises = require("node:fs/promises");
|
|
3
|
+
let pathe = require("pathe");
|
|
4
|
+
let tinyglobby = require("tinyglobby");
|
|
5
|
+
//#region src/copy-files-plugin.ts
|
|
6
|
+
function copyFilesPlugin({ fromDir, toDir, pattern = "**" }) {
|
|
7
|
+
return {
|
|
8
|
+
name: "copy-files",
|
|
9
|
+
async writeBundle() {
|
|
10
|
+
const entries = await (0, tinyglobby.glob)(pattern, { cwd: fromDir });
|
|
11
|
+
if (entries.length === 0) throw new Error(`No files found matching pattern "${pattern}" in directory "${fromDir}"`);
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const srcPath = (0, pathe.join)(fromDir, entry);
|
|
14
|
+
const destPath = (0, pathe.join)(toDir, entry);
|
|
15
|
+
await (0, node_fs_promises.mkdir)((0, pathe.dirname)(destPath), { recursive: true });
|
|
16
|
+
await (0, node_fs_promises.copyFile)(srcPath, destPath);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
exports.copyFilesPlugin = copyFilesPlugin;
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=copy-files-plugin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-files-plugin.cjs","names":[],"sources":["../../src/copy-files-plugin.ts"],"sourcesContent":["import { copyFile, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'pathe'\nimport { glob } from 'tinyglobby'\nimport type { Plugin } from 'vite'\n\nexport function copyFilesPlugin({\n fromDir,\n toDir,\n pattern = '**',\n}: {\n pattern?: string | Array<string>\n fromDir: string\n toDir: string\n}): Plugin {\n return {\n name: 'copy-files',\n async writeBundle() {\n const entries = await glob(pattern, { cwd: fromDir })\n if (entries.length === 0) {\n throw new Error(\n `No files found matching pattern \"${pattern}\" in directory \"${fromDir}\"`,\n )\n }\n\n for (const entry of entries) {\n const srcPath = join(fromDir, entry)\n const destPath = join(toDir, entry)\n // Ensure the destination directory exists\n await mkdir(dirname(destPath), { recursive: true })\n await copyFile(srcPath, destPath)\n }\n },\n }\n}\n"],"mappings":";;;;;AAKA,SAAgB,gBAAgB,EAC9B,SACA,OACA,UAAU,QAKD;AACT,QAAO;EACL,MAAM;EACN,MAAM,cAAc;GAClB,MAAM,UAAU,OAAA,GAAA,WAAA,MAAW,SAAS,EAAE,KAAK,SAAS,CAAC;AACrD,OAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MACR,oCAAoC,QAAQ,kBAAkB,QAAQ,GACvE;AAGH,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,WAAA,GAAA,MAAA,MAAe,SAAS,MAAM;IACpC,MAAM,YAAA,GAAA,MAAA,MAAgB,OAAO,MAAM;AAEnC,WAAA,GAAA,iBAAA,QAAA,GAAA,MAAA,SAAoB,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,WAAA,GAAA,iBAAA,UAAe,SAAS,SAAS;;;EAGtC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("./_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_ast = require("./ast.cjs");
|
|
4
|
+
const require_logger = require("./logger.cjs");
|
|
5
|
+
const require_copy_files_plugin = require("./copy-files-plugin.cjs");
|
|
6
|
+
let babel_dead_code_elimination = require("babel-dead-code-elimination");
|
|
7
|
+
exports.copyFilesPlugin = require_copy_files_plugin.copyFilesPlugin;
|
|
8
|
+
exports.deadCodeElimination = require_ast.deadCodeElimination;
|
|
9
|
+
exports.findReferencedIdentifiers = babel_dead_code_elimination.findReferencedIdentifiers;
|
|
10
|
+
exports.generateFromAst = require_ast.generateFromAst;
|
|
11
|
+
exports.logDiff = require_logger.logDiff;
|
|
12
|
+
exports.parseAst = require_ast.parseAst;
|
|
13
|
+
exports.stripTypeExports = require_ast.stripTypeExports;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { parseAst, generateFromAst, deadCodeElimination, findReferencedIdentifiers, stripTypeExports, } from './ast.cjs';
|
|
2
|
+
export type { ParseAstOptions, ParseAstResult, GeneratorResult } from './ast.cjs';
|
|
3
|
+
export { logDiff } from './logger.cjs';
|
|
4
|
+
export { copyFilesPlugin } from './copy-files-plugin.cjs';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let ansis = require("ansis");
|
|
3
|
+
ansis = require_runtime.__toESM(ansis);
|
|
4
|
+
let diff = require("diff");
|
|
5
|
+
//#region src/logger.ts
|
|
6
|
+
function logDiff(oldStr, newStr) {
|
|
7
|
+
const differences = (0, diff.diffWords)(oldStr, newStr);
|
|
8
|
+
let output = "";
|
|
9
|
+
let unchangedLines = "";
|
|
10
|
+
function processUnchangedLines(lines) {
|
|
11
|
+
const lineArray = lines.split("\n");
|
|
12
|
+
if (lineArray.length > 4) return [
|
|
13
|
+
ansis.default.dim(lineArray[0]),
|
|
14
|
+
ansis.default.dim(lineArray[1]),
|
|
15
|
+
"",
|
|
16
|
+
ansis.default.dim.bold(`... (${lineArray.length - 4} lines) ...`),
|
|
17
|
+
"",
|
|
18
|
+
ansis.default.dim(lineArray[lineArray.length - 2]),
|
|
19
|
+
ansis.default.dim(lineArray[lineArray.length - 1])
|
|
20
|
+
].join("\n");
|
|
21
|
+
return ansis.default.dim(lines);
|
|
22
|
+
}
|
|
23
|
+
differences.forEach((part, index) => {
|
|
24
|
+
const nextPart = differences[index + 1];
|
|
25
|
+
if (part.added) {
|
|
26
|
+
if (unchangedLines) {
|
|
27
|
+
output += processUnchangedLines(unchangedLines);
|
|
28
|
+
unchangedLines = "";
|
|
29
|
+
}
|
|
30
|
+
output += ansis.default.green.bold(part.value);
|
|
31
|
+
if (nextPart?.removed) output += " ";
|
|
32
|
+
} else if (part.removed) {
|
|
33
|
+
if (unchangedLines) {
|
|
34
|
+
output += processUnchangedLines(unchangedLines);
|
|
35
|
+
unchangedLines = "";
|
|
36
|
+
}
|
|
37
|
+
output += ansis.default.red.bold(part.value);
|
|
38
|
+
if (nextPart?.added) output += " ";
|
|
39
|
+
} else unchangedLines += part.value;
|
|
40
|
+
});
|
|
41
|
+
if (unchangedLines) output += processUnchangedLines(unchangedLines);
|
|
42
|
+
if (output) {
|
|
43
|
+
console.log("\nDiff:");
|
|
44
|
+
console.log(output + "\n\n");
|
|
45
|
+
} else console.log("No changes");
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
exports.logDiff = logDiff;
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=logger.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.cjs","names":[],"sources":["../../src/logger.ts"],"sourcesContent":["import ansis from 'ansis'\nimport { diffWords } from 'diff'\n\nexport function logDiff(oldStr: string, newStr: string) {\n const differences = diffWords(oldStr, newStr)\n\n let output = ''\n let unchangedLines = ''\n\n function processUnchangedLines(lines: string): string {\n const lineArray = lines.split('\\n')\n if (lineArray.length > 4) {\n return [\n ansis.dim(lineArray[0]),\n ansis.dim(lineArray[1]),\n '',\n ansis.dim.bold(`... (${lineArray.length - 4} lines) ...`),\n '',\n ansis.dim(lineArray[lineArray.length - 2]),\n ansis.dim(lineArray[lineArray.length - 1]),\n ].join('\\n')\n }\n return ansis.dim(lines)\n }\n\n differences.forEach((part, index) => {\n const nextPart = differences[index + 1]\n\n if (part.added) {\n if (unchangedLines) {\n output += processUnchangedLines(unchangedLines)\n unchangedLines = ''\n }\n output += ansis.green.bold(part.value)\n if (nextPart?.removed) output += ' '\n } else if (part.removed) {\n if (unchangedLines) {\n output += processUnchangedLines(unchangedLines)\n unchangedLines = ''\n }\n output += ansis.red.bold(part.value)\n if (nextPart?.added) output += ' '\n } else {\n unchangedLines += part.value\n }\n })\n\n // Process any remaining unchanged lines at the end\n if (unchangedLines) {\n output += processUnchangedLines(unchangedLines)\n }\n\n if (output) {\n console.log('\\nDiff:')\n console.log(output + '\\n\\n')\n } else {\n console.log('No changes')\n }\n}\n"],"mappings":";;;;;AAGA,SAAgB,QAAQ,QAAgB,QAAgB;CACtD,MAAM,eAAA,GAAA,KAAA,WAAwB,QAAQ,OAAO;CAE7C,IAAI,SAAS;CACb,IAAI,iBAAiB;CAErB,SAAS,sBAAsB,OAAuB;EACpD,MAAM,YAAY,MAAM,MAAM,KAAK;AACnC,MAAI,UAAU,SAAS,EACrB,QAAO;GACL,MAAA,QAAM,IAAI,UAAU,GAAG;GACvB,MAAA,QAAM,IAAI,UAAU,GAAG;GACvB;GACA,MAAA,QAAM,IAAI,KAAK,QAAQ,UAAU,SAAS,EAAE,aAAa;GACzD;GACA,MAAA,QAAM,IAAI,UAAU,UAAU,SAAS,GAAG;GAC1C,MAAA,QAAM,IAAI,UAAU,UAAU,SAAS,GAAG;GAC3C,CAAC,KAAK,KAAK;AAEd,SAAO,MAAA,QAAM,IAAI,MAAM;;AAGzB,aAAY,SAAS,MAAM,UAAU;EACnC,MAAM,WAAW,YAAY,QAAQ;AAErC,MAAI,KAAK,OAAO;AACd,OAAI,gBAAgB;AAClB,cAAU,sBAAsB,eAAe;AAC/C,qBAAiB;;AAEnB,aAAU,MAAA,QAAM,MAAM,KAAK,KAAK,MAAM;AACtC,OAAI,UAAU,QAAS,WAAU;aACxB,KAAK,SAAS;AACvB,OAAI,gBAAgB;AAClB,cAAU,sBAAsB,eAAe;AAC/C,qBAAiB;;AAEnB,aAAU,MAAA,QAAM,IAAI,KAAK,KAAK,MAAM;AACpC,OAAI,UAAU,MAAO,WAAU;QAE/B,mBAAkB,KAAK;GAEzB;AAGF,KAAI,eACF,WAAU,sBAAsB,eAAe;AAGjD,KAAI,QAAQ;AACV,UAAQ,IAAI,UAAU;AACtB,UAAQ,IAAI,SAAS,OAAO;OAE5B,SAAQ,IAAI,aAAa"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function logDiff(oldStr: string, newStr: string): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { findReferencedIdentifiers } from 'babel-dead-code-elimination';
|
|
2
|
+
import { GeneratorOptions, GeneratorResult } from '@babel/generator';
|
|
3
|
+
import { ParseResult, ParserOptions } from '@babel/parser';
|
|
4
|
+
import type * as _babel_types from '@babel/types';
|
|
5
|
+
export type ParseAstOptions = ParserOptions & {
|
|
6
|
+
code: string;
|
|
7
|
+
};
|
|
8
|
+
export type ParseAstResult = ParseResult<_babel_types.File>;
|
|
9
|
+
export declare function parseAst({ code, ...opts }: ParseAstOptions): ParseAstResult;
|
|
10
|
+
type GenerateFromAstOptions = GeneratorOptions & Required<Pick<GeneratorOptions, 'sourceFileName' | 'filename'>>;
|
|
11
|
+
export declare function generateFromAst(ast: _babel_types.Node, opts?: GenerateFromAstOptions): GeneratorResult;
|
|
12
|
+
export type { GeneratorResult } from '@babel/generator';
|
|
13
|
+
/**
|
|
14
|
+
* Strips TypeScript type-only exports and imports from an AST.
|
|
15
|
+
*
|
|
16
|
+
* This is necessary because babel-dead-code-elimination doesn't handle
|
|
17
|
+
* TypeScript type exports/imports. When a type export references an import
|
|
18
|
+
* that pulls in server-only code, the dead code elimination won't remove
|
|
19
|
+
* that import because it sees the type as still referencing it.
|
|
20
|
+
*
|
|
21
|
+
* This function removes:
|
|
22
|
+
* - `export type Foo = ...`
|
|
23
|
+
* - `export interface Foo { ... }`
|
|
24
|
+
* - `export type { Foo } from './module.js'`
|
|
25
|
+
* - `export type * from './module.js'`
|
|
26
|
+
* - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`
|
|
27
|
+
* - `import type { Foo } from './module.js'`
|
|
28
|
+
* - Type specifiers in mixed imports: `import { value, type Foo } from './module.js'` -> `import { value }`
|
|
29
|
+
*
|
|
30
|
+
* Note: Non-exported type/interface declarations are preserved as they may be
|
|
31
|
+
* used as type annotations within the code.
|
|
32
|
+
*
|
|
33
|
+
* @param ast - The Babel AST (or ParseResult) to mutate
|
|
34
|
+
*/
|
|
35
|
+
export declare function stripTypeExports(ast: ParseResult<_babel_types.File>): void;
|
|
36
|
+
export { findReferencedIdentifiers };
|
|
37
|
+
/**
|
|
38
|
+
* Performs dead code elimination on the AST, with TypeScript type stripping.
|
|
39
|
+
*
|
|
40
|
+
* This is a wrapper around babel-dead-code-elimination that first strips
|
|
41
|
+
* TypeScript type-only exports and imports. This is necessary because
|
|
42
|
+
* babel-dead-code-elimination doesn't handle type exports, which can cause
|
|
43
|
+
* imports to be retained when they're only referenced by type exports.
|
|
44
|
+
*
|
|
45
|
+
* @param ast - The Babel AST to mutate
|
|
46
|
+
* @param candidates - Optional set of identifier paths to consider for removal.
|
|
47
|
+
* If provided, only these identifiers will be candidates for removal.
|
|
48
|
+
* This should be the result of `findReferencedIdentifiers(ast)` called
|
|
49
|
+
* before any AST transformations.
|
|
50
|
+
*/
|
|
51
|
+
export declare function deadCodeElimination(ast: ParseResult<_babel_types.File>, candidates?: ReturnType<typeof findReferencedIdentifiers>): void;
|
package/dist/esm/ast.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import _generate from "@babel/generator";
|
|
3
|
+
import * as t from "@babel/types";
|
|
4
|
+
import { deadCodeElimination, findReferencedIdentifiers } from "babel-dead-code-elimination";
|
|
5
|
+
//#region src/ast.ts
|
|
6
|
+
function parseAst({ code, ...opts }) {
|
|
7
|
+
return parse(code, {
|
|
8
|
+
plugins: [
|
|
9
|
+
"jsx",
|
|
10
|
+
"typescript",
|
|
11
|
+
"explicitResourceManagement",
|
|
12
|
+
"decorators"
|
|
13
|
+
],
|
|
14
|
+
sourceType: "module",
|
|
15
|
+
...opts
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
var generate = _generate;
|
|
19
|
+
if ("default" in generate) generate = generate.default;
|
|
20
|
+
function generateFromAst(ast, opts) {
|
|
21
|
+
return generate(ast, opts ? {
|
|
22
|
+
importAttributesKeyword: "with",
|
|
23
|
+
sourceMaps: true,
|
|
24
|
+
...opts
|
|
25
|
+
} : void 0);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Strips TypeScript type-only exports and imports from an AST.
|
|
29
|
+
*
|
|
30
|
+
* This is necessary because babel-dead-code-elimination doesn't handle
|
|
31
|
+
* TypeScript type exports/imports. When a type export references an import
|
|
32
|
+
* that pulls in server-only code, the dead code elimination won't remove
|
|
33
|
+
* that import because it sees the type as still referencing it.
|
|
34
|
+
*
|
|
35
|
+
* This function removes:
|
|
36
|
+
* - `export type Foo = ...`
|
|
37
|
+
* - `export interface Foo { ... }`
|
|
38
|
+
* - `export type { Foo } from './module'`
|
|
39
|
+
* - `export type * from './module'`
|
|
40
|
+
* - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`
|
|
41
|
+
* - `import type { Foo } from './module'`
|
|
42
|
+
* - Type specifiers in mixed imports: `import { value, type Foo } from './module'` -> `import { value }`
|
|
43
|
+
*
|
|
44
|
+
* Note: Non-exported type/interface declarations are preserved as they may be
|
|
45
|
+
* used as type annotations within the code.
|
|
46
|
+
*
|
|
47
|
+
* @param ast - The Babel AST (or ParseResult) to mutate
|
|
48
|
+
*/
|
|
49
|
+
function stripTypeExports(ast) {
|
|
50
|
+
ast.program.body = ast.program.body.filter((node) => {
|
|
51
|
+
if (t.isExportNamedDeclaration(node)) {
|
|
52
|
+
if (node.exportKind === "type") return false;
|
|
53
|
+
if (node.specifiers.length > 0) {
|
|
54
|
+
node.specifiers = node.specifiers.filter((specifier) => {
|
|
55
|
+
if (t.isExportSpecifier(specifier)) return specifier.exportKind !== "type";
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
if (node.specifiers.length === 0 && !node.declaration) return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (t.isExportAllDeclaration(node)) {
|
|
62
|
+
if (node.exportKind === "type") return false;
|
|
63
|
+
}
|
|
64
|
+
if (t.isImportDeclaration(node)) {
|
|
65
|
+
if (node.importKind === "type") return false;
|
|
66
|
+
if (node.specifiers.length > 0) {
|
|
67
|
+
node.specifiers = node.specifiers.filter((specifier) => {
|
|
68
|
+
if (t.isImportSpecifier(specifier)) return specifier.importKind !== "type";
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
if (node.specifiers.length === 0) return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Performs dead code elimination on the AST, with TypeScript type stripping.
|
|
79
|
+
*
|
|
80
|
+
* This is a wrapper around babel-dead-code-elimination that first strips
|
|
81
|
+
* TypeScript type-only exports and imports. This is necessary because
|
|
82
|
+
* babel-dead-code-elimination doesn't handle type exports, which can cause
|
|
83
|
+
* imports to be retained when they're only referenced by type exports.
|
|
84
|
+
*
|
|
85
|
+
* @param ast - The Babel AST to mutate
|
|
86
|
+
* @param candidates - Optional set of identifier paths to consider for removal.
|
|
87
|
+
* If provided, only these identifiers will be candidates for removal.
|
|
88
|
+
* This should be the result of `findReferencedIdentifiers(ast)` called
|
|
89
|
+
* before any AST transformations.
|
|
90
|
+
*/
|
|
91
|
+
function deadCodeElimination$1(ast, candidates) {
|
|
92
|
+
stripTypeExports(ast);
|
|
93
|
+
deadCodeElimination(ast, candidates);
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { deadCodeElimination$1 as deadCodeElimination, findReferencedIdentifiers, generateFromAst, parseAst, stripTypeExports };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","names":[],"sources":["../../src/ast.ts"],"sourcesContent":["import { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nimport * as t from '@babel/types'\nimport {\n deadCodeElimination as _deadCodeElimination,\n findReferencedIdentifiers,\n} from 'babel-dead-code-elimination'\nimport type { GeneratorOptions, GeneratorResult } from '@babel/generator'\nimport type { ParseResult, ParserOptions } from '@babel/parser'\nimport type * as _babel_types from '@babel/types'\n\nexport type ParseAstOptions = ParserOptions & {\n code: string\n}\n\nexport type ParseAstResult = ParseResult<_babel_types.File>\nexport function parseAst({ code, ...opts }: ParseAstOptions): ParseAstResult {\n return parse(code, {\n plugins: [\n 'jsx',\n 'typescript',\n 'explicitResourceManagement',\n 'decorators', // required for Angular and other decorator-based code\n ],\n sourceType: 'module',\n ...opts,\n })\n}\n\nlet generate = _generate\n\nif ('default' in generate) {\n generate = generate.default as typeof generate\n}\ntype GenerateFromAstOptions = GeneratorOptions &\n Required<Pick<GeneratorOptions, 'sourceFileName' | 'filename'>>\nexport function generateFromAst(\n ast: _babel_types.Node,\n opts?: GenerateFromAstOptions,\n): GeneratorResult {\n return generate(\n ast,\n opts\n ? { importAttributesKeyword: 'with', sourceMaps: true, ...opts }\n : undefined,\n )\n}\nexport type { GeneratorResult } from '@babel/generator'\n\n/**\n * Strips TypeScript type-only exports and imports from an AST.\n *\n * This is necessary because babel-dead-code-elimination doesn't handle\n * TypeScript type exports/imports. When a type export references an import\n * that pulls in server-only code, the dead code elimination won't remove\n * that import because it sees the type as still referencing it.\n *\n * This function removes:\n * - `export type Foo = ...`\n * - `export interface Foo { ... }`\n * - `export type { Foo } from './module'`\n * - `export type * from './module'`\n * - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`\n * - `import type { Foo } from './module'`\n * - Type specifiers in mixed imports: `import { value, type Foo } from './module'` -> `import { value }`\n *\n * Note: Non-exported type/interface declarations are preserved as they may be\n * used as type annotations within the code.\n *\n * @param ast - The Babel AST (or ParseResult) to mutate\n */\nexport function stripTypeExports(ast: ParseResult<_babel_types.File>): void {\n // Filter the program body to remove type-only nodes\n ast.program.body = ast.program.body.filter((node) => {\n // Handle export declarations\n if (t.isExportNamedDeclaration(node)) {\n // Remove entire export if it's a type-only export\n // e.g., `export type Foo = string`, `export interface Bar {}`, `export type { X } from './y'`\n if (node.exportKind === 'type') {\n return false\n }\n\n // For value exports with mixed specifiers, filter out type-only specifiers\n // e.g., `export { value, type TypeOnly }` -> `export { value }`\n if (node.specifiers.length > 0) {\n node.specifiers = node.specifiers.filter((specifier) => {\n if (t.isExportSpecifier(specifier)) {\n return specifier.exportKind !== 'type'\n }\n return true\n })\n\n // If all specifiers were removed, remove the entire export declaration\n // (unless it has a declaration like `export const x = 1`)\n if (node.specifiers.length === 0 && !node.declaration) {\n return false\n }\n }\n }\n\n // Handle type-only export-all declarations\n // e.g., `export type * from './module'`\n if (t.isExportAllDeclaration(node)) {\n if (node.exportKind === 'type') {\n return false\n }\n }\n\n // Handle import declarations\n if (t.isImportDeclaration(node)) {\n // Remove entire import if it's a type-only import\n // e.g., `import type { Foo } from './module'`\n if (node.importKind === 'type') {\n return false\n }\n\n // For value imports with mixed specifiers, filter out type-only specifiers\n // e.g., `import { value, type TypeOnly } from './module'` -> `import { value }`\n if (node.specifiers.length > 0) {\n node.specifiers = node.specifiers.filter((specifier) => {\n if (t.isImportSpecifier(specifier)) {\n return specifier.importKind !== 'type'\n }\n return true\n })\n\n // If all specifiers were removed, remove the entire import declaration\n if (node.specifiers.length === 0) {\n return false\n }\n }\n }\n\n return true\n })\n}\n\n// Re-export findReferencedIdentifiers from babel-dead-code-elimination\nexport { findReferencedIdentifiers }\n\n/**\n * Performs dead code elimination on the AST, with TypeScript type stripping.\n *\n * This is a wrapper around babel-dead-code-elimination that first strips\n * TypeScript type-only exports and imports. This is necessary because\n * babel-dead-code-elimination doesn't handle type exports, which can cause\n * imports to be retained when they're only referenced by type exports.\n *\n * @param ast - The Babel AST to mutate\n * @param candidates - Optional set of identifier paths to consider for removal.\n * If provided, only these identifiers will be candidates for removal.\n * This should be the result of `findReferencedIdentifiers(ast)` called\n * before any AST transformations.\n */\nexport function deadCodeElimination(\n ast: ParseResult<_babel_types.File>,\n candidates?: ReturnType<typeof findReferencedIdentifiers>,\n): void {\n // First strip TypeScript type-only exports and imports\n stripTypeExports(ast)\n\n // Then run the original dead code elimination\n _deadCodeElimination(ast, candidates)\n}\n"],"mappings":";;;;;AAgBA,SAAgB,SAAS,EAAE,MAAM,GAAG,QAAyC;AAC3E,QAAO,MAAM,MAAM;EACjB,SAAS;GACP;GACA;GACA;GACA;GACD;EACD,YAAY;EACZ,GAAG;EACJ,CAAC;;AAGJ,IAAI,WAAW;AAEf,IAAI,aAAa,SACf,YAAW,SAAS;AAItB,SAAgB,gBACd,KACA,MACiB;AACjB,QAAO,SACL,KACA,OACI;EAAE,yBAAyB;EAAQ,YAAY;EAAM,GAAG;EAAM,GAC9D,KAAA,EACL;;;;;;;;;;;;;;;;;;;;;;;;AA0BH,SAAgB,iBAAiB,KAA2C;AAE1E,KAAI,QAAQ,OAAO,IAAI,QAAQ,KAAK,QAAQ,SAAS;AAEnD,MAAI,EAAE,yBAAyB,KAAK,EAAE;AAGpC,OAAI,KAAK,eAAe,OACtB,QAAO;AAKT,OAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,SAAK,aAAa,KAAK,WAAW,QAAQ,cAAc;AACtD,SAAI,EAAE,kBAAkB,UAAU,CAChC,QAAO,UAAU,eAAe;AAElC,YAAO;MACP;AAIF,QAAI,KAAK,WAAW,WAAW,KAAK,CAAC,KAAK,YACxC,QAAO;;;AAOb,MAAI,EAAE,uBAAuB,KAAK;OAC5B,KAAK,eAAe,OACtB,QAAO;;AAKX,MAAI,EAAE,oBAAoB,KAAK,EAAE;AAG/B,OAAI,KAAK,eAAe,OACtB,QAAO;AAKT,OAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,SAAK,aAAa,KAAK,WAAW,QAAQ,cAAc;AACtD,SAAI,EAAE,kBAAkB,UAAU,CAChC,QAAO,UAAU,eAAe;AAElC,YAAO;MACP;AAGF,QAAI,KAAK,WAAW,WAAW,EAC7B,QAAO;;;AAKb,SAAO;GACP;;;;;;;;;;;;;;;;AAoBJ,SAAgB,sBACd,KACA,YACM;AAEN,kBAAiB,IAAI;AAGrB,qBAAqB,KAAK,WAAW"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { copyFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "pathe";
|
|
3
|
+
import { glob } from "tinyglobby";
|
|
4
|
+
//#region src/copy-files-plugin.ts
|
|
5
|
+
function copyFilesPlugin({ fromDir, toDir, pattern = "**" }) {
|
|
6
|
+
return {
|
|
7
|
+
name: "copy-files",
|
|
8
|
+
async writeBundle() {
|
|
9
|
+
const entries = await glob(pattern, { cwd: fromDir });
|
|
10
|
+
if (entries.length === 0) throw new Error(`No files found matching pattern "${pattern}" in directory "${fromDir}"`);
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
const srcPath = join(fromDir, entry);
|
|
13
|
+
const destPath = join(toDir, entry);
|
|
14
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
15
|
+
await copyFile(srcPath, destPath);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { copyFilesPlugin };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=copy-files-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-files-plugin.js","names":[],"sources":["../../src/copy-files-plugin.ts"],"sourcesContent":["import { copyFile, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'pathe'\nimport { glob } from 'tinyglobby'\nimport type { Plugin } from 'vite'\n\nexport function copyFilesPlugin({\n fromDir,\n toDir,\n pattern = '**',\n}: {\n pattern?: string | Array<string>\n fromDir: string\n toDir: string\n}): Plugin {\n return {\n name: 'copy-files',\n async writeBundle() {\n const entries = await glob(pattern, { cwd: fromDir })\n if (entries.length === 0) {\n throw new Error(\n `No files found matching pattern \"${pattern}\" in directory \"${fromDir}\"`,\n )\n }\n\n for (const entry of entries) {\n const srcPath = join(fromDir, entry)\n const destPath = join(toDir, entry)\n // Ensure the destination directory exists\n await mkdir(dirname(destPath), { recursive: true })\n await copyFile(srcPath, destPath)\n }\n },\n }\n}\n"],"mappings":";;;;AAKA,SAAgB,gBAAgB,EAC9B,SACA,OACA,UAAU,QAKD;AACT,QAAO;EACL,MAAM;EACN,MAAM,cAAc;GAClB,MAAM,UAAU,MAAM,KAAK,SAAS,EAAE,KAAK,SAAS,CAAC;AACrD,OAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MACR,oCAAoC,QAAQ,kBAAkB,QAAQ,GACvE;AAGH,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,UAAU,KAAK,SAAS,MAAM;IACpC,MAAM,WAAW,KAAK,OAAO,MAAM;AAEnC,UAAM,MAAM,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,UAAM,SAAS,SAAS,SAAS;;;EAGtC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { parseAst, generateFromAst, deadCodeElimination, findReferencedIdentifiers, stripTypeExports, } from './ast.js';
|
|
2
|
+
export type { ParseAstOptions, ParseAstResult, GeneratorResult } from './ast.js';
|
|
3
|
+
export { logDiff } from './logger.js';
|
|
4
|
+
export { copyFilesPlugin } from './copy-files-plugin.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { deadCodeElimination, findReferencedIdentifiers, generateFromAst, parseAst, stripTypeExports } from "./ast.js";
|
|
2
|
+
import { logDiff } from "./logger.js";
|
|
3
|
+
import { copyFilesPlugin } from "./copy-files-plugin.js";
|
|
4
|
+
export { copyFilesPlugin, deadCodeElimination, findReferencedIdentifiers, generateFromAst, logDiff, parseAst, stripTypeExports };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function logDiff(oldStr: string, newStr: string): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import ansis from "ansis";
|
|
2
|
+
import { diffWords } from "diff";
|
|
3
|
+
//#region src/logger.ts
|
|
4
|
+
function logDiff(oldStr, newStr) {
|
|
5
|
+
const differences = diffWords(oldStr, newStr);
|
|
6
|
+
let output = "";
|
|
7
|
+
let unchangedLines = "";
|
|
8
|
+
function processUnchangedLines(lines) {
|
|
9
|
+
const lineArray = lines.split("\n");
|
|
10
|
+
if (lineArray.length > 4) return [
|
|
11
|
+
ansis.dim(lineArray[0]),
|
|
12
|
+
ansis.dim(lineArray[1]),
|
|
13
|
+
"",
|
|
14
|
+
ansis.dim.bold(`... (${lineArray.length - 4} lines) ...`),
|
|
15
|
+
"",
|
|
16
|
+
ansis.dim(lineArray[lineArray.length - 2]),
|
|
17
|
+
ansis.dim(lineArray[lineArray.length - 1])
|
|
18
|
+
].join("\n");
|
|
19
|
+
return ansis.dim(lines);
|
|
20
|
+
}
|
|
21
|
+
differences.forEach((part, index) => {
|
|
22
|
+
const nextPart = differences[index + 1];
|
|
23
|
+
if (part.added) {
|
|
24
|
+
if (unchangedLines) {
|
|
25
|
+
output += processUnchangedLines(unchangedLines);
|
|
26
|
+
unchangedLines = "";
|
|
27
|
+
}
|
|
28
|
+
output += ansis.green.bold(part.value);
|
|
29
|
+
if (nextPart?.removed) output += " ";
|
|
30
|
+
} else if (part.removed) {
|
|
31
|
+
if (unchangedLines) {
|
|
32
|
+
output += processUnchangedLines(unchangedLines);
|
|
33
|
+
unchangedLines = "";
|
|
34
|
+
}
|
|
35
|
+
output += ansis.red.bold(part.value);
|
|
36
|
+
if (nextPart?.added) output += " ";
|
|
37
|
+
} else unchangedLines += part.value;
|
|
38
|
+
});
|
|
39
|
+
if (unchangedLines) output += processUnchangedLines(unchangedLines);
|
|
40
|
+
if (output) {
|
|
41
|
+
console.log("\nDiff:");
|
|
42
|
+
console.log(output + "\n\n");
|
|
43
|
+
} else console.log("No changes");
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { logDiff };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../src/logger.ts"],"sourcesContent":["import ansis from 'ansis'\nimport { diffWords } from 'diff'\n\nexport function logDiff(oldStr: string, newStr: string) {\n const differences = diffWords(oldStr, newStr)\n\n let output = ''\n let unchangedLines = ''\n\n function processUnchangedLines(lines: string): string {\n const lineArray = lines.split('\\n')\n if (lineArray.length > 4) {\n return [\n ansis.dim(lineArray[0]),\n ansis.dim(lineArray[1]),\n '',\n ansis.dim.bold(`... (${lineArray.length - 4} lines) ...`),\n '',\n ansis.dim(lineArray[lineArray.length - 2]),\n ansis.dim(lineArray[lineArray.length - 1]),\n ].join('\\n')\n }\n return ansis.dim(lines)\n }\n\n differences.forEach((part, index) => {\n const nextPart = differences[index + 1]\n\n if (part.added) {\n if (unchangedLines) {\n output += processUnchangedLines(unchangedLines)\n unchangedLines = ''\n }\n output += ansis.green.bold(part.value)\n if (nextPart?.removed) output += ' '\n } else if (part.removed) {\n if (unchangedLines) {\n output += processUnchangedLines(unchangedLines)\n unchangedLines = ''\n }\n output += ansis.red.bold(part.value)\n if (nextPart?.added) output += ' '\n } else {\n unchangedLines += part.value\n }\n })\n\n // Process any remaining unchanged lines at the end\n if (unchangedLines) {\n output += processUnchangedLines(unchangedLines)\n }\n\n if (output) {\n console.log('\\nDiff:')\n console.log(output + '\\n\\n')\n } else {\n console.log('No changes')\n }\n}\n"],"mappings":";;;AAGA,SAAgB,QAAQ,QAAgB,QAAgB;CACtD,MAAM,cAAc,UAAU,QAAQ,OAAO;CAE7C,IAAI,SAAS;CACb,IAAI,iBAAiB;CAErB,SAAS,sBAAsB,OAAuB;EACpD,MAAM,YAAY,MAAM,MAAM,KAAK;AACnC,MAAI,UAAU,SAAS,EACrB,QAAO;GACL,MAAM,IAAI,UAAU,GAAG;GACvB,MAAM,IAAI,UAAU,GAAG;GACvB;GACA,MAAM,IAAI,KAAK,QAAQ,UAAU,SAAS,EAAE,aAAa;GACzD;GACA,MAAM,IAAI,UAAU,UAAU,SAAS,GAAG;GAC1C,MAAM,IAAI,UAAU,UAAU,SAAS,GAAG;GAC3C,CAAC,KAAK,KAAK;AAEd,SAAO,MAAM,IAAI,MAAM;;AAGzB,aAAY,SAAS,MAAM,UAAU;EACnC,MAAM,WAAW,YAAY,QAAQ;AAErC,MAAI,KAAK,OAAO;AACd,OAAI,gBAAgB;AAClB,cAAU,sBAAsB,eAAe;AAC/C,qBAAiB;;AAEnB,aAAU,MAAM,MAAM,KAAK,KAAK,MAAM;AACtC,OAAI,UAAU,QAAS,WAAU;aACxB,KAAK,SAAS;AACvB,OAAI,gBAAgB;AAClB,cAAU,sBAAsB,eAAe;AAC/C,qBAAiB;;AAEnB,aAAU,MAAM,IAAI,KAAK,KAAK,MAAM;AACpC,OAAI,UAAU,MAAO,WAAU;QAE/B,mBAAkB,KAAK;GAEzB;AAGF,KAAI,eACF,WAAU,sBAAsB,eAAe;AAGjD,KAAI,QAAQ;AACV,UAAQ,IAAI,UAAU;AACtB,UAAQ,IAAI,SAAS,OAAO;OAE5B,SAAQ,IAAI,aAAa"}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@benjavicente/router-utils",
|
|
3
|
+
"version": "1.161.6",
|
|
4
|
+
"description": "Modern and scalable routing for React applications",
|
|
5
|
+
"author": "Tanner Linsley",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanStack/router.git",
|
|
10
|
+
"directory": "packages/router-utils"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://tanstack.com/router",
|
|
13
|
+
"funding": {
|
|
14
|
+
"type": "github",
|
|
15
|
+
"url": "https://github.com/sponsors/tannerlinsley"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"react",
|
|
19
|
+
"location",
|
|
20
|
+
"router",
|
|
21
|
+
"routing",
|
|
22
|
+
"async",
|
|
23
|
+
"async router",
|
|
24
|
+
"typescript"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"types": "dist/esm/index.d.ts",
|
|
28
|
+
"main": "dist/cjs/index.cjs",
|
|
29
|
+
"module": "dist/esm/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/esm/index.d.ts",
|
|
34
|
+
"default": "./dist/esm/index.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/cjs/index.d.cts",
|
|
38
|
+
"default": "./dist/cjs/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"src"
|
|
47
|
+
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20.19"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@babel/core": "^7.28.5",
|
|
53
|
+
"@babel/generator": "^7.28.5",
|
|
54
|
+
"@babel/parser": "^7.28.5",
|
|
55
|
+
"@babel/types": "^7.28.5",
|
|
56
|
+
"ansis": "^4.1.0",
|
|
57
|
+
"babel-dead-code-elimination": "^1.0.12",
|
|
58
|
+
"diff": "^8.0.2",
|
|
59
|
+
"pathe": "^2.0.3",
|
|
60
|
+
"tinyglobby": "^0.2.15"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/babel__core": "^7.20.5",
|
|
64
|
+
"@types/babel__generator": "^7.27.0",
|
|
65
|
+
"@types/diff": "^7.0.2",
|
|
66
|
+
"vite": "*",
|
|
67
|
+
"@types/node": ">=20"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"clean": "rimraf ./dist && rimraf ./coverage",
|
|
71
|
+
"test:eslint": "eslint ./src",
|
|
72
|
+
"test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"",
|
|
73
|
+
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js",
|
|
74
|
+
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js",
|
|
75
|
+
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js",
|
|
76
|
+
"test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js",
|
|
77
|
+
"test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js",
|
|
78
|
+
"test:types:ts60": "tsc",
|
|
79
|
+
"test:unit": "exit 0; vitest --typecheck",
|
|
80
|
+
"test:build": "publint --strict && attw --ignore-rules no-resolution --pack .",
|
|
81
|
+
"build": "vite build"
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/ast.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import _generate from '@babel/generator'
|
|
3
|
+
import * as t from '@babel/types'
|
|
4
|
+
import {
|
|
5
|
+
deadCodeElimination as _deadCodeElimination,
|
|
6
|
+
findReferencedIdentifiers,
|
|
7
|
+
} from 'babel-dead-code-elimination'
|
|
8
|
+
import type { GeneratorOptions, GeneratorResult } from '@babel/generator'
|
|
9
|
+
import type { ParseResult, ParserOptions } from '@babel/parser'
|
|
10
|
+
import type * as _babel_types from '@babel/types'
|
|
11
|
+
|
|
12
|
+
export type ParseAstOptions = ParserOptions & {
|
|
13
|
+
code: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ParseAstResult = ParseResult<_babel_types.File>
|
|
17
|
+
export function parseAst({ code, ...opts }: ParseAstOptions): ParseAstResult {
|
|
18
|
+
return parse(code, {
|
|
19
|
+
plugins: [
|
|
20
|
+
'jsx',
|
|
21
|
+
'typescript',
|
|
22
|
+
'explicitResourceManagement',
|
|
23
|
+
'decorators', // required for Angular and other decorator-based code
|
|
24
|
+
],
|
|
25
|
+
sourceType: 'module',
|
|
26
|
+
...opts,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let generate = _generate
|
|
31
|
+
|
|
32
|
+
if ('default' in generate) {
|
|
33
|
+
generate = generate.default as typeof generate
|
|
34
|
+
}
|
|
35
|
+
type GenerateFromAstOptions = GeneratorOptions &
|
|
36
|
+
Required<Pick<GeneratorOptions, 'sourceFileName' | 'filename'>>
|
|
37
|
+
export function generateFromAst(
|
|
38
|
+
ast: _babel_types.Node,
|
|
39
|
+
opts?: GenerateFromAstOptions,
|
|
40
|
+
): GeneratorResult {
|
|
41
|
+
return generate(
|
|
42
|
+
ast,
|
|
43
|
+
opts
|
|
44
|
+
? { importAttributesKeyword: 'with', sourceMaps: true, ...opts }
|
|
45
|
+
: undefined,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
export type { GeneratorResult } from '@babel/generator'
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Strips TypeScript type-only exports and imports from an AST.
|
|
52
|
+
*
|
|
53
|
+
* This is necessary because babel-dead-code-elimination doesn't handle
|
|
54
|
+
* TypeScript type exports/imports. When a type export references an import
|
|
55
|
+
* that pulls in server-only code, the dead code elimination won't remove
|
|
56
|
+
* that import because it sees the type as still referencing it.
|
|
57
|
+
*
|
|
58
|
+
* This function removes:
|
|
59
|
+
* - `export type Foo = ...`
|
|
60
|
+
* - `export interface Foo { ... }`
|
|
61
|
+
* - `export type { Foo } from './module'`
|
|
62
|
+
* - `export type * from './module'`
|
|
63
|
+
* - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`
|
|
64
|
+
* - `import type { Foo } from './module'`
|
|
65
|
+
* - Type specifiers in mixed imports: `import { value, type Foo } from './module'` -> `import { value }`
|
|
66
|
+
*
|
|
67
|
+
* Note: Non-exported type/interface declarations are preserved as they may be
|
|
68
|
+
* used as type annotations within the code.
|
|
69
|
+
*
|
|
70
|
+
* @param ast - The Babel AST (or ParseResult) to mutate
|
|
71
|
+
*/
|
|
72
|
+
export function stripTypeExports(ast: ParseResult<_babel_types.File>): void {
|
|
73
|
+
// Filter the program body to remove type-only nodes
|
|
74
|
+
ast.program.body = ast.program.body.filter((node) => {
|
|
75
|
+
// Handle export declarations
|
|
76
|
+
if (t.isExportNamedDeclaration(node)) {
|
|
77
|
+
// Remove entire export if it's a type-only export
|
|
78
|
+
// e.g., `export type Foo = string`, `export interface Bar {}`, `export type { X } from './y'`
|
|
79
|
+
if (node.exportKind === 'type') {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// For value exports with mixed specifiers, filter out type-only specifiers
|
|
84
|
+
// e.g., `export { value, type TypeOnly }` -> `export { value }`
|
|
85
|
+
if (node.specifiers.length > 0) {
|
|
86
|
+
node.specifiers = node.specifiers.filter((specifier) => {
|
|
87
|
+
if (t.isExportSpecifier(specifier)) {
|
|
88
|
+
return specifier.exportKind !== 'type'
|
|
89
|
+
}
|
|
90
|
+
return true
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// If all specifiers were removed, remove the entire export declaration
|
|
94
|
+
// (unless it has a declaration like `export const x = 1`)
|
|
95
|
+
if (node.specifiers.length === 0 && !node.declaration) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle type-only export-all declarations
|
|
102
|
+
// e.g., `export type * from './module'`
|
|
103
|
+
if (t.isExportAllDeclaration(node)) {
|
|
104
|
+
if (node.exportKind === 'type') {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle import declarations
|
|
110
|
+
if (t.isImportDeclaration(node)) {
|
|
111
|
+
// Remove entire import if it's a type-only import
|
|
112
|
+
// e.g., `import type { Foo } from './module'`
|
|
113
|
+
if (node.importKind === 'type') {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// For value imports with mixed specifiers, filter out type-only specifiers
|
|
118
|
+
// e.g., `import { value, type TypeOnly } from './module'` -> `import { value }`
|
|
119
|
+
if (node.specifiers.length > 0) {
|
|
120
|
+
node.specifiers = node.specifiers.filter((specifier) => {
|
|
121
|
+
if (t.isImportSpecifier(specifier)) {
|
|
122
|
+
return specifier.importKind !== 'type'
|
|
123
|
+
}
|
|
124
|
+
return true
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// If all specifiers were removed, remove the entire import declaration
|
|
128
|
+
if (node.specifiers.length === 0) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Re-export findReferencedIdentifiers from babel-dead-code-elimination
|
|
139
|
+
export { findReferencedIdentifiers }
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Performs dead code elimination on the AST, with TypeScript type stripping.
|
|
143
|
+
*
|
|
144
|
+
* This is a wrapper around babel-dead-code-elimination that first strips
|
|
145
|
+
* TypeScript type-only exports and imports. This is necessary because
|
|
146
|
+
* babel-dead-code-elimination doesn't handle type exports, which can cause
|
|
147
|
+
* imports to be retained when they're only referenced by type exports.
|
|
148
|
+
*
|
|
149
|
+
* @param ast - The Babel AST to mutate
|
|
150
|
+
* @param candidates - Optional set of identifier paths to consider for removal.
|
|
151
|
+
* If provided, only these identifiers will be candidates for removal.
|
|
152
|
+
* This should be the result of `findReferencedIdentifiers(ast)` called
|
|
153
|
+
* before any AST transformations.
|
|
154
|
+
*/
|
|
155
|
+
export function deadCodeElimination(
|
|
156
|
+
ast: ParseResult<_babel_types.File>,
|
|
157
|
+
candidates?: ReturnType<typeof findReferencedIdentifiers>,
|
|
158
|
+
): void {
|
|
159
|
+
// First strip TypeScript type-only exports and imports
|
|
160
|
+
stripTypeExports(ast)
|
|
161
|
+
|
|
162
|
+
// Then run the original dead code elimination
|
|
163
|
+
_deadCodeElimination(ast, candidates)
|
|
164
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { copyFile, mkdir } from 'node:fs/promises'
|
|
2
|
+
import { dirname, join } from 'pathe'
|
|
3
|
+
import { glob } from 'tinyglobby'
|
|
4
|
+
import type { Plugin } from 'vite'
|
|
5
|
+
|
|
6
|
+
export function copyFilesPlugin({
|
|
7
|
+
fromDir,
|
|
8
|
+
toDir,
|
|
9
|
+
pattern = '**',
|
|
10
|
+
}: {
|
|
11
|
+
pattern?: string | Array<string>
|
|
12
|
+
fromDir: string
|
|
13
|
+
toDir: string
|
|
14
|
+
}): Plugin {
|
|
15
|
+
return {
|
|
16
|
+
name: 'copy-files',
|
|
17
|
+
async writeBundle() {
|
|
18
|
+
const entries = await glob(pattern, { cwd: fromDir })
|
|
19
|
+
if (entries.length === 0) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`No files found matching pattern "${pattern}" in directory "${fromDir}"`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const srcPath = join(fromDir, entry)
|
|
27
|
+
const destPath = join(toDir, entry)
|
|
28
|
+
// Ensure the destination directory exists
|
|
29
|
+
await mkdir(dirname(destPath), { recursive: true })
|
|
30
|
+
await copyFile(srcPath, destPath)
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
parseAst,
|
|
3
|
+
generateFromAst,
|
|
4
|
+
deadCodeElimination,
|
|
5
|
+
findReferencedIdentifiers,
|
|
6
|
+
stripTypeExports,
|
|
7
|
+
} from './ast'
|
|
8
|
+
export type { ParseAstOptions, ParseAstResult, GeneratorResult } from './ast'
|
|
9
|
+
export { logDiff } from './logger'
|
|
10
|
+
|
|
11
|
+
export { copyFilesPlugin } from './copy-files-plugin'
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import ansis from 'ansis'
|
|
2
|
+
import { diffWords } from 'diff'
|
|
3
|
+
|
|
4
|
+
export function logDiff(oldStr: string, newStr: string) {
|
|
5
|
+
const differences = diffWords(oldStr, newStr)
|
|
6
|
+
|
|
7
|
+
let output = ''
|
|
8
|
+
let unchangedLines = ''
|
|
9
|
+
|
|
10
|
+
function processUnchangedLines(lines: string): string {
|
|
11
|
+
const lineArray = lines.split('\n')
|
|
12
|
+
if (lineArray.length > 4) {
|
|
13
|
+
return [
|
|
14
|
+
ansis.dim(lineArray[0]),
|
|
15
|
+
ansis.dim(lineArray[1]),
|
|
16
|
+
'',
|
|
17
|
+
ansis.dim.bold(`... (${lineArray.length - 4} lines) ...`),
|
|
18
|
+
'',
|
|
19
|
+
ansis.dim(lineArray[lineArray.length - 2]),
|
|
20
|
+
ansis.dim(lineArray[lineArray.length - 1]),
|
|
21
|
+
].join('\n')
|
|
22
|
+
}
|
|
23
|
+
return ansis.dim(lines)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
differences.forEach((part, index) => {
|
|
27
|
+
const nextPart = differences[index + 1]
|
|
28
|
+
|
|
29
|
+
if (part.added) {
|
|
30
|
+
if (unchangedLines) {
|
|
31
|
+
output += processUnchangedLines(unchangedLines)
|
|
32
|
+
unchangedLines = ''
|
|
33
|
+
}
|
|
34
|
+
output += ansis.green.bold(part.value)
|
|
35
|
+
if (nextPart?.removed) output += ' '
|
|
36
|
+
} else if (part.removed) {
|
|
37
|
+
if (unchangedLines) {
|
|
38
|
+
output += processUnchangedLines(unchangedLines)
|
|
39
|
+
unchangedLines = ''
|
|
40
|
+
}
|
|
41
|
+
output += ansis.red.bold(part.value)
|
|
42
|
+
if (nextPart?.added) output += ' '
|
|
43
|
+
} else {
|
|
44
|
+
unchangedLines += part.value
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Process any remaining unchanged lines at the end
|
|
49
|
+
if (unchangedLines) {
|
|
50
|
+
output += processUnchangedLines(unchangedLines)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (output) {
|
|
54
|
+
console.log('\nDiff:')
|
|
55
|
+
console.log(output + '\n\n')
|
|
56
|
+
} else {
|
|
57
|
+
console.log('No changes')
|
|
58
|
+
}
|
|
59
|
+
}
|