Skip to content

Commit 017d397

Browse files
committedNov 13, 2024
feat: add declaration task
1 parent 9d79a4e commit 017d397

17 files changed

+505
-230
lines changed
 

‎.changeset/long-experts-design.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ice/pkg': minor
3+
---
4+
5+
feat: add individual declaration task for speed

‎packages/pkg/src/config/userConfig.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import type {
88
BundleUserConfig,
99
TransformUserConfig,
1010
TransformTaskConfig,
11+
DeclarationTaskConfig,
12+
DeclarationUserConfig,
1113
} from '../types.js';
1214

1315
function getUserConfig() {
@@ -24,6 +26,9 @@ function getUserConfig() {
2426
const defaultTransformUserConfig: TransformUserConfig = {
2527
formats: ['esm', 'es2017'],
2628
};
29+
const defaultDeclarationUserConfig: DeclarationUserConfig = {
30+
outputMode: 'multi',
31+
};
2732
const userConfig = [
2833
{
2934
name: 'entry',
@@ -75,8 +80,30 @@ function getUserConfig() {
7580
},
7681
{
7782
name: 'declaration',
78-
validation: 'boolean',
83+
validation: 'boolean|object',
7984
defaultValue: true,
85+
setConfig: (config: TaskConfig, declaration: UserConfig['declaration']) => {
86+
if (config.type === 'declaration') {
87+
if (declaration === false) {
88+
return config;
89+
}
90+
let taskConfig = config;
91+
const mergedConfig = typeof declaration === 'object' ? {
92+
...defaultDeclarationUserConfig,
93+
...declaration,
94+
} : { ...defaultDeclarationUserConfig };
95+
96+
Object.keys(mergedConfig).forEach((key) => {
97+
taskConfig = mergeValueToTaskConfig<DeclarationTaskConfig>(
98+
taskConfig,
99+
key,
100+
mergedConfig[key],
101+
);
102+
});
103+
104+
return taskConfig;
105+
}
106+
},
80107
},
81108
// TODO: validate values recursively
82109
{

‎packages/pkg/src/helpers/dts.ts

+100-92
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import ts from 'typescript';
22
import consola from 'consola';
3-
import { performance } from 'perf_hooks';
4-
import { timeFrom, normalizePath } from '../utils.js';
5-
import { createLogger } from './logger.js';
6-
import formatAliasToTSPathsConfig from './formatAliasToTSPathsConfig.js';
7-
import type { TaskConfig } from '../types.js';
3+
import { normalizePath } from '../utils.js';
4+
import { TaskConfig } from '../types.js';
85
import { prepareSingleFileReplaceTscAliasPaths } from 'tsc-alias';
96
import fse from 'fs-extra';
107
import * as path from 'path';
@@ -23,8 +20,8 @@ export interface DtsInputFile extends File {
2320
dtsPath?: string;
2421
}
2522

26-
const normalizeDtsInput = (file: File, rootDir: string, outputDir: string): DtsInputFile => {
27-
const { filePath, ext } = file;
23+
const normalizeDtsInput = (filePath: string, rootDir: string, outputDir: string): DtsInputFile => {
24+
const ext = path.extname(filePath) as FileExt;
2825
// https://www.typescriptlang.org/docs/handbook/esm-node.html#new-file-extensions
2926
// a.js -> a.d.ts
3027
// a.cjs -> a.d.cts
@@ -34,59 +31,106 @@ const normalizeDtsInput = (file: File, rootDir: string, outputDir: string): DtsI
3431
// a.mts -> a.d.mts
3532
const dtsPath = filePath.replace(path.join(rootDir, 'src'), outputDir).replace(ext, `.d.${/^\.[jt]/.test(ext) ? '' : ext[1]}ts`);
3633
return {
37-
...file,
34+
filePath,
35+
ext,
3836
dtsPath,
3937
};
4038
};
4139

42-
interface DtsCompileOptions {
40+
export interface DtsCompileOptions {
4341
// In watch mode, it only contains the updated file names. In build mode, it contains all file names.
44-
files: File[];
42+
files: string[];
4543
alias: TaskConfig['alias'];
4644
rootDir: string;
4745
outputDir: string;
46+
}
47+
48+
function formatAliasToTSPathsConfig(alias: TaskConfig['alias']) {
49+
const paths: { [from: string]: [string] } = {};
50+
51+
Object.entries(alias || {})
52+
.forEach(([key, value]) => {
53+
const [pathKey, pathValue] = formatPath(key, value);
54+
paths[pathKey] = [pathValue];
55+
});
4856

57+
return paths;
4958
}
5059

51-
export async function dtsCompile({ files, alias, rootDir, outputDir }: DtsCompileOptions): Promise<DtsInputFile[]> {
52-
if (!files.length) {
53-
return;
60+
function formatPath(key: string, value: string) {
61+
if (key.endsWith('$')) {
62+
return [key.replace(/\$$/, ''), value];
5463
}
64+
// abc -> abc/*
65+
// abc/ -> abc/*
66+
return [addWildcard(key), addWildcard(value)];
67+
}
5568

56-
const tsConfig = await getTSConfig(rootDir, outputDir, alias);
69+
function addWildcard(str: string) {
70+
return `${str.endsWith('/') ? str : `${str}/`}*`;
71+
}
5772

58-
const logger = createLogger('dts');
73+
async function getTSConfig(
74+
rootDir: string,
75+
outputDir: string,
76+
alias: TaskConfig['alias'],
77+
) {
78+
const defaultTSCompilerOptions: ts.CompilerOptions = {
79+
allowJs: true,
80+
declaration: true,
81+
emitDeclarationOnly: true,
82+
incremental: true,
83+
skipLibCheck: true,
84+
paths: formatAliasToTSPathsConfig(alias), // default add alias to paths
85+
};
86+
const projectTSConfig = await getProjectTSConfig(rootDir);
87+
const tsConfig: ts.ParsedCommandLine = merge(
88+
{ options: defaultTSCompilerOptions },
89+
projectTSConfig,
90+
{
91+
options: {
92+
outDir: outputDir,
93+
rootDir: path.join(rootDir, 'src'),
94+
},
95+
},
96+
);
5997

60-
logger.debug('Start Compiling typescript declarations...');
98+
return tsConfig;
99+
}
61100

62-
const dtsCompileStart = performance.now();
101+
async function getProjectTSConfig(rootDir: string): Promise<ts.ParsedCommandLine> {
102+
const tsconfigPath = ts.findConfigFile(rootDir, ts.sys.fileExists);
103+
if (tsconfigPath) {
104+
const tsconfigFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
105+
return ts.parseJsonConfigFileContent(
106+
tsconfigFile.config,
107+
ts.sys,
108+
path.dirname(tsconfigPath),
109+
);
110+
}
63111

64-
const _files = files
65-
.map((file) => normalizeDtsInput(file, rootDir, outputDir))
66-
.map(({ filePath, dtsPath, ...rest }) => ({
67-
...rest,
68-
// Be compatible with Windows env.
69-
filePath: normalizePath(filePath),
70-
dtsPath: normalizePath(dtsPath),
71-
}));
112+
return {
113+
options: {},
114+
fileNames: [],
115+
errors: [],
116+
};
117+
}
72118

73-
const dtsFiles = {};
119+
export async function dtsCompile({ files, rootDir, outputDir, alias }: DtsCompileOptions): Promise<DtsInputFile[]> {
120+
if (!files.length) {
121+
return [];
122+
}
74123

75-
// Create ts host and custom the writeFile and readFile.
76-
const host = ts.createCompilerHost(tsConfig.options);
77-
host.writeFile = (fileName, contents) => {
78-
dtsFiles[fileName] = contents;
79-
};
124+
const tsConfig = await getTSConfig(rootDir, outputDir, alias);
80125

81-
const _readFile = host.readFile;
82-
// Hijack `readFile` to prevent reading file twice
83-
host.readFile = (fileName) => {
84-
const foundItem = files.find((file) => file.filePath === fileName);
85-
if (foundItem && foundItem.srcCode) {
86-
return foundItem.srcCode;
87-
}
88-
return _readFile(fileName);
89-
};
126+
const _files = files
127+
.map((file) => normalizeDtsInput(file, rootDir, outputDir))
128+
.map<DtsInputFile>(({ filePath, dtsPath, ...rest }) => ({
129+
...rest,
130+
// Be compatible with Windows env.
131+
filePath: normalizePath(filePath),
132+
dtsPath: normalizePath(dtsPath),
133+
}));
90134

91135
// In order to only include the update files instead of all the files in the watch mode.
92136
function getProgramRootNames(originalFilenames: string[]) {
@@ -97,7 +141,13 @@ export async function dtsCompile({ files, alias, rootDir, outputDir }: DtsCompil
97141
return [...needCompileFileNames, ...dtsFilenames];
98142
}
99143

100-
// Create ts program.
144+
const dtsFiles = {};
145+
const host = ts.createCompilerHost(tsConfig.options);
146+
147+
host.writeFile = (fileName, contents) => {
148+
dtsFiles[fileName] = contents;
149+
};
150+
101151
const programOptions: ts.CreateProgramOptions = {
102152
rootNames: getProgramRootNames(tsConfig.fileNames),
103153
options: tsConfig.options,
@@ -107,8 +157,6 @@ export async function dtsCompile({ files, alias, rootDir, outputDir }: DtsCompil
107157
};
108158
const program = ts.createProgram(programOptions);
109159

110-
logger.debug(`Initializing program takes ${timeFrom(dtsCompileStart)}`);
111-
112160
const emitResult = program.emit();
113161

114162
if (emitResult.diagnostics && emitResult.diagnostics.length > 0) {
@@ -123,9 +171,17 @@ export async function dtsCompile({ files, alias, rootDir, outputDir }: DtsCompil
123171
});
124172
}
125173

174+
if (!Object.keys(alias).length) {
175+
// no alias config
176+
return _files.map((file) => ({
177+
...file,
178+
dtsContent: dtsFiles[file.dtsPath],
179+
}));
180+
}
181+
126182
// We use tsc-alias to resolve d.ts alias.
127183
// Reason: https://github.com/microsoft/TypeScript/issues/30952#issuecomment-1114225407
128-
const tsConfigLocalPath = path.join(rootDir, 'node_modules/pkg/tsconfig.json');
184+
const tsConfigLocalPath = path.join(rootDir, 'node_modules/.cache/ice-pkg/tsconfig.json');
129185
await fse.ensureFile(tsConfigLocalPath);
130186
await fse.writeJSON(tsConfigLocalPath, {
131187
...tsConfig,
@@ -142,53 +198,5 @@ export async function dtsCompile({ files, alias, rootDir, outputDir }: DtsCompil
142198
dtsContent: dtsFiles[file.dtsPath] ? runFile({ fileContents: dtsFiles[file.dtsPath], filePath: file.dtsPath }) : '',
143199
}));
144200

145-
logger.debug(`Generating declaration files take ${timeFrom(dtsCompileStart)}`);
146-
147201
return result;
148202
}
149-
150-
async function getTSConfig(
151-
rootDir: string,
152-
outputDir: string,
153-
alias: TaskConfig['alias'],
154-
) {
155-
const defaultTSCompilerOptions: ts.CompilerOptions = {
156-
allowJs: true,
157-
declaration: true,
158-
emitDeclarationOnly: true,
159-
incremental: true,
160-
skipLibCheck: true,
161-
paths: formatAliasToTSPathsConfig(alias), // default add alias to paths
162-
};
163-
const projectTSConfig = await getProjectTSConfig(rootDir);
164-
const tsConfig: ts.ParsedCommandLine = merge(
165-
{ options: defaultTSCompilerOptions },
166-
projectTSConfig,
167-
{
168-
options: {
169-
outDir: outputDir,
170-
rootDir: path.join(rootDir, 'src'),
171-
},
172-
},
173-
);
174-
175-
return tsConfig;
176-
}
177-
178-
async function getProjectTSConfig(rootDir: string): Promise<ts.ParsedCommandLine> {
179-
const tsconfigPath = ts.findConfigFile(rootDir, ts.sys.fileExists);
180-
if (tsconfigPath) {
181-
const tsconfigFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
182-
return ts.parseJsonConfigFileContent(
183-
tsconfigFile.config,
184-
ts.sys,
185-
path.dirname(tsconfigPath),
186-
);
187-
}
188-
189-
return {
190-
options: {},
191-
fileNames: [],
192-
errors: [],
193-
};
194-
}

‎packages/pkg/src/helpers/formatAliasToTSPathsConfig.ts

-25
This file was deleted.

‎packages/pkg/src/helpers/getBuildTasks.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import deepmerge from 'deepmerge';
2+
import path from 'node:path';
23
import { formatEntry, getTransformDefaultOutputDir } from './getTaskIO.js';
34
import { getDefaultBundleSwcConfig, getDefaultTransformSwcConfig } from './defaultSwcConfig.js';
45
import { stringifyObject } from '../utils.js';
@@ -49,6 +50,14 @@ function getBuildTask(buildTask: BuildTask, context: Context): BuildTask {
4950
defaultTransformSwcConfig,
5051
config.swcCompileOptions || {},
5152
);
53+
} else if (config.type === 'declaration') {
54+
// 这个 output 仅仅用于生成正确的 .d.ts 的 alias,不做实际输出目录
55+
config.outputDir = path.resolve(rootDir, config.transformFormats[0]);
56+
if (config.outputMode === 'unique') {
57+
config.declarationOutputDirs = [path.resolve(rootDir, 'typings')];
58+
} else {
59+
config.declarationOutputDirs = config.transformFormats.map((format) => path.resolve(rootDir, format));
60+
}
5261
} else {
5362
throw new Error('Invalid task type.');
5463
}

‎packages/pkg/src/helpers/getRollupOptions.ts

+1-13
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import autoprefixer from 'autoprefixer';
66
import PostcssPluginRpxToVw from 'postcss-plugin-rpx2vw';
77
import json from '@rollup/plugin-json';
88
import swcPlugin from '../rollupPlugins/swc.js';
9-
import dtsPlugin from '../rollupPlugins/dts.js';
109
import minifyPlugin from '../rollupPlugins/minify.js';
1110
import babelPlugin from '../rollupPlugins/babel.js';
1211
import { builtinNodeModules } from './builtinModules.js';
@@ -44,7 +43,7 @@ export function getRollupOptions(
4443
context: Context,
4544
taskRunnerContext: TaskRunnerContext,
4645
) {
47-
const { pkg, commandArgs, command, userConfig, rootDir } = context;
46+
const { pkg, commandArgs, command, rootDir } = context;
4847
const { name: taskName, config: taskConfig } = taskRunnerContext.buildTask;
4948
const rollupOptions: RollupOptions = {};
5049
const plugins: Plugin[] = [];
@@ -73,17 +72,6 @@ export function getRollupOptions(
7372
);
7473

7574
if (taskConfig.type === 'transform') {
76-
if (userConfig.declaration) {
77-
plugins.unshift(
78-
dtsPlugin({
79-
rootDir,
80-
entry: taskConfig.entry as Record<string, string>,
81-
generateTypesForJs: userConfig.generateTypesForJs,
82-
alias: taskConfig.alias,
83-
outputDir: taskConfig.outputDir,
84-
}),
85-
);
86-
}
8775
plugins.push(transformAliasPlugin(rootDir, taskConfig.alias));
8876
} else if (taskConfig.type === 'bundle') {
8977
const [external, globals] = getExternalsAndGlobals(taskConfig, pkg as PkgJson);

0 commit comments

Comments
 (0)