forked from elastic/eui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdtsgenerator.js
175 lines (164 loc) Β· 6.66 KB
/
dtsgenerator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
const findup = require('findup');
const resolve = require('resolve');
const fs = require('fs');
const path = require('path');
const dtsGenerator = require('dts-generator').default;
const baseDir = path.resolve(__dirname, '..');
const srcDir = path.resolve(baseDir, 'src');
function hasParentIndex(pathToFile) {
const isIndexFile =
path.basename(pathToFile, path.extname(pathToFile)) === 'index';
try {
const fileDirectory = path.dirname(pathToFile);
const parentIndex = findup.sync(
// if this is an index file start looking in its parent directory
isIndexFile ? path.resolve(fileDirectory, '..') : fileDirectory,
'index.ts'
);
// ensure the found file is in the project
return parentIndex.startsWith(baseDir);
} catch (e) {
return false;
}
}
const generator = dtsGenerator({
prefix: '@elastic/eui',
project: baseDir,
out: 'eui.d.ts',
exclude: [
'node_modules/**/*.d.ts',
'*/custom_typings/**/*.d.ts',
'**/*.test.{ts,tsx}',
'**/*.testenv.{ts,tsx}',
'**/*.spec.{ts,tsx}',
'**/*.mock.{ts,tsx}',
'**/__mocks__/*',
'src/themes/charts/*', // A separate d.ts file is generated for the charts theme file
'src/test/*', // A separate d.ts file is generated for test utils
'src-docs/**/*', // Don't include src-docs
],
resolveModuleId(params) {
if (
path.basename(params.currentModuleId) === 'index' &&
!hasParentIndex(path.resolve(baseDir, params.currentModuleId))
) {
// this module is exporting from an `index(.d)?.ts` file, declare its exports straight to @elastic/eui module
return '@elastic/eui';
} else {
// otherwise export as the module's path relative to the @elastic/eui namespace
if (params.currentModuleId.endsWith('/index')) {
return path.join('@elastic/eui', path.dirname(params.currentModuleId));
} else {
return path.join('@elastic/eui', params.currentModuleId);
}
}
},
resolveModuleImport(params) {
// only intercept relative imports (don't modify node-modules references)
const importFromBaseDir = path.resolve(
baseDir,
path.dirname(params.currentModuleId)
);
const isFromEuiSrc = importFromBaseDir.startsWith(srcDir);
const isRelativeImport = isFromEuiSrc && params.importedModuleId[0] === '.';
if (isRelativeImport) {
// if importing from an `index` file (directly or targeting a directory with an index),
// then if there is no parent index file this should import from @elastic/eui
const importPathTarget = resolve.sync(params.importedModuleId, {
basedir: importFromBaseDir,
extensions: ['.ts', '.tsx', '.d.ts'],
});
const isIndexFile = importPathTarget.endsWith('/index.ts');
const isModuleIndex = isIndexFile && !hasParentIndex(importPathTarget);
if (isModuleIndex) {
// importing an `index` file, in `resolveModuleId` above we change those modules to '@elastic/eui'
return '@elastic/eui';
} else {
// importing from a non-index TS source file, keep the import path but re-scope it to '@elastic/eui' namespace
return path.join(
'@elastic/eui',
path.dirname(params.currentModuleId),
params.importedModuleId
);
}
} else {
return params.importedModuleId;
}
},
});
// NOTE: once EUI is all converted to typescript this madness can be deleted forever
// 1. strip any `/// <reference` lines from the generated eui.d.ts
// 2. replace any import("src/...") declarations to import("@elastic/eui/src/...")
// 3. replace any import("./...") declarations to import("@elastic/eui/src/...)
// 4. generate & add EuiTokenObject
generator.then(() => {
const defsFilePath = path.resolve(baseDir, 'eui.d.ts');
fs.writeFileSync(
defsFilePath,
fs
.readFileSync(defsFilePath)
.toString()
.replace(/\/\/\/\W+<reference.*/g, '') // 1.
.replace(/import\("src\/(.*?)"\)/g, 'import("@elastic/eui/src/$1")') // 2.
.replace(
// start 3.
// find any singular `declare module { ... }` block
// {.*?^} matches anything until a } starts a new line (via `m` regex option, and `s` is dotall)
//
// aren't regex really bad for this? Yes.
// However, @babel/preset-typescript doesn't understand some syntax generated in eui.d.ts
// and the tooling around typescript's parsing & code generation is lacking and undocumented
// so... because this works with the guarantee that the newline-brace combination matches a module...
/declare module '(.*?)' {.*?^}/gms,
(module, moduleName) => {
// `moduleName` is the namespace for this ambient module
return module.replace(
// replace relative imports by attaching them to the module's namespace
/import\("([.]{1,2}\/.*?)"\)/g,
(importStatement, importPath) => {
let target = path.join(path.dirname(moduleName), importPath);
// if the target resolves to an orphaned index.ts file, remap to '@elastic/eui'
const filePath = target.replace('@elastic/eui', baseDir);
const filePathTs = `${filePath}.ts`;
const filePathTsx = `${filePath}.tsx`;
const filePathResolvedToIndex = path.join(filePath, 'index.ts');
if (
// fs.existsSync(filePath) === false && // target file doesn't exist
fs.existsSync(filePathTs) === false && // target file (.ts) doesn't exist
fs.existsSync(filePathTsx) === false && // target file (.tsx) doesn't exist
fs.existsSync(filePathResolvedToIndex) && // and it resolves to an index.ts
hasParentIndex(filePathResolvedToIndex) === false // does not get exported at a higher level
) {
target = '@elastic/eui';
}
return `import ("${target}")`;
}
);
}
) // end 3.
.replace(/$/, `\n\n${buildEuiTokensObject()}`) // 4.
);
});
/** For step 4 **/
// i18ntokens.json is generated as the first step in the build and can be relied upon here
function buildEuiTokensObject() {
// reduce over the tokens list as a few of the tokens are used multiple times and must be
// filtered down to a list
const { i18ndefs } = require('../i18ntokens.json').reduce(
({ i18ndefs, tokens }, def) => {
if (!tokens.has(def.token)) {
tokens.add(def.token);
i18ndefs.push(def);
}
return { i18ndefs, tokens };
},
{ i18ndefs: [], tokens: new Set() }
);
return `
declare module '@elastic/eui' {
export type EuiTokensObject = {
${i18ndefs.map(({ token }) => `"${token}": any;`).join('\n')}
}
}
`;
}