Skip to content

Commit c2fa7c4

Browse files
authored
refactor(module-tools): use ast-grep to replace dts alias instead of babel (web-infra-dev#5044)
1 parent 4ee48fb commit c2fa7c4

File tree

11 files changed

+123
-208
lines changed

11 files changed

+123
-208
lines changed

.changeset/breezy-lemons-provide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modern-js/module-tools': patch
3+
---
4+
5+
refactor(module-tools): use ast-grep to replace dts alias instead of babel
6+
refactor(module-tools): 使用 ast-grep 替代 babel 处理 d.ts 文件里的别名

packages/document/module-doc/docs/en/guide/advance/in-depth-about-build.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,7 @@ During the bundleless build process, if an alias appears in the source code, e.g
255255
import utils from '@common/utils';
256256
```
257257

258-
Normally, the type files generated with `tsc` will also contain these aliases. However, Modern.js Module will convert the aliases in the type file generated by `tsc` to:
259-
260-
- Alias conversion is possible for code of the form `import '@common/utils'` or `import utils from '@common/utils'`.
261-
- Aliasing is possible for code of the form `export { utils } from '@common/utils'`.
262-
263-
However, there are some cases that cannot be handled at this time.Output types of the form `Promise<import('@common/utils')>` cannot be converted at this time.
264-
You can discuss it [here](https://github.com/web-infra-dev/modern.js/discussions/4511)
258+
The type files generated with `tsc` will also contain these aliases. However, Modern.js Module will convert the aliases in the type file generated by `tsc`.
265259

266260
### Some examples of the use of `dts`
267261

packages/document/module-doc/docs/zh/guide/advance/in-depth-about-build.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,7 @@ export default defineConfig({
255255
import utils from '@common/utils';
256256
```
257257

258-
正常来说,使用 `tsc` 生成的产物类型文件也会包含这些别名。不过 Modern.js Module 会对 `tsc` 生成的类型文件里的别名进行转换处理:
259-
260-
- 对于类似 `import '@common/utils'` 或者 `import utils from '@common/utils'` 这样形式的代码可以进行别名转换。
261-
- 对于类似 `export { utils } from '@common/utils'` 这样形式的代码可以进行别名转换。
262-
263-
然而也存在一些情况,目前还无法处理,例如 `Promise<import('@common/utils')>` 这样形式的输出类型目前无法进行转换。
264-
对于这种情况的解决办法,可以参与[讨论](https://github.com/web-infra-dev/modern.js/discussions/4511)
258+
使用 `tsc` 生成的产物类型文件也会包含这些别名。不过 Modern.js Module 会对 `tsc` 生成的类型文件里的别名进行转换处理。
265259

266260
### 一些示例
267261

packages/solutions/module-tools/package.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@
5757
"dependencies": {
5858
"@ampproject/remapping": "1.0.2",
5959
"@ast-grep/napi": "0.12.0",
60-
"@babel/generator": "^7.22.15",
61-
"@babel/parser": "^7.22.15",
62-
"@babel/traverse": "^7.23.2",
63-
"@babel/types": "^7.22.15",
6460
"@modern-js/core": "workspace:*",
6561
"@modern-js/new-action": "workspace:*",
6662
"@modern-js/plugin": "workspace:*",
@@ -95,8 +91,6 @@
9591
"@modern-js/self": "workspace:@modern-js/module-tools@*",
9692
"@scripts/build": "workspace:*",
9793
"@scripts/vitest-config": "workspace:*",
98-
"@types/babel__generator": "7.6.4",
99-
"@types/babel__traverse": "7.18.5",
10094
"@types/convert-source-map": "1.5.2",
10195
"@types/node": "^14",
10296
"typescript": "^5"

packages/solutions/module-tools/src/builder/dts/tsc.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import type { GeneratorDtsConfig, PluginAPI, ModuleTools } from '../../types';
44
import {
55
getTscBinPath,
66
printOrThrowDtsErrors,
7-
resolveAlias,
87
addDtsFiles,
9-
writeDtsFiles,
10-
addBannerAndFooter,
118
withLogTitle,
9+
processDtsFilesAfterTsc,
1210
} from '../../utils';
1311
import { watchDoneText } from '../../constants/dts';
1412

@@ -100,9 +98,7 @@ const runTscBin = async (
10098
resolveLog(childProgress, {
10199
watch,
102100
watchFn: async () => {
103-
const result = await resolveAlias(config);
104-
const dtsFiles = addBannerAndFooter(result, config.banner, config.footer);
105-
await writeDtsFiles(config, dtsFiles);
101+
await processDtsFilesAfterTsc(config);
106102
runner.buildWatchDts({ buildType: 'bundleless' });
107103
},
108104
});
@@ -119,8 +115,6 @@ export const runTsc = async (
119115
config: GeneratorDtsConfig,
120116
) => {
121117
await runTscBin(api, config);
122-
const result = await resolveAlias(config);
123-
const dtsFiles = addBannerAndFooter(result, config.banner, config.footer);
124-
await writeDtsFiles(config, dtsFiles);
118+
await processDtsFilesAfterTsc(config);
125119
await addDtsFiles(config.distPath, config.appDirectory);
126120
};

packages/solutions/module-tools/src/utils/dts.ts

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
import { join, dirname, isAbsolute } from 'path';
1+
import { join, dirname, relative, resolve } from 'path';
22
import { chalk, fs, globby, json5, logger } from '@modern-js/utils';
33
import MagicString from 'magic-string';
4+
import { createMatchPath, loadConfig } from '@modern-js/utils/tsconfig-paths';
5+
import { ts } from '@ast-grep/napi';
46
import type {
57
ITsconfig,
68
GeneratorDtsConfig,
79
BuildType,
810
TsTarget,
911
} from '../types';
12+
import { normalizeSlashes } from './builder';
13+
14+
type MatchModule = {
15+
name?: string;
16+
start: number;
17+
end: number;
18+
}[];
1019

1120
export const getProjectTsconfig = async (
1221
tsconfigPath: string,
@@ -43,66 +52,114 @@ export const getTscBinPath = async (appDirectory: string) => {
4352
return tscBinFile;
4453
};
4554

46-
export const resolveAlias = async (config: GeneratorDtsConfig) => {
47-
const { userTsconfig, distPath, tsconfigPath } = config;
48-
const { transformDtsAlias } = await import('./tspath');
49-
const dtsFilenames = await globby('**/*.d.ts', {
55+
export const processDtsFilesAfterTsc = async (config: GeneratorDtsConfig) => {
56+
const { distPath, tsconfigPath, userTsconfig, dtsExtension, banner, footer } =
57+
config;
58+
const dtsFilesPath = await globby('**/*.d.ts', {
5059
absolute: true,
5160
cwd: distPath,
5261
});
53-
const userBaseUrl = userTsconfig.compilerOptions?.baseUrl;
54-
const baseUrl = isAbsolute(userBaseUrl || '.')
55-
? userBaseUrl!
56-
: join(dirname(tsconfigPath), userBaseUrl || '.');
57-
const result = transformDtsAlias({
58-
filenames: dtsFilenames,
59-
baseUrl,
60-
paths: userTsconfig.compilerOptions?.paths ?? {},
61-
});
62-
return result;
63-
};
6462

65-
export const writeDtsFiles = async (
66-
config: GeneratorDtsConfig,
67-
result: { path: string; content: string }[],
68-
) => {
69-
const { dtsExtension } = config;
70-
// write to dist
63+
/**
64+
* get matchPath func to support tsconfig paths
65+
*/
66+
const result = loadConfig(tsconfigPath);
67+
if (result.resultType === 'failed') {
68+
logger.error(result.message);
69+
return;
70+
}
71+
const { absoluteBaseUrl, paths, mainFields, addMatchAll } = result;
72+
const matchPath = createMatchPath(
73+
absoluteBaseUrl,
74+
paths,
75+
mainFields,
76+
addMatchAll,
77+
);
78+
79+
/**
80+
* `export $VAR from` is invalid, so we need `{$$$VAR}`, `*` and `* as $VAR`
81+
* But `import $VAR from` is valid.
82+
*/
83+
const Pattern = [
84+
`import $VAR from '$MATCH'`,
85+
`import $VAR from "$MATCH"`,
86+
`export {$$$VAR} from '$MATCH'`,
87+
`export {$$$VAR} from "$MATCH"`,
88+
`export * from '$MATCH'`,
89+
`export * from "$MATCH"`,
90+
`export * as $VAR from '$MATCH'`,
91+
`export * as $VAR from "$MATCH"`,
92+
`import('$MATCH')`,
93+
`import("$MATCH")`,
94+
];
95+
7196
await Promise.all(
72-
result.map(({ path, content }) => {
73-
const filepath =
97+
dtsFilesPath.map(filePath => {
98+
const code = fs.readFileSync(filePath, 'utf8');
99+
let matchModule: MatchModule = [];
100+
try {
101+
const sgNode = ts.parse(code).root();
102+
matchModule = Pattern.map(p => sgNode.findAll(p))
103+
.flat()
104+
.map(node => {
105+
const matchNode = node.getMatch('MATCH')!;
106+
return {
107+
name: matchNode.text(),
108+
start: matchNode.range().start.index,
109+
end: matchNode.range().end.index,
110+
};
111+
});
112+
} catch (e) {
113+
logger.error('[parse error]', e);
114+
}
115+
const str: MagicString = new MagicString(code);
116+
117+
const originalFilePath = resolve(
118+
absoluteBaseUrl,
119+
userTsconfig?.compilerOptions?.rootDir || 'src',
120+
relative(distPath, filePath),
121+
);
122+
123+
matchModule.forEach(module => {
124+
if (!module.name) {
125+
return;
126+
}
127+
const { start, end, name } = module;
128+
const absoluteImportPath = matchPath(name);
129+
if (absoluteImportPath) {
130+
const relativePath = relative(
131+
dirname(originalFilePath),
132+
absoluteImportPath,
133+
);
134+
const relativeImportPath = normalizeSlashes(
135+
relativePath.startsWith('..') ? relativePath : `./${relativePath}`,
136+
);
137+
str.overwrite(start, end, relativeImportPath);
138+
}
139+
});
140+
141+
// add banner and footer
142+
banner && str.prepend(`${banner}\n`);
143+
footer && str.append(`\n${footer}\n`);
144+
145+
// rewrite dts file
146+
const content = str.toString();
147+
const finalPath =
74148
// We confirm that users will not mix ts and c(m)ts files in their projects.
75149
// If a mix is required, please configure separate buildConfig to handle different inputs.
76150
// So we don't replace .d.(c|m)ts that generated by tsc directly, this can confirm that
77151
// users can use c(m)ts directly rather than enable autoExtension, in this condition,
78152
// users need to set esbuild out-extensions like { '.js': '.mjs' }
79-
path.replace(/\.d\.ts/, dtsExtension);
80-
fs.ensureFileSync(filepath);
153+
filePath.replace(/\.d\.ts/, dtsExtension);
81154
return fs.writeFile(
82155
// only replace .d.ts, if tsc generate .d.m(c)ts, keep.
83-
filepath,
156+
finalPath,
84157
content,
85158
);
86159
}),
87160
);
88161
};
89162

90-
export const addBannerAndFooter = (
91-
result: { path: string; content: string }[],
92-
banner?: string,
93-
footer?: string,
94-
) => {
95-
return result.map(({ path, content }) => {
96-
const ms = new MagicString(content);
97-
banner && ms.prepend(`${banner}\n`);
98-
footer && ms.append(`\n${footer}\n`);
99-
return {
100-
path,
101-
content: ms.toString(),
102-
};
103-
});
104-
};
105-
106163
export const printOrThrowDtsErrors = async (
107164
error: unknown,
108165
options: { abortOnError?: boolean; buildType: BuildType },

packages/solutions/module-tools/src/utils/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ export * from './log';
88
export * from './map';
99
export * from './print';
1010
export * from './style';
11-
export * from './tspath';
1211
export * from './outExtension';

packages/solutions/module-tools/src/utils/tspath.ts

Lines changed: 0 additions & 110 deletions
This file was deleted.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy