@mirta/rollup 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -10
- package/README.ru.md +99 -11
- package/dist/config-package.d.mts +18 -0
- package/dist/config-package.mjs +36 -0
- package/dist/config.d.mts +12 -0
- package/dist/config.mjs +24 -0
- package/dist/errors.mjs +185 -0
- package/dist/index.d.mts +39 -24
- package/dist/index.mjs +8 -6
- package/dist/package.mjs +1030 -0
- package/dist/runtime.mjs +75 -1350
- package/package.json +46 -34
- package/dist/configs/index.d.mts +0 -19
- package/dist/configs/index.mjs +0 -47
package/dist/runtime.mjs
CHANGED
|
@@ -1,1250 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import multi from '@rollup/plugin-multi-entry';
|
|
2
2
|
import nodeResolve from '@rollup/plugin-node-resolve';
|
|
3
|
-
import
|
|
3
|
+
import ts from 'rollup-plugin-typescript2';
|
|
4
4
|
import replace from '@rollup/plugin-replace';
|
|
5
|
-
import copy from 'rollup-plugin-copy';
|
|
6
|
-
import dts from 'rollup-plugin-dts';
|
|
7
|
-
import ts from 'typescript';
|
|
8
|
-
import nodePath from 'node:path';
|
|
9
|
-
import multi from '@rollup/plugin-multi-entry';
|
|
10
|
-
import ts$2 from 'rollup-plugin-typescript2';
|
|
11
|
-
import dotenv from '@dotenv-run/rollup';
|
|
12
5
|
import { getBabelOutputPlugin } from '@rollup/plugin-babel';
|
|
13
|
-
import {
|
|
6
|
+
import { loadEnvReplacements } from '@mirta/env-loader';
|
|
7
|
+
import { resolveMonorepoContextAsync } from '@mirta/workspace';
|
|
8
|
+
import { B as BuildError, d as del } from './errors.mjs';
|
|
14
9
|
import path from 'path';
|
|
15
10
|
import MagicString from 'magic-string';
|
|
16
|
-
import
|
|
17
|
-
import { findWorkspacePackages } from '@pnpm/workspace.find-packages';
|
|
18
|
-
import { readFileSync } from 'fs';
|
|
19
|
-
|
|
20
|
-
function del(options = {}) {
|
|
21
|
-
const { hook = 'buildStart', runOnce = false, targets = [], verbose = false, } = options;
|
|
22
|
-
let isDeleted = false;
|
|
23
|
-
return {
|
|
24
|
-
name: 'del',
|
|
25
|
-
[hook]: async () => {
|
|
26
|
-
if (runOnce && isDeleted)
|
|
27
|
-
return;
|
|
28
|
-
const paths = await deleteAsync(targets, options);
|
|
29
|
-
if (verbose || options.dryRun) {
|
|
30
|
-
const message = options.dryRun
|
|
31
|
-
? `Expected files and folders to be deleted: ${paths.length.toString()}`
|
|
32
|
-
: `Deleted files and folders: ${paths.length.toString()}`;
|
|
33
|
-
console.log(message);
|
|
34
|
-
if (paths.length)
|
|
35
|
-
paths.forEach((path) => {
|
|
36
|
-
console.log(path);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
isDeleted = true;
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Класс ошибки для обработки проблем с монорепозиторием,
|
|
46
|
-
* расширяющий стандартный Error.
|
|
47
|
-
*
|
|
48
|
-
* @since 0.3.5
|
|
49
|
-
*
|
|
50
|
-
**/
|
|
51
|
-
class WorkspaceError extends Error {
|
|
52
|
-
/**
|
|
53
|
-
* Приватный конструктор для создания экземпляра ошибки.
|
|
54
|
-
*
|
|
55
|
-
* @param message - Сообщение об ошибке.
|
|
56
|
-
* @param scope - Область, к которой относится ошибка (по умолчанию '@mirta/rollup').
|
|
57
|
-
*
|
|
58
|
-
**/
|
|
59
|
-
constructor(message, scope = '@mirta/rollup') {
|
|
60
|
-
super(`[${scope}] ${message}`);
|
|
61
|
-
this.name = 'WorkspaceError';
|
|
62
|
-
if ('captureStackTrace' in Error)
|
|
63
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
64
|
-
Error.captureStackTrace(this, WorkspaceError.get);
|
|
65
|
-
}
|
|
66
|
-
static codeMappings = {
|
|
67
|
-
noPackageName: (packagePath) => `Package with path "${packagePath}" missing required 'name' field in package.json`,
|
|
68
|
-
noWorkspaces: () => 'No workspaces configured in root package.json',
|
|
69
|
-
};
|
|
70
|
-
static get(code, ...args) {
|
|
71
|
-
const messageFn = this.codeMappings[code];
|
|
72
|
-
const message = messageFn(...args);
|
|
73
|
-
return new WorkspaceError(message);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Класс ошибки для обработки проблем с менеджерами пакетов,
|
|
78
|
-
* расширяющий стандартный Error.
|
|
79
|
-
*
|
|
80
|
-
* @since 0.3.5
|
|
81
|
-
*
|
|
82
|
-
**/
|
|
83
|
-
class PackageManagerError extends Error {
|
|
84
|
-
/**
|
|
85
|
-
* Приватный конструктор для создания экземпляра ошибки.
|
|
86
|
-
*
|
|
87
|
-
* @param message - Сообщение об ошибке.
|
|
88
|
-
* @param scope - Область, к которой относится ошибка (по умолчанию '@mirta/rollup').
|
|
89
|
-
*
|
|
90
|
-
**/
|
|
91
|
-
constructor(message, scope = '@mirta/rollup') {
|
|
92
|
-
super(`[${scope}] ${message}`);
|
|
93
|
-
this.name = 'PackageManagerError';
|
|
94
|
-
if ('captureStackTrace' in Error)
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
96
|
-
Error.captureStackTrace(this, PackageManagerError.get);
|
|
97
|
-
}
|
|
98
|
-
static codeMappings = {
|
|
99
|
-
pnpmOnly: () => 'PNPM is required for building. Other package managers are not supported at this time',
|
|
100
|
-
};
|
|
101
|
-
static get(code, ...args) {
|
|
102
|
-
const messageFn = this.codeMappings[code];
|
|
103
|
-
const message = messageFn(...args);
|
|
104
|
-
return new PackageManagerError(message);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Класс ошибки для обработки проблем с файлами, расширяющий стандартный Error.
|
|
109
|
-
*
|
|
110
|
-
* @since 0.3.5
|
|
111
|
-
*
|
|
112
|
-
**/
|
|
113
|
-
class FileError extends Error {
|
|
114
|
-
/**
|
|
115
|
-
* Приватный конструктор для создания экземпляра ошибки.
|
|
116
|
-
*
|
|
117
|
-
* @param message - Сообщение об ошибке.
|
|
118
|
-
* @param scope - Область, к которой относится ошибка (по умолчанию '@mirta/rollup').
|
|
119
|
-
*
|
|
120
|
-
**/
|
|
121
|
-
constructor(message, scope = '@mirta/rollup') {
|
|
122
|
-
super(`[${scope}] ${message}`);
|
|
123
|
-
this.name = 'FileError';
|
|
124
|
-
if ('captureStackTrace' in Error)
|
|
125
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
126
|
-
Error.captureStackTrace(this, FileError.get);
|
|
127
|
-
}
|
|
128
|
-
/** Карта кодов ошибок с соответствующими сообщениями. */
|
|
129
|
-
static codeMappings = {
|
|
130
|
-
/** Ошибка, возникающая при отсутствии файла в указанном расположении. */
|
|
131
|
-
notFound: (filePath) => `File not found: "${filePath}"`,
|
|
132
|
-
/** Ошибка, возникающая при отсутствии доступа к указанному файлу. */
|
|
133
|
-
noAccess: (filePath) => `No access to file "${filePath}"`,
|
|
134
|
-
/** Ошибка, возникающая при невалидном JSON в файле. */
|
|
135
|
-
invalidJson: (filePath, message) => `Invalid JSON in file "${filePath}": ${message}`,
|
|
136
|
-
/** Ошибка парсинга, возникающая по неуточненным причинам. */
|
|
137
|
-
failedToParse: (filePath, message) => `Failed to parse "${nodePath.basename(filePath)}": ${message}`,
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* Статический метод для получения экземпляра ошибки по коду.
|
|
141
|
-
*
|
|
142
|
-
* @template T - Тип ключа из codeMappings
|
|
143
|
-
* @param code - Код ошибки
|
|
144
|
-
* @param args - Аргументы для формирования сообщения
|
|
145
|
-
* @returns Экземпляр {@link FileError}
|
|
146
|
-
*
|
|
147
|
-
**/
|
|
148
|
-
static get(code, ...args) {
|
|
149
|
-
const messageFn = this.codeMappings[code];
|
|
150
|
-
const message = messageFn(...args);
|
|
151
|
-
return new FileError(message);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Класс ошибки сборки под NPM, расширяющий стандартный Error.
|
|
156
|
-
*
|
|
157
|
-
* @since 0.3.5
|
|
158
|
-
*
|
|
159
|
-
**/
|
|
160
|
-
class NpmBuildError extends Error {
|
|
161
|
-
/**
|
|
162
|
-
* Приватный конструктор для создания экземпляра ошибки.
|
|
163
|
-
*
|
|
164
|
-
* @param message - Сообщение об ошибке
|
|
165
|
-
* @param scope - Область действия ошибки (по умолчанию '@mirta/rollup NPM')
|
|
166
|
-
*
|
|
167
|
-
**/
|
|
168
|
-
constructor(message, scope = '@mirta/rollup NPM') {
|
|
169
|
-
super(`[${scope}] ${message}`);
|
|
170
|
-
this.name = 'NpmBuildError';
|
|
171
|
-
if ('captureStackTrace' in Error)
|
|
172
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
173
|
-
Error.captureStackTrace(this, NpmBuildError.get);
|
|
174
|
-
}
|
|
175
|
-
/** Карта кодов ошибок с соответствующими сообщениями. */
|
|
176
|
-
static codeMappings = {
|
|
177
|
-
/** Ошибка, возникающая когда конфигурация input-файлов Rollup пуста. */
|
|
178
|
-
inputEmpty: () => 'Rollup Config: Input configuration cannot be empty',
|
|
179
|
-
/** Ошибка, возникающая когда input-файл не начинается с требуемого префикса. */
|
|
180
|
-
inputPathRequiresPrefix: (input, prefix) => `Rollup Config: Input path "${input}" must start with required prefix "${prefix}"`,
|
|
181
|
-
/** Ошибка, возникающая когда input-файл имеет недопустимое расширение. */
|
|
182
|
-
inputFileExtensionNotSupported: (input) => `Rollup Config: Unsupported input "${input}". Please use valid JS or TS file extension`,
|
|
183
|
-
/** Ошибка, возникающая из-за дублирования выходного файла несколькими input-файлами. */
|
|
184
|
-
inputGeneratesDuplicateOutput: (outputFile) => `Rollup Config: Duplicate output file "${outputFile}" produced by multiple inputs. Ensure each input maps to a unique export path`,
|
|
185
|
-
/** Ошибка, возникающая когда input-файл не ассоциирован с экспортом в package.json. */
|
|
186
|
-
inputHasNoExport: (input, entry) => `Rollup Config: The input file "${input}" is not associated with corresponding export "${entry}"`,
|
|
187
|
-
/** Ошибка, возникающая при отсутствии экспорта в package.json. */
|
|
188
|
-
exportEmpty: () => 'Package Config: Missing export configuration. Please define the "exports" field',
|
|
189
|
-
/** Ошибка, возникающая при экспорте типов без указания default-импорта. */
|
|
190
|
-
exportTypesOnly: (types) => `Package Config: Export contains only types "${types}" without specifying a default import in package.json`,
|
|
191
|
-
/** Ошибка, возникающая при отсутствии соответствия с input-файлом конфигурации Rollup. */
|
|
192
|
-
exportHasNoInput: (entry) => `Package Config: Export "${entry}" has no corresponding input file in Rollup configuration`,
|
|
193
|
-
/** Ошибка, возникающая при использовании массива в качестве значения exports. */
|
|
194
|
-
exportDisallowArrayType: () => 'Package Config: The field "exports" must be either a string or an object, but found an array',
|
|
195
|
-
/** Ошибка, возникающая при отсутствии точки в начале пути экспорта. */
|
|
196
|
-
exportMustStartWithDot: (key) => `Package Config: Invalid export path "${key}", it must start with "."`,
|
|
197
|
-
};
|
|
198
|
-
/**
|
|
199
|
-
* Статический метод для получения экземпляра ошибки по коду.
|
|
200
|
-
*
|
|
201
|
-
* @template T - Тип ключа из codeMappings
|
|
202
|
-
* @param code - Код ошибки
|
|
203
|
-
* @param args - Аргументы для формирования сообщения
|
|
204
|
-
* @returns Экземпляр {@link NpmBuildError}
|
|
205
|
-
*
|
|
206
|
-
**/
|
|
207
|
-
static get(code, ...args) {
|
|
208
|
-
const messageFn = this.codeMappings[code];
|
|
209
|
-
const message = messageFn(...args);
|
|
210
|
-
return new NpmBuildError(message);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Класс ошибки трансформации AST, расширяющий стандартный Error.
|
|
215
|
-
*
|
|
216
|
-
* @since 0.3.5
|
|
217
|
-
*
|
|
218
|
-
**/
|
|
219
|
-
class AstTransformError extends Error {
|
|
220
|
-
/**
|
|
221
|
-
* Приватный конструктор для создания экземпляра ошибки.
|
|
222
|
-
*
|
|
223
|
-
* @param message - Сообщение об ошибке
|
|
224
|
-
* @param scope - Область действия ошибки (по умолчанию '@mirta/rollup AST')
|
|
225
|
-
*
|
|
226
|
-
**/
|
|
227
|
-
constructor(message, scope = '@mirta/rollup AST') {
|
|
228
|
-
super(`[${scope}] ${message}`);
|
|
229
|
-
this.name = 'AstTransformError';
|
|
230
|
-
if ('captureStackTrace' in Error)
|
|
231
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
232
|
-
Error.captureStackTrace(this, AstTransformError.get);
|
|
233
|
-
}
|
|
234
|
-
/** Карта кодов ошибок с соответствующими сообщениями. */
|
|
235
|
-
static codeMappings = {
|
|
236
|
-
/** Ошибка, возникающая при отсутствии root-файлов в проекте. */
|
|
237
|
-
noRootFilesInProject: () => 'No root files found in the project. Check your TypeScript configuration (tsconfig.json)',
|
|
238
|
-
/** Ошибка, возникающая при отсутствии модуля для указанного спецификатора. */
|
|
239
|
-
moduleNotFound: (modulePath, sourceFileName) => `Module "${modulePath}" not found in "${sourceFileName}"`,
|
|
240
|
-
/** Ошибка, возникающая когда modulePath содержит недопустимые символы. */
|
|
241
|
-
invalidPathFormat: (modulePath) => `Invalid format of module path: "${modulePath}"`,
|
|
242
|
-
};
|
|
243
|
-
/**
|
|
244
|
-
* Статический метод для получения экземпляра ошибки по коду.
|
|
245
|
-
*
|
|
246
|
-
* @template T - Тип ключа из codeMappings
|
|
247
|
-
* @param code - Код ошибки
|
|
248
|
-
* @param args - Аргументы для формирования сообщения
|
|
249
|
-
* @returns Экземпляр {@link AstTransformError}
|
|
250
|
-
*
|
|
251
|
-
**/
|
|
252
|
-
static get(code, ...args) {
|
|
253
|
-
const messageFn = this.codeMappings[code];
|
|
254
|
-
const message = messageFn(...args);
|
|
255
|
-
return new AstTransformError(message);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Удаляет расширение файла `.ts`, `.d.ts` или `.js`.
|
|
261
|
-
*
|
|
262
|
-
* @param fileName - Полное имя файла
|
|
263
|
-
* @returns Имя файла без расширения
|
|
264
|
-
*
|
|
265
|
-
* @since 0.3.5
|
|
266
|
-
*
|
|
267
|
-
**/
|
|
268
|
-
const removeFileExtension = (fileName) => fileName.replace(/\.(?:d\.)?(?:[cm]?[tj]s)$/i, '');
|
|
269
|
-
/**
|
|
270
|
-
* Находит общий префикс двух путей.
|
|
271
|
-
*
|
|
272
|
-
* @param a - Первый путь
|
|
273
|
-
* @param b - Второй путь
|
|
274
|
-
* @returns Общий префикс
|
|
275
|
-
*
|
|
276
|
-
* @since 0.3.5
|
|
277
|
-
*
|
|
278
|
-
**/
|
|
279
|
-
function getCommonPrefix(a, b) {
|
|
280
|
-
const aParts = nodePath.normalize(a).split(nodePath.sep);
|
|
281
|
-
const bParts = nodePath.normalize(b).split(nodePath.sep);
|
|
282
|
-
const minLength = Math.min(aParts.length, bParts.length);
|
|
283
|
-
let i = 0;
|
|
284
|
-
while (i < minLength && aParts[i] === bParts[i]) {
|
|
285
|
-
i++;
|
|
286
|
-
}
|
|
287
|
-
return aParts.slice(0, i).join(nodePath.sep);
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Проверяет, является ли указанный файл частью проекта.
|
|
291
|
-
*
|
|
292
|
-
* Файл считается проектным, если он:
|
|
293
|
-
* - Не находится в директории `node_modules`;
|
|
294
|
-
* - Расположен внутри указанной корневой директории.
|
|
295
|
-
*
|
|
296
|
-
* @param fileName - Полный путь к проверяемому файлу.
|
|
297
|
-
* @param rootDir - Корневая директория проекта.
|
|
298
|
-
* @returns `true`, если файл принадлежит проекту, иначе false.
|
|
299
|
-
*
|
|
300
|
-
* @since 0.3.5
|
|
301
|
-
*
|
|
302
|
-
**/
|
|
303
|
-
function isProjectFile(fileName, rootDir) {
|
|
304
|
-
if (fileName.includes('node_modules'))
|
|
305
|
-
return false;
|
|
306
|
-
if (nodePath.relative(rootDir, fileName).startsWith('..'))
|
|
307
|
-
return false;
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Вычисляет относительный путь от директории исходного файла к целевому файлу,
|
|
312
|
-
* добавляет имя выходного файла и нормализует путь для совместимости с POSIX-системами.
|
|
313
|
-
*
|
|
314
|
-
* @param sourceFilePath - Полный путь к исходному файлу.
|
|
315
|
-
* @param targetFilePath - Полный путь к целевому файлу.
|
|
316
|
-
* @param outputName - Имя выходного файла, который будет добавлен к относительному пути.
|
|
317
|
-
* @returns Нормализованный относительный путь с именем выходного файла.
|
|
318
|
-
*
|
|
319
|
-
* @example
|
|
320
|
-
*
|
|
321
|
-
* ```ts
|
|
322
|
-
* const sourceFilePath = '/project/src/index.ts'
|
|
323
|
-
* const targetFilePath = '/project/src/utils/index.ts'
|
|
324
|
-
* const outputName = 'utils.d.ts'
|
|
325
|
-
*
|
|
326
|
-
* getRelativeOutputPath(sourceFilePath, targetFilePath, outputName) // Возвращает './utils.d.ts'
|
|
327
|
-
*
|
|
328
|
-
* ```
|
|
329
|
-
* @since 0.3.5
|
|
330
|
-
*
|
|
331
|
-
**/
|
|
332
|
-
function getRelativeOutputPath(sourceFilePath, targetFilePath, outputFileName) {
|
|
333
|
-
// Шаг 1: Получаем директорию исходного файла.
|
|
334
|
-
const sourceDir = nodePath
|
|
335
|
-
.dirname(sourceFilePath);
|
|
336
|
-
// Шаг 2: Вычисляем относительный путь от директории исходного файла до целевого файла.
|
|
337
|
-
const relativeDir = nodePath
|
|
338
|
-
.dirname(nodePath.relative(sourceDir, targetFilePath));
|
|
339
|
-
// Шаг 3: Объединяем относительную директорию с именем выходного файла.
|
|
340
|
-
let relativePath = nodePath
|
|
341
|
-
.join(relativeDir, outputFileName);
|
|
342
|
-
relativePath = relativePath
|
|
343
|
-
.split(nodePath.sep)
|
|
344
|
-
.join(nodePath.posix.sep);
|
|
345
|
-
// Шаг 4: Гарантируем, что путь является относительным.
|
|
346
|
-
if (!relativePath.startsWith('.'))
|
|
347
|
-
relativePath = `./${relativePath}`;
|
|
348
|
-
return relativePath;
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Определяет корневую директорию файла на основе конфигурации.
|
|
352
|
-
*
|
|
353
|
-
* @param context - Базовый контекст
|
|
354
|
-
* @param sourceFile - Обрабатываемый файл
|
|
355
|
-
* @returns Путь к корню проекта
|
|
356
|
-
* @throws Ошибка, если файлы не найдены
|
|
357
|
-
*
|
|
358
|
-
* @since 0.3.5
|
|
359
|
-
*
|
|
360
|
-
**/
|
|
361
|
-
function getRootDir(context, sourceFile) {
|
|
362
|
-
const { compilerOptions, program } = context;
|
|
363
|
-
// Если rootDirs указаны — ищем, к какому корню относится файл.
|
|
364
|
-
if (compilerOptions.rootDirs?.length) {
|
|
365
|
-
const sortedRoots = [...compilerOptions.rootDirs]
|
|
366
|
-
.sort((a, b) => b.length - a.length);
|
|
367
|
-
const normalizedFile = nodePath.resolve(sourceFile.fileName);
|
|
368
|
-
for (const rootDir of sortedRoots) {
|
|
369
|
-
const normalizedRoot = nodePath.resolve(rootDir);
|
|
370
|
-
if (normalizedFile.startsWith(normalizedRoot + nodePath.sep))
|
|
371
|
-
return rootDir;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// Если rootDir указан — используем его.
|
|
375
|
-
if (compilerOptions.rootDir)
|
|
376
|
-
return compilerOptions.rootDir;
|
|
377
|
-
// Иначе находим общий корень для всех файлов.
|
|
378
|
-
const fileNames = program.getRootFileNames();
|
|
379
|
-
if (!fileNames.length)
|
|
380
|
-
throw AstTransformError.get('noRootFilesInProject');
|
|
381
|
-
let commonPrefix = nodePath.dirname(fileNames[0]);
|
|
382
|
-
for (const fileName of fileNames.slice(1)) {
|
|
383
|
-
commonPrefix = getCommonPrefix(commonPrefix, fileName);
|
|
384
|
-
}
|
|
385
|
-
return commonPrefix;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Перечисление типов индексных файлов.
|
|
390
|
-
* Используется для классификации модулей в зависимости от их отношения к файлам `index`.
|
|
391
|
-
*
|
|
392
|
-
* @since 0.3.5
|
|
393
|
-
*
|
|
394
|
-
**/
|
|
395
|
-
var IndexType;
|
|
396
|
-
(function (IndexType) {
|
|
397
|
-
/** Не индексный файл. */
|
|
398
|
-
IndexType["NonIndex"] = "non-index";
|
|
399
|
-
/** Явный индекс (например, `import './index'`). */
|
|
400
|
-
IndexType["Explicit"] = "explicit";
|
|
401
|
-
/** Неявный индекс (например, `import './dir'`). */
|
|
402
|
-
IndexType["Implicit"] = "implicit";
|
|
403
|
-
/** Индекс внутри пакета (например, import 'package/index'). */
|
|
404
|
-
IndexType["ImplicitPackage"] = "implicit-package";
|
|
405
|
-
})(IndexType || (IndexType = {}));
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Проверяет безопасность пути модуля, блокируя подозрительные символы.
|
|
409
|
-
*
|
|
410
|
-
* Эта функция проверяет, не содержит ли путь модуля недопустимых символов,
|
|
411
|
-
* таких как `:` (используется в URL) или `~` (ссылка на домашнюю директорию),
|
|
412
|
-
* которые могут представлять риск безопасности.
|
|
413
|
-
*
|
|
414
|
-
* @param path - Путь модуля для проверки.
|
|
415
|
-
*
|
|
416
|
-
* @throws {AstTransformError} Если обнаружены запрещённые символы.
|
|
417
|
-
*
|
|
418
|
-
* @example
|
|
419
|
-
* ```ts
|
|
420
|
-
* assertPathIsValid('./utils') // OK
|
|
421
|
-
* assertPathIsValid('http://malicious.com') // Выбросит ошибку
|
|
422
|
-
*
|
|
423
|
-
* ```
|
|
424
|
-
* @since 0.3.5
|
|
425
|
-
*
|
|
426
|
-
**/
|
|
427
|
-
function assertPathIsValid(path) {
|
|
428
|
-
if (path.includes(':') || path.includes('~'))
|
|
429
|
-
throw AstTransformError.get('invalidPathFormat', path);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Создает кэш файлов по имени без расширения.
|
|
434
|
-
*
|
|
435
|
-
* @param program - Программа TypeScript
|
|
436
|
-
* @returns Карта: ключ - имя файла без расширения, значение - объект файла
|
|
437
|
-
*
|
|
438
|
-
* @since 0.3.5
|
|
439
|
-
*
|
|
440
|
-
**/
|
|
441
|
-
function createSourceFilesCache(program) {
|
|
442
|
-
return new Map(program.getSourceFiles().map(sourceFile => [
|
|
443
|
-
removeFileExtension(sourceFile.fileName),
|
|
444
|
-
sourceFile,
|
|
445
|
-
]));
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Получает файл из программы или создает его при необходимости.
|
|
449
|
-
* Использует кэш для повышения производительности при повторных запросах.
|
|
450
|
-
*
|
|
451
|
-
* @param context - Контекст трансформера, содержащий ссылку на программу и кэш файлов.
|
|
452
|
-
* @param fileName - Полный путь к файлу, который необходимо найти или создать.
|
|
453
|
-
* @returns Экземпляр {@link ts.SourceFile} для существующего или созданного файла.
|
|
454
|
-
*
|
|
455
|
-
* @since 0.3.5
|
|
456
|
-
*
|
|
457
|
-
**/
|
|
458
|
-
function resolveSourceFile(context, fileName) {
|
|
459
|
-
const { program, compilerOptions } = context;
|
|
460
|
-
let result = program.getSourceFile(fileName);
|
|
461
|
-
if (result)
|
|
462
|
-
return result;
|
|
463
|
-
// Если кэш уже создан, используем его. Иначе создаем новый.
|
|
464
|
-
const sourceFilesCache = context.sourceFilesCache ??= createSourceFilesCache(program);
|
|
465
|
-
const normalizedFileName = removeFileExtension(fileName);
|
|
466
|
-
// Попытка найти файл в кэше.
|
|
467
|
-
result = sourceFilesCache.get(normalizedFileName);
|
|
468
|
-
if (!result) {
|
|
469
|
-
result = ts.createSourceFile(fileName, '', compilerOptions.target ?? ts.ScriptTarget.ESNext, false);
|
|
470
|
-
sourceFilesCache.set(normalizedFileName, result);
|
|
471
|
-
}
|
|
472
|
-
return result;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Анализирует путь модуля и определяет его тип (явный, неявный, пакетный или обычный).
|
|
477
|
-
* Собирает метаданные о модуле для дальнейшей обработки путей импорта.
|
|
478
|
-
*
|
|
479
|
-
* @param path - Путь модуля из исходного кода (например, './dir' или 'package/index').
|
|
480
|
-
* @param resolvedModule - Объект, содержащий информацию о разрешённом модуле из TypeScript.
|
|
481
|
-
* @returns Объект с детализацией пути, включая тип индекса, имя файла, расширение и директорию.
|
|
482
|
-
*
|
|
483
|
-
* @since 0.3.5
|
|
484
|
-
*
|
|
485
|
-
**/
|
|
486
|
-
function getPathDetails(path, resolvedModule) {
|
|
487
|
-
const { resolvedFileName, packageId } = resolvedModule;
|
|
488
|
-
const implicitPackagePath = packageId?.subModuleName;
|
|
489
|
-
// Указывает, является ли модуль частью пакета (например, 'package/index').
|
|
490
|
-
const isPackage = !!implicitPackagePath;
|
|
491
|
-
// Базовые данные разрешённого файла
|
|
492
|
-
const resolvedBaseName = nodePath.basename(isPackage
|
|
493
|
-
? implicitPackagePath
|
|
494
|
-
: resolvedFileName);
|
|
495
|
-
const resolvedBaseNameNoExtension = resolvedBaseName
|
|
496
|
-
? removeFileExtension(resolvedBaseName)
|
|
497
|
-
: undefined;
|
|
498
|
-
// const resolvedExtension = resolvedBaseName
|
|
499
|
-
// ? nodePath.extname(resolvedFileName)
|
|
500
|
-
// : undefined
|
|
501
|
-
// Базовые данные оригинального модуля
|
|
502
|
-
let baseName = isPackage
|
|
503
|
-
? undefined
|
|
504
|
-
: nodePath.basename(path);
|
|
505
|
-
let baseNameNoExtension = baseName
|
|
506
|
-
? removeFileExtension(baseName)
|
|
507
|
-
: undefined;
|
|
508
|
-
let extName = baseName
|
|
509
|
-
? nodePath.extname(path)
|
|
510
|
-
: undefined;
|
|
511
|
-
// Если имя оригинального модуля совпадает с разрешённым, убираем расширение.
|
|
512
|
-
if (resolvedBaseNameNoExtension
|
|
513
|
-
&& baseName
|
|
514
|
-
&& resolvedBaseNameNoExtension === baseName) {
|
|
515
|
-
baseNameNoExtension = baseName;
|
|
516
|
-
extName = undefined;
|
|
517
|
-
}
|
|
518
|
-
let indexType;
|
|
519
|
-
if (isPackage) {
|
|
520
|
-
// Модуль внутри пакета (например, import 'package/index').
|
|
521
|
-
indexType = IndexType.ImplicitPackage;
|
|
522
|
-
}
|
|
523
|
-
else if (baseNameNoExtension === 'index' && resolvedBaseNameNoExtension === 'index') {
|
|
524
|
-
// Явный импорт файла index (например, import './dir/index').
|
|
525
|
-
indexType = IndexType.Explicit;
|
|
526
|
-
}
|
|
527
|
-
else if (baseNameNoExtension !== 'index' && resolvedBaseNameNoExtension === 'index') {
|
|
528
|
-
// Неявный импорт index (например, import './dir').
|
|
529
|
-
indexType = IndexType.Implicit;
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
// Обычный файл, не связанный с index.
|
|
533
|
-
indexType = IndexType.NonIndex;
|
|
534
|
-
}
|
|
535
|
-
// Для неявных индексов убирает лишние поля оригинального
|
|
536
|
-
// модуля, чтобы не отображать index и расширения.
|
|
537
|
-
//
|
|
538
|
-
if (indexType === IndexType.Implicit) {
|
|
539
|
-
baseName = undefined;
|
|
540
|
-
baseNameNoExtension = undefined;
|
|
541
|
-
extName = undefined;
|
|
542
|
-
}
|
|
543
|
-
return {
|
|
544
|
-
// baseName,
|
|
545
|
-
// baseNameNoExtension,
|
|
546
|
-
extName,
|
|
547
|
-
// resolvedBaseName,
|
|
548
|
-
resolvedBaseNameNoExtension,
|
|
549
|
-
// resolvedExtension,
|
|
550
|
-
// resolvedDir: isPackage
|
|
551
|
-
// ? removeSuffix(resolvedFileName, `/${implicitPackageIndex}`)
|
|
552
|
-
// : nodePath.dirname(resolvedFileName),
|
|
553
|
-
indexType,
|
|
554
|
-
// implicitPackagePath,
|
|
555
|
-
// resolvedFileName,
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Генерирует новый относительный путь для импорта модуля, исключая расширения и индексные файлы.
|
|
560
|
-
* Проверяет безопасность пути и убеждается, что файл находится внутри корня проекта.
|
|
561
|
-
*
|
|
562
|
-
* @param context - Контекст трансформера, содержащий информацию о текущем файле и конфигурации.
|
|
563
|
-
* @param oldPath - Путь модуля из исходного импорта (например, './utils/index').
|
|
564
|
-
* @returns Относительный путь без расширения и индекса, или `undefined`, если модуль недопустим.
|
|
565
|
-
*
|
|
566
|
-
* @since 0.3.5
|
|
567
|
-
*
|
|
568
|
-
**/
|
|
569
|
-
function resolveNewModulePath(context, oldPath) {
|
|
570
|
-
assertPathIsValid(oldPath);
|
|
571
|
-
const {
|
|
572
|
-
// Текущий обрабатываемый файл.
|
|
573
|
-
sourceFile: currentSourceFile,
|
|
574
|
-
// Актуальная конфигурация TypeScript.
|
|
575
|
-
compilerOptions, } = context;
|
|
576
|
-
// Получаем модуль импортированного файла.
|
|
577
|
-
//
|
|
578
|
-
const { resolvedModule: importedModule } = ts.resolveModuleName(oldPath, currentSourceFile.fileName, compilerOptions, ts.sys);
|
|
579
|
-
if (!importedModule)
|
|
580
|
-
throw AstTransformError.get('moduleNotFound', oldPath, currentSourceFile.fileName);
|
|
581
|
-
if (!isProjectFile(importedModule.resolvedFileName, context.rootDir))
|
|
582
|
-
return null;
|
|
583
|
-
// Получает детали пути импортированного модуля.
|
|
584
|
-
const pathDetails = getPathDetails(oldPath, importedModule);
|
|
585
|
-
const { indexType, resolvedBaseNameNoExtension, extName } = pathDetails;
|
|
586
|
-
let outputBaseName = resolvedBaseNameNoExtension ?? '';
|
|
587
|
-
if (indexType === IndexType.Implicit && outputBaseName.endsWith('index'))
|
|
588
|
-
outputBaseName = outputBaseName.slice(0, -5);
|
|
589
|
-
if (outputBaseName && extName)
|
|
590
|
-
outputBaseName = `${outputBaseName}${extName}`;
|
|
591
|
-
// Получает исходный файл импортированного модуля.
|
|
592
|
-
const importedSourceFile = resolveSourceFile(context, importedModule.resolvedFileName);
|
|
593
|
-
const newPath = getRelativeOutputPath(currentSourceFile.fileName, importedSourceFile.fileName, outputBaseName);
|
|
594
|
-
return newPath;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
const aliasPattern = /^[@#]/;
|
|
598
|
-
/**
|
|
599
|
-
* Обновляет спецификатор модуля в узле импорта или экспорта.
|
|
600
|
-
*
|
|
601
|
-
* @param factory - Фабрика для создания новых узлов AST.
|
|
602
|
-
* @param node - Узел импорта или экспорта.
|
|
603
|
-
* @param newModuleSpecifier - Новый относительный путь.
|
|
604
|
-
* @returns Обновлённый узел.
|
|
605
|
-
*
|
|
606
|
-
* @since 0.3.5
|
|
607
|
-
*
|
|
608
|
-
**/
|
|
609
|
-
function updateModuleSpecifier(factory, node, newModuleSpecifier) {
|
|
610
|
-
if (ts.isImportDeclaration(node)) {
|
|
611
|
-
return factory.updateImportDeclaration(node, node.modifiers, node.importClause, factory.createStringLiteral(newModuleSpecifier), node.attributes);
|
|
612
|
-
}
|
|
613
|
-
else if (ts.isExportDeclaration(node)) {
|
|
614
|
-
return factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, node.exportClause, factory.createStringLiteral(newModuleSpecifier), node.attributes);
|
|
615
|
-
}
|
|
616
|
-
return node;
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Рекурсивно обходит дочерние узлы AST с текущим визитором.
|
|
620
|
-
*/
|
|
621
|
-
function visitChildren(context, node) {
|
|
622
|
-
return ts.visitEachChild(node, context.getVisitor(), context.transformationContext);
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Визитор для обработки узлов AST, связанных с импортами.
|
|
626
|
-
*
|
|
627
|
-
* Обновляет пути модулей, начинающиеся с `#` или `@`, в файлах объявлений TypeScript.
|
|
628
|
-
*
|
|
629
|
-
* @param this - Контекст трансформера, предоставляющий инструменты для работы с AST.
|
|
630
|
-
* @param node - Текущий узел AST, который проверяется и, при необходимости, обновляется.
|
|
631
|
-
* @returns Обновленный узел AST или результат рекурсивного обхода дочерних узлов.
|
|
632
|
-
*
|
|
633
|
-
* @example
|
|
634
|
-
* ```ts
|
|
635
|
-
* // Исходный узел импорта:
|
|
636
|
-
* import { foo } from '#utils/index'
|
|
637
|
-
*
|
|
638
|
-
* // После обработки:
|
|
639
|
-
* import { foo } from './utils'
|
|
640
|
-
*
|
|
641
|
-
* ```
|
|
642
|
-
* @since 0.3.5
|
|
643
|
-
*
|
|
644
|
-
**/
|
|
645
|
-
function nodeVisitor(node) {
|
|
646
|
-
if (!ts.isImportDeclaration(node) && !ts.isExportDeclaration(node))
|
|
647
|
-
return visitChildren(this, node);
|
|
648
|
-
const moduleSpecifier = node.moduleSpecifier;
|
|
649
|
-
if (!moduleSpecifier || !ts.isStringLiteral(moduleSpecifier))
|
|
650
|
-
return visitChildren(this, node);
|
|
651
|
-
// Извлекаем путь импорта/экспорта (указывается в кавычках после from).
|
|
652
|
-
const { text: oldPath } = moduleSpecifier;
|
|
653
|
-
// Проверяем, относится ли спецификатор к алиасу проекта (`#`) или кастомному алиасу (`@`).
|
|
654
|
-
// Такие спецификаторы требуют пересчета в относительный путь.
|
|
655
|
-
//
|
|
656
|
-
if (!aliasPattern.test(oldPath))
|
|
657
|
-
return visitChildren(this, node);
|
|
658
|
-
const cachedNewPath = this.pathsCache.get(oldPath);
|
|
659
|
-
const newPath = cachedNewPath === undefined
|
|
660
|
-
? resolveNewModulePath(this, oldPath)
|
|
661
|
-
: cachedNewPath;
|
|
662
|
-
if (cachedNewPath === undefined)
|
|
663
|
-
this.pathsCache.set(oldPath, newPath);
|
|
664
|
-
if (!newPath)
|
|
665
|
-
return node;
|
|
666
|
-
// Обновляем узел импорта новым значением пути.
|
|
667
|
-
// Если путь не удалось преобразовать, возвращаем исходный узел.
|
|
668
|
-
//
|
|
669
|
-
return updateModuleSpecifier(this.factory, node, newPath);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* Фабрика трансформеров для Rollup, предназначенная для обработки файлов объявлений TypeScript (`.d.ts`).
|
|
674
|
-
* Создаёт трансформер, который модифицирует пути импортов в декларациях, исключая расширения и индексные файлы.
|
|
675
|
-
*
|
|
676
|
-
* @param program - Экземпляр программы TypeScript, предоставляющий доступ к всем файлам проекта.
|
|
677
|
-
* @returns Фабрика трансформеров, применяющаяся ко всем `.d.ts`-файлам.
|
|
678
|
-
*
|
|
679
|
-
* @since 0.3.5
|
|
680
|
-
*
|
|
681
|
-
**/
|
|
682
|
-
function dtsAliasTransformerFactory(program) {
|
|
683
|
-
return (context) => {
|
|
684
|
-
const compilerOptions = program.getCompilerOptions();
|
|
685
|
-
/**
|
|
686
|
-
* Базовый контекст для работы с AST-трансформером.
|
|
687
|
-
* Содержит общие параметры и ссылки на программу TypeScript.
|
|
688
|
-
*
|
|
689
|
-
**/
|
|
690
|
-
const visitorContextBase = {
|
|
691
|
-
compilerOptions,
|
|
692
|
-
program,
|
|
693
|
-
factory: context.factory,
|
|
694
|
-
transformationContext: context,
|
|
695
|
-
};
|
|
696
|
-
return (sourceFile) => {
|
|
697
|
-
/**
|
|
698
|
-
* Обработка происходит только для файлов объявлений (`.d.ts`).
|
|
699
|
-
* Обычные файлы (`*.ts`) игнорируются.
|
|
700
|
-
*
|
|
701
|
-
**/
|
|
702
|
-
if (!sourceFile.isDeclarationFile)
|
|
703
|
-
return sourceFile;
|
|
704
|
-
/**
|
|
705
|
-
* Контекст визитора, расширенный информацией о текущем файле и корне проекта.
|
|
706
|
-
* Используется для передачи данных в `nodeVisitor`.
|
|
707
|
-
*
|
|
708
|
-
**/
|
|
709
|
-
const visitorContext = {
|
|
710
|
-
...visitorContextBase,
|
|
711
|
-
sourceFile,
|
|
712
|
-
rootDir: getRootDir(visitorContextBase, sourceFile),
|
|
713
|
-
pathsCache: new Map(),
|
|
714
|
-
getVisitor() {
|
|
715
|
-
return nodeVisitor.bind(this);
|
|
716
|
-
},
|
|
717
|
-
};
|
|
718
|
-
/**
|
|
719
|
-
* Рекурсивный обход AST с применением визитора.
|
|
720
|
-
* Обрабатывает все узлы файла, начиная с корня.
|
|
721
|
-
*
|
|
722
|
-
**/
|
|
723
|
-
return ts.visitEachChild(sourceFile, visitorContext.getVisitor(), context);
|
|
724
|
-
};
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Экспортируемый трансформер для Rollup, предназначенный для обработки файлов объявлений TypeScript (`.d.ts`).
|
|
730
|
-
* Используется в фазе `afterDeclarations`, чтобы корректировать пути импортов после генерации типов.
|
|
731
|
-
*
|
|
732
|
-
* @returns Объект трансформера, который может быть передан в конфигурацию Rollup.
|
|
733
|
-
*
|
|
734
|
-
* @example
|
|
735
|
-
* Пример использования в конфигурации Rollup:
|
|
736
|
-
* ```ts
|
|
737
|
-
* import { dtsAlias } from '#ast/dts-alias';
|
|
738
|
-
*
|
|
739
|
-
* export default {
|
|
740
|
-
* plugins: [
|
|
741
|
-
* typescript({
|
|
742
|
-
* transformers: {
|
|
743
|
-
* afterDeclarations: [
|
|
744
|
-
* dtsAlias()
|
|
745
|
-
* ]
|
|
746
|
-
* }
|
|
747
|
-
* })
|
|
748
|
-
* ]
|
|
749
|
-
* }
|
|
750
|
-
*
|
|
751
|
-
* ```
|
|
752
|
-
* @since 0.3.5
|
|
753
|
-
*
|
|
754
|
-
**/
|
|
755
|
-
const dtsAlias = () => ({
|
|
756
|
-
type: 'program',
|
|
757
|
-
factory: dtsAliasTransformerFactory,
|
|
758
|
-
});
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* Синхронно парсит файл `package.json` и возвращает его содержимое в виде
|
|
762
|
-
* типизированного объекта. Обрабатывает распространённые ошибки
|
|
763
|
-
* файловой системы и синтаксические ошибки JSON с помощью пользовательских исключений.
|
|
764
|
-
*
|
|
765
|
-
* @param filePath - Абсолютный или относительный путь к файлу `package.json`.
|
|
766
|
-
* @returns Объект типа {@link Package}, представляющий данные из файла.
|
|
767
|
-
* @throws {FileError} При ошибке обработки файла.
|
|
768
|
-
*
|
|
769
|
-
* @since 0.3.5
|
|
770
|
-
*
|
|
771
|
-
**/
|
|
772
|
-
function parsePackageJson(filePath) {
|
|
773
|
-
try {
|
|
774
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
775
|
-
return JSON.parse(content);
|
|
776
|
-
}
|
|
777
|
-
catch (e) {
|
|
778
|
-
if (e instanceof SyntaxError)
|
|
779
|
-
throw FileError.get('invalidJson', filePath, e.message);
|
|
780
|
-
if (e instanceof Error) {
|
|
781
|
-
if ('code' in e) {
|
|
782
|
-
switch (e.code) {
|
|
783
|
-
case 'ENOENT':
|
|
784
|
-
throw FileError.get('notFound', filePath);
|
|
785
|
-
case 'EACCES':
|
|
786
|
-
case 'EPERM':
|
|
787
|
-
throw FileError.get('noAccess', filePath);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
throw FileError.get('failedToParse', filePath, e.message);
|
|
791
|
-
}
|
|
792
|
-
throw FileError.get('failedToParse', filePath, String(e));
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
/**
|
|
797
|
-
* Преобразует входные данные в массив, исключая "ложные" значения (`false`, `null`, `undefined`).
|
|
798
|
-
*
|
|
799
|
-
* @param input - Входные данные, которые могут быть:
|
|
800
|
-
* - массивом элементов типа `TItem | false | null | undefined`;
|
|
801
|
-
* - отдельным элементом типа `TItem | false | null | undefined`.
|
|
802
|
-
* @returns Массив элементов типа `TItem`, содержащий только "истинные" значения.
|
|
803
|
-
*
|
|
804
|
-
* @example
|
|
805
|
-
*
|
|
806
|
-
* ```ts
|
|
807
|
-
* ensureCompactArray([1, null, 2]) // [1, 2]
|
|
808
|
-
*
|
|
809
|
-
* ensureCompactArray(undefined) // []
|
|
810
|
-
*
|
|
811
|
-
* ensureCompactArray('test') // ['test']
|
|
812
|
-
*
|
|
813
|
-
* ```
|
|
814
|
-
* @since 0.3.5
|
|
815
|
-
*
|
|
816
|
-
**/
|
|
817
|
-
function ensureCompactArray(input) {
|
|
818
|
-
if (Array.isArray(input))
|
|
819
|
-
return input.filter(Boolean);
|
|
820
|
-
if (input)
|
|
821
|
-
return [input];
|
|
822
|
-
return [];
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Создаёт фильтр для определения внешних модулей на основе указанных правил.
|
|
827
|
-
*
|
|
828
|
-
* @param cwd Рабочая директория проекта (используется для проверки относительных путей)
|
|
829
|
-
* @param externals Набор паттернов или функций для определения внешних модулей
|
|
830
|
-
* @returns Функция-предикат, которая принимает параметры модуля и возвращает true,
|
|
831
|
-
* если модуль должен быть считаться внешним
|
|
832
|
-
*
|
|
833
|
-
* @returns Функция-предикат `(target: string, importer: string | undefined, isResolved: boolean): boolean`,
|
|
834
|
-
* которая возвращает `true`, если модуль считается внешним.
|
|
835
|
-
*
|
|
836
|
-
* @since 0.3.5
|
|
837
|
-
*
|
|
838
|
-
**/
|
|
839
|
-
function createExternalFilter(cwd, ...externals) {
|
|
840
|
-
/**
|
|
841
|
-
* Функция-предикат для определения внешнего модуля
|
|
842
|
-
*
|
|
843
|
-
* @param target Целевой путь/имя модуля
|
|
844
|
-
* @param importer Имя файла, который импортирует модуль (если доступно)
|
|
845
|
-
* @param isResolved Флаг, указывающий, был ли модуль успешно разрешён
|
|
846
|
-
* @returns `true`, если модуль внешний, иначе false
|
|
847
|
-
*/
|
|
848
|
-
return (target, importer, isResolved) => {
|
|
849
|
-
for (const external of externals) {
|
|
850
|
-
// Шаг 1: Пользовательская функция
|
|
851
|
-
if (typeof external === 'function') {
|
|
852
|
-
if (external(target, importer, isResolved))
|
|
853
|
-
return true;
|
|
854
|
-
}
|
|
855
|
-
else {
|
|
856
|
-
// Шаг 2: Массив паттернов
|
|
857
|
-
const isExternal = ensureCompactArray(external).some((item) => {
|
|
858
|
-
if (item instanceof RegExp)
|
|
859
|
-
return item.test(target);
|
|
860
|
-
return item === target;
|
|
861
|
-
});
|
|
862
|
-
if (isExternal)
|
|
863
|
-
return true;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
// Шаг 3: Путь вне cwd (только для обнаруженных модулей)
|
|
867
|
-
if (isResolved && cwd && nodePath.isAbsolute(target)) {
|
|
868
|
-
const relativePath = nodePath.relative(cwd, target);
|
|
869
|
-
// Если путь вне cwd, отмечаем его как внешний
|
|
870
|
-
if (relativePath.startsWith('..') || nodePath.isAbsolute(relativePath)) {
|
|
871
|
-
if ((process.env.NODE_ENV !== 'production'))
|
|
872
|
-
console.debug(`Skipping non-project "${relativePath}"`);
|
|
873
|
-
return true;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
// Шаг 4: По умолчанию — внутренний
|
|
877
|
-
return false;
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
const dtsOutputDir = 'dist/dts';
|
|
882
|
-
/**
|
|
883
|
-
* Удаляет префикс './dist/' из пути.
|
|
884
|
-
* @param path Путь к файлу.
|
|
885
|
-
* @returns Нормализованный путь.
|
|
886
|
-
*
|
|
887
|
-
* @since 0.3.5
|
|
888
|
-
*
|
|
889
|
-
**/
|
|
890
|
-
function sliceDistPrefix(path) {
|
|
891
|
-
return path.startsWith('./dist/')
|
|
892
|
-
? path.slice(7)
|
|
893
|
-
: path;
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Нормализует входные данные в массив строк.
|
|
897
|
-
*
|
|
898
|
-
* @param input Входные данные (строка, массив или объект).
|
|
899
|
-
* @returns Массив путей к входным файлам.
|
|
900
|
-
* @throws {NpmBuildError} Если входная конфигурация пуста.
|
|
901
|
-
*
|
|
902
|
-
* @since 0.3.4
|
|
903
|
-
*
|
|
904
|
-
**/
|
|
905
|
-
function normalizeInput(input) {
|
|
906
|
-
const inputs = [];
|
|
907
|
-
// Нормализация входных данных.
|
|
908
|
-
// Строка, массив или объект преобразуются в единый массив строк inputs.
|
|
909
|
-
if (typeof input === 'string') {
|
|
910
|
-
inputs.push(input);
|
|
911
|
-
}
|
|
912
|
-
else if (Array.isArray(input)) {
|
|
913
|
-
inputs.push(...input);
|
|
914
|
-
}
|
|
915
|
-
else if (typeof input === 'object') {
|
|
916
|
-
inputs.push(...Object.values(input));
|
|
917
|
-
}
|
|
918
|
-
if (inputs.length === 0)
|
|
919
|
-
throw NpmBuildError.get('inputEmpty');
|
|
920
|
-
return inputs;
|
|
921
|
-
}
|
|
922
|
-
/**
|
|
923
|
-
* Проверяет, является ли объект условной записью экспорта.
|
|
924
|
-
*
|
|
925
|
-
* @param source Объект для проверки.
|
|
926
|
-
* @returns `true`, если объект содержит поле `import`.
|
|
927
|
-
*
|
|
928
|
-
* @since 0.3.5
|
|
929
|
-
*
|
|
930
|
-
**/
|
|
931
|
-
function isConditionalEntry(source) {
|
|
932
|
-
return 'import' in source;
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Обрабатывает условную запись экспорта.
|
|
936
|
-
*
|
|
937
|
-
* @param source Условная запись.
|
|
938
|
-
* @returns Объект с полями entry и types.
|
|
939
|
-
*
|
|
940
|
-
* @since 0.3.5
|
|
941
|
-
*
|
|
942
|
-
**/
|
|
943
|
-
function processConditionalEntry(source) {
|
|
944
|
-
const result = {};
|
|
945
|
-
if (source.import) {
|
|
946
|
-
if (typeof source.import === 'string') {
|
|
947
|
-
// Путь точки входа определён, типизация отсутствует.
|
|
948
|
-
result.entry = source.import;
|
|
949
|
-
}
|
|
950
|
-
else {
|
|
951
|
-
result.entry = source.import.default;
|
|
952
|
-
result.types = source.import.types;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
return result;
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Проверяет наличие точки входа для типизации.
|
|
959
|
-
*
|
|
960
|
-
* @param entry Путь к точке входа.
|
|
961
|
-
* @param types Путь к файлу типов.
|
|
962
|
-
* @throws {NpmBuildError} Если отсутствует точка входа.
|
|
963
|
-
*
|
|
964
|
-
* @since 0.3.5
|
|
965
|
-
*
|
|
966
|
-
**/
|
|
967
|
-
function assertTypesHaveEntry(entry, types) {
|
|
968
|
-
if (types && !entry)
|
|
969
|
-
throw NpmBuildError.get('exportTypesOnly', types);
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Нормализует поле `exports` из package.json в словарь точек входа.
|
|
973
|
-
*
|
|
974
|
-
* @param exportsField Значение поля `exports`.
|
|
975
|
-
* @returns Словарь точек входа с метаданными.
|
|
976
|
-
* @throws {NpmBuildError} Если конфигурация экспорта отсутствует или некорректна.
|
|
977
|
-
*
|
|
978
|
-
* @since 0.3.5
|
|
979
|
-
*
|
|
980
|
-
**/
|
|
981
|
-
function normalizeExports(exportsField) {
|
|
982
|
-
if (!exportsField)
|
|
983
|
-
throw NpmBuildError.get('exportEmpty');
|
|
984
|
-
if (Array.isArray(exportsField))
|
|
985
|
-
throw NpmBuildError.get('exportDisallowArrayType');
|
|
986
|
-
const result = {};
|
|
987
|
-
if (typeof exportsField === 'string') {
|
|
988
|
-
result[exportsField] = {};
|
|
989
|
-
return result;
|
|
990
|
-
}
|
|
991
|
-
if (isConditionalEntry(exportsField)) {
|
|
992
|
-
const { entry, types } = processConditionalEntry(exportsField);
|
|
993
|
-
assertTypesHaveEntry(entry, types);
|
|
994
|
-
if (entry)
|
|
995
|
-
result[entry] = types
|
|
996
|
-
? { dtsOutputFile: types }
|
|
997
|
-
: {};
|
|
998
|
-
return result;
|
|
999
|
-
}
|
|
1000
|
-
for (const [key, value] of Object.entries(exportsField)) {
|
|
1001
|
-
if (!value)
|
|
1002
|
-
continue;
|
|
1003
|
-
if (!key.startsWith('.'))
|
|
1004
|
-
throw NpmBuildError.get('exportMustStartWithDot', key);
|
|
1005
|
-
let entry, types;
|
|
1006
|
-
if (typeof value === 'string') {
|
|
1007
|
-
// Путь точки входа определён, типизация отсутствует.
|
|
1008
|
-
entry = value;
|
|
1009
|
-
}
|
|
1010
|
-
else if (isConditionalEntry(value)) {
|
|
1011
|
-
({ entry, types } = processConditionalEntry(value));
|
|
1012
|
-
}
|
|
1013
|
-
else {
|
|
1014
|
-
entry = value.default;
|
|
1015
|
-
types = value.types;
|
|
1016
|
-
}
|
|
1017
|
-
assertTypesHaveEntry(entry, types);
|
|
1018
|
-
if (entry)
|
|
1019
|
-
result[entry] = types
|
|
1020
|
-
? { dtsOutputFile: types }
|
|
1021
|
-
: {};
|
|
1022
|
-
}
|
|
1023
|
-
return result;
|
|
1024
|
-
}
|
|
1025
|
-
/**
|
|
1026
|
-
* Создаёт отображение между входными файлами и выходными путями.
|
|
1027
|
-
*
|
|
1028
|
-
* @param inputs Массив исходных файлов.
|
|
1029
|
-
* @param normalizedExports Нормализованные дескрипторы экспорта.
|
|
1030
|
-
* @param skipExports Позволяет пропустить проверку экспорта.
|
|
1031
|
-
* @returns Словарь связей вход-выход.
|
|
1032
|
-
* @throws {NpmBuildError} Если входной файл не связан с экспортом.
|
|
1033
|
-
*
|
|
1034
|
-
* @since 0.3.4
|
|
1035
|
-
*
|
|
1036
|
-
**/
|
|
1037
|
-
function getInputBindings(inputs, normalizedExports, skipExports) {
|
|
1038
|
-
const bodyPattern = /^src\/(.*)\.[jt]s$/;
|
|
1039
|
-
const result = {};
|
|
1040
|
-
const usedExports = new Set();
|
|
1041
|
-
const producingOutputs = new Set();
|
|
1042
|
-
for (const input of inputs) {
|
|
1043
|
-
if (!input.startsWith('src/'))
|
|
1044
|
-
throw NpmBuildError.get('inputPathRequiresPrefix', input, 'src/');
|
|
1045
|
-
const match = bodyPattern.exec(input);
|
|
1046
|
-
if (!match)
|
|
1047
|
-
throw NpmBuildError.get('inputFileExtensionNotSupported', input);
|
|
1048
|
-
const outputFile = `${match[1]}.mjs`;
|
|
1049
|
-
if (producingOutputs.has(outputFile))
|
|
1050
|
-
throw NpmBuildError.get('inputGeneratesDuplicateOutput', outputFile);
|
|
1051
|
-
producingOutputs.add(outputFile);
|
|
1052
|
-
const exportEntry = `./dist/${outputFile}`;
|
|
1053
|
-
usedExports.add(exportEntry);
|
|
1054
|
-
const descriptor = normalizedExports[exportEntry];
|
|
1055
|
-
// Проверяем наличие ключа в словаре экспорта (при необходимости).
|
|
1056
|
-
if (!descriptor && !skipExports)
|
|
1057
|
-
throw NpmBuildError.get('inputHasNoExport', input, exportEntry);
|
|
1058
|
-
result[input] = {
|
|
1059
|
-
outputFile,
|
|
1060
|
-
dtsSourceFile: `${dtsOutputDir}/${match[1]}.d.ts`,
|
|
1061
|
-
dtsOutputFile: descriptor?.dtsOutputFile,
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
for (const key of Object.keys(normalizedExports)) {
|
|
1065
|
-
// Выявляем незадействованные ключи в словаре экспорта (обратная проверка).
|
|
1066
|
-
if (!usedExports.has(key))
|
|
1067
|
-
throw NpmBuildError.get('exportHasNoInput', key);
|
|
1068
|
-
}
|
|
1069
|
-
return result;
|
|
1070
|
-
}
|
|
1071
|
-
// Проверка TypeScript выполняется только для первой конфигурации.
|
|
1072
|
-
let hasTsChecked = false;
|
|
1073
|
-
/**
|
|
1074
|
-
* Определяет конфигурацию сборки на основе package.json.
|
|
1075
|
-
*
|
|
1076
|
-
* @param options Опции конфигурации Rollup.
|
|
1077
|
-
* @returns Массив конфигураций Rollup.
|
|
1078
|
-
*
|
|
1079
|
-
* @since 0.3.0
|
|
1080
|
-
*
|
|
1081
|
-
**/
|
|
1082
|
-
function definePackageConfig(options = {}) {
|
|
1083
|
-
const { cwd = process.cwd(), input = 'src/index.ts', external = [], plugins, skipExports = false, } = options;
|
|
1084
|
-
const pkgPath = nodePath.resolve(cwd, 'package.json');
|
|
1085
|
-
const externalFilter = createExternalFilter(cwd, [
|
|
1086
|
-
/node_modules/,
|
|
1087
|
-
pkgPath,
|
|
1088
|
-
], external);
|
|
1089
|
-
const { exports = {} } = parsePackageJson(pkgPath);
|
|
1090
|
-
const normalizedExports = !skipExports
|
|
1091
|
-
? normalizeExports(exports)
|
|
1092
|
-
: {};
|
|
1093
|
-
const inputBindings = getInputBindings(normalizeInput(input), normalizedExports, skipExports);
|
|
1094
|
-
// Создаёт отображение между файлами типов `.d.ts` и их выходными путями
|
|
1095
|
-
const dtsMappings = Object.values(inputBindings)
|
|
1096
|
-
.reduce((mappings, nextValue) => {
|
|
1097
|
-
if (nextValue?.dtsOutputFile)
|
|
1098
|
-
mappings[nextValue.dtsSourceFile] = sliceDistPrefix(nextValue.dtsOutputFile);
|
|
1099
|
-
return mappings;
|
|
1100
|
-
}, {});
|
|
1101
|
-
const dtsInputs = Object.keys(dtsMappings);
|
|
1102
|
-
const rollupConfigs = [
|
|
1103
|
-
createBuildConfig('mjs', {
|
|
1104
|
-
cwd,
|
|
1105
|
-
input,
|
|
1106
|
-
external: externalFilter,
|
|
1107
|
-
emitDeclarations: dtsInputs.length > 0,
|
|
1108
|
-
plugins,
|
|
1109
|
-
output: {
|
|
1110
|
-
dir: 'dist/',
|
|
1111
|
-
format: 'es',
|
|
1112
|
-
importAttributesKey: 'with',
|
|
1113
|
-
entryFileNames(chunk) {
|
|
1114
|
-
if (chunk.facadeModuleId) {
|
|
1115
|
-
const localPath = nodePath
|
|
1116
|
-
.relative(cwd, chunk.facadeModuleId)
|
|
1117
|
-
.replaceAll(nodePath.sep, nodePath.posix.sep);
|
|
1118
|
-
const binding = inputBindings[localPath];
|
|
1119
|
-
if (binding)
|
|
1120
|
-
return binding.outputFile;
|
|
1121
|
-
}
|
|
1122
|
-
return `${chunk.name}.mjs`;
|
|
1123
|
-
},
|
|
1124
|
-
chunkFileNames: '[name].mjs',
|
|
1125
|
-
},
|
|
1126
|
-
}),
|
|
1127
|
-
];
|
|
1128
|
-
if (dtsInputs.length > 0) {
|
|
1129
|
-
rollupConfigs.push({
|
|
1130
|
-
input: dtsInputs,
|
|
1131
|
-
external: externalFilter,
|
|
1132
|
-
plugins: [
|
|
1133
|
-
nodeResolve(),
|
|
1134
|
-
commonjs(),
|
|
1135
|
-
dts(),
|
|
1136
|
-
del({
|
|
1137
|
-
targets: [dtsOutputDir],
|
|
1138
|
-
hook: 'closeBundle',
|
|
1139
|
-
}),
|
|
1140
|
-
],
|
|
1141
|
-
output: {
|
|
1142
|
-
dir: 'dist/',
|
|
1143
|
-
format: 'es',
|
|
1144
|
-
entryFileNames(chunk) {
|
|
1145
|
-
if (chunk.facadeModuleId) {
|
|
1146
|
-
const localPath = nodePath
|
|
1147
|
-
.relative(cwd, chunk.facadeModuleId)
|
|
1148
|
-
.replaceAll(nodePath.sep, nodePath.posix.sep);
|
|
1149
|
-
if (dtsMappings[localPath])
|
|
1150
|
-
return dtsMappings[localPath];
|
|
1151
|
-
}
|
|
1152
|
-
return `${chunk.name}.mts`;
|
|
1153
|
-
},
|
|
1154
|
-
},
|
|
1155
|
-
});
|
|
1156
|
-
}
|
|
1157
|
-
return rollupConfigs;
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Создаёт конфигурацию сборки Rollup.
|
|
1161
|
-
*
|
|
1162
|
-
* @param buildName Имя сборки.
|
|
1163
|
-
* @param options Параметры сборки.
|
|
1164
|
-
* @returns Конфигурация Rollup.
|
|
1165
|
-
*
|
|
1166
|
-
* @since 0.3.0
|
|
1167
|
-
*
|
|
1168
|
-
**/
|
|
1169
|
-
function createBuildConfig(buildName, options) {
|
|
1170
|
-
const { cwd, external, input, emitDeclarations, plugins = [], output } = options;
|
|
1171
|
-
output.sourcemap = !!process.env.SOURCE_MAP;
|
|
1172
|
-
output.externalLiveBindings = false;
|
|
1173
|
-
process.env.NODE_ENV === 'production';
|
|
1174
|
-
const tsPlugin = ts$1({
|
|
1175
|
-
tsconfig: nodePath.resolve(cwd, './tsconfig.build.json'),
|
|
1176
|
-
compilerOptions: {
|
|
1177
|
-
noCheck: hasTsChecked,
|
|
1178
|
-
declaration: emitDeclarations,
|
|
1179
|
-
declarationDir: emitDeclarations ? dtsOutputDir : void 0,
|
|
1180
|
-
},
|
|
1181
|
-
exclude: [
|
|
1182
|
-
'packages/*/tests',
|
|
1183
|
-
],
|
|
1184
|
-
transformers: {
|
|
1185
|
-
afterDeclarations: [
|
|
1186
|
-
dtsAlias(),
|
|
1187
|
-
],
|
|
1188
|
-
},
|
|
1189
|
-
});
|
|
1190
|
-
// При запуске команды build, проверки TS и генерация определений
|
|
1191
|
-
// выполняются единожды - для первой конфигурации.
|
|
1192
|
-
hasTsChecked = true;
|
|
1193
|
-
return {
|
|
1194
|
-
input,
|
|
1195
|
-
external,
|
|
1196
|
-
plugins: [
|
|
1197
|
-
tsPlugin,
|
|
1198
|
-
createReplacePlugin(),
|
|
1199
|
-
nodeResolve(),
|
|
1200
|
-
commonjs(),
|
|
1201
|
-
...plugins,
|
|
1202
|
-
copy({
|
|
1203
|
-
targets: [
|
|
1204
|
-
{ src: 'public/*', dest: 'dist' },
|
|
1205
|
-
],
|
|
1206
|
-
}),
|
|
1207
|
-
],
|
|
1208
|
-
output,
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Создаёт плагин замены значений.
|
|
1213
|
-
*
|
|
1214
|
-
* @param isProduction Признак production-сборки.
|
|
1215
|
-
* @param isBundlerEsmBuild Признак сборки для bundler ESM.
|
|
1216
|
-
* @param isNodeBuild Признак сборки для Node.js.
|
|
1217
|
-
* @returns Плагин замены.
|
|
1218
|
-
*
|
|
1219
|
-
* @since 0.3.0
|
|
1220
|
-
*
|
|
1221
|
-
**/
|
|
1222
|
-
function createReplacePlugin(isProduction, isBundlerEsmBuild, isNodeBuild) {
|
|
1223
|
-
const replacements = {
|
|
1224
|
-
// Preserve to be handled by bundlers
|
|
1225
|
-
__DEV__: `(process.env.NODE_ENV !== 'production')`
|
|
1226
|
-
,
|
|
1227
|
-
__TEST__: `(process.env.NODE_ENV === 'test')`
|
|
1228
|
-
,
|
|
1229
|
-
};
|
|
1230
|
-
// Allow inline overrides like
|
|
1231
|
-
// __DEV__=true pnpm build
|
|
1232
|
-
Object.keys(replacements).forEach((key) => {
|
|
1233
|
-
if (key in process.env)
|
|
1234
|
-
replacements[key] = process.env[key];
|
|
1235
|
-
});
|
|
1236
|
-
return replace({
|
|
1237
|
-
preventAssignment: true,
|
|
1238
|
-
values: replacements,
|
|
1239
|
-
delimiters: ['\\b', '\\b(?![\\.\\:])'],
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
11
|
+
import nodePath from 'node:path';
|
|
1242
12
|
|
|
1243
13
|
// Абсолютный путь к результирующему каталогу.
|
|
1244
|
-
const outputDir
|
|
1245
|
-
const modulesDir = path.join(outputDir
|
|
14
|
+
const outputDir = path.join(process.cwd(), 'dist');
|
|
15
|
+
const modulesDir = path.join(outputDir, 'wb-rules-modules');
|
|
1246
16
|
// Расположение обрабатываемого чанка.
|
|
1247
|
-
const getChunkDir = (fileName) => path.dirname(path.join(outputDir
|
|
17
|
+
const getChunkDir = (fileName) => path.dirname(path.join(outputDir, fileName));
|
|
1248
18
|
// Шаблон для отлова конструкций require.
|
|
1249
19
|
const patternRequire = /require\(['"]([^'"]+)'\)/g;
|
|
1250
20
|
/**
|
|
@@ -1308,103 +78,52 @@ function wbRulesImports() {
|
|
|
1308
78
|
}
|
|
1309
79
|
|
|
1310
80
|
/**
|
|
1311
|
-
*
|
|
1312
|
-
*
|
|
1313
|
-
* @param cwd - Текущая рабочая директория для поиска
|
|
1314
|
-
* @returns Promise<string | undefined> - Абсолютный путь к корню или undefined
|
|
1315
|
-
* @throws {PackageManagerError} Если используется неподдерживаемый менеджер пакетов
|
|
1316
|
-
*
|
|
1317
|
-
* @remarks
|
|
1318
|
-
* Поддерживает только PNPM в текущей реализации.
|
|
1319
|
-
*
|
|
1320
|
-
* @since 0.3.5
|
|
1321
|
-
*
|
|
1322
|
-
**/
|
|
1323
|
-
async function findMonorepoDirAsync(cwd) {
|
|
1324
|
-
if (process.env.PNPM_HOME)
|
|
1325
|
-
return await findWorkspaceDir(cwd);
|
|
1326
|
-
// TODO: реализовать поддержку остальных пакетных менеджеров.
|
|
1327
|
-
throw PackageManagerError.get('pnpmOnly');
|
|
1328
|
-
}
|
|
1329
|
-
/**
|
|
1330
|
-
* Формирует относительный путь в формате posix.
|
|
1331
|
-
*
|
|
1332
|
-
* @param workspaceDir - Корневая директория монорепозитория
|
|
1333
|
-
* @param packageRootDir - Директория конкретного пакета
|
|
1334
|
-
* @returns Стандартизированный относительный путь с завершающим слешем
|
|
1335
|
-
*
|
|
1336
|
-
**/
|
|
1337
|
-
function getWorkspacePath(workspaceDir, packageRootDir) {
|
|
1338
|
-
return nodePath.relative(workspaceDir, packageRootDir)
|
|
1339
|
-
.replaceAll(nodePath.sep, nodePath.posix.sep) + '/';
|
|
1340
|
-
}
|
|
1341
|
-
/**
|
|
1342
|
-
* Получает полную информацию о структуре монорепозитория.
|
|
1343
|
-
*
|
|
1344
|
-
* @param cwd - Текущая рабочая директория для поиска
|
|
1345
|
-
* @returns Объект контекста {@link MonorepoContext} или undefined
|
|
1346
|
-
* @throws {WorkspaceError} Если проект определён как монорепозиторий и отсутствует секция `workspaces` в package.json
|
|
1347
|
-
*
|
|
1348
|
-
* @since 0.3.5
|
|
81
|
+
* Находит пакет, которому принадлежит указанный чанк.
|
|
1349
82
|
*
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const monorepoDir = await findMonorepoDirAsync(cwd);
|
|
1353
|
-
if (monorepoDir) {
|
|
1354
|
-
const pkg = parsePackageJson(`${monorepoDir}/package.json`);
|
|
1355
|
-
if (!pkg.workspaces)
|
|
1356
|
-
throw WorkspaceError.get('noWorkspaces');
|
|
1357
|
-
const packages = await findWorkspacePackages(monorepoDir, {
|
|
1358
|
-
patterns: pkg.workspaces,
|
|
1359
|
-
});
|
|
1360
|
-
const context = {
|
|
1361
|
-
rootDir: monorepoDir,
|
|
1362
|
-
packages: packages
|
|
1363
|
-
// Не рассматриваем корневой каталог в качестве пакета.
|
|
1364
|
-
.filter(x => x.rootDir !== monorepoDir)
|
|
1365
|
-
// Сортируем по длине пути к корню монорепозитория (самые длинные первыми).
|
|
1366
|
-
.sort((a, b) => b.rootDir.length - a.rootDir.length)
|
|
1367
|
-
// Создаём массив объектов с информацией о пакетах монорепозитория.
|
|
1368
|
-
.map(item => ({
|
|
1369
|
-
workspacePath: getWorkspacePath(monorepoDir, item.rootDir),
|
|
1370
|
-
name: item.manifest.name,
|
|
1371
|
-
})),
|
|
1372
|
-
};
|
|
1373
|
-
return context;
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* Поиск пакета по имени чанка
|
|
83
|
+
* Поиск выполняется по префиксу пути: первый пакет, чей `workspacePath`
|
|
84
|
+
* является началом `chunkName`, считается владельцем.
|
|
1378
85
|
*
|
|
1379
|
-
* @param context - Контекст
|
|
1380
|
-
* @param chunkName - Имя
|
|
1381
|
-
* @returns PackageDefinition
|
|
86
|
+
* @param context - Контекст монорепозитория.
|
|
87
|
+
* @param chunkName - Имя чанка, предоставляемое Rollup (`chunk.name`).
|
|
88
|
+
* @returns Объект {@link PackageDefinition} или `undefined`, если пакет не найден.
|
|
1382
89
|
*
|
|
1383
|
-
* @since 0.
|
|
90
|
+
* @since 0.4.0
|
|
1384
91
|
*
|
|
1385
92
|
**/
|
|
1386
|
-
function
|
|
93
|
+
function findPackageByChunkName(context, chunkName) {
|
|
1387
94
|
for (const pkg of context.packages) {
|
|
1388
|
-
if (chunkName.startsWith(pkg.workspacePath))
|
|
95
|
+
if (chunkName.startsWith(pkg.workspacePath + '/'))
|
|
1389
96
|
return pkg;
|
|
1390
|
-
}
|
|
1391
97
|
}
|
|
1392
98
|
}
|
|
99
|
+
|
|
1393
100
|
/**
|
|
1394
|
-
* Преобразует
|
|
101
|
+
* Преобразует имя чанка в путь, имитирующий установленный пакет в `node_modules`.
|
|
1395
102
|
*
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
* @returns Строка в формате node_modules/<package-name>/<relative-path>
|
|
1399
|
-
* @throws {WorkspaceError} Если имя пакета не указано
|
|
103
|
+
* Позволяет обрабатывать импорты из других пакетов монорепозитория
|
|
104
|
+
* наравне с установленными зависимостями.
|
|
1400
105
|
*
|
|
1401
|
-
* @
|
|
106
|
+
* @param chunkName - Путь к файлу от корня монорепозитория в формате POSIX (и без расширения).
|
|
107
|
+
* @param pkgDefinition - Пакет, которому принадлежит файл.
|
|
108
|
+
* @returns Виртуальный путь вида `node_modules/<имя-пакета>/<относительный-путь>`.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* toVirtualModulePath('packages/mirta-home/dist/heater', {
|
|
113
|
+
* name: '@mirta/home',
|
|
114
|
+
* workspacePath: 'packages/mirta-home'
|
|
115
|
+
* })
|
|
116
|
+
* // → 'node_modules/@mirta/home/dist/heater'
|
|
117
|
+
* ```
|
|
118
|
+
* @since 0.4.0
|
|
1402
119
|
*
|
|
1403
120
|
**/
|
|
1404
|
-
function
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
121
|
+
function toVirtualModulePath(chunkName, pkgDefinition) {
|
|
122
|
+
const relativePath = nodePath.posix.relative(pkgDefinition.workspacePath, chunkName);
|
|
123
|
+
// Проверяем, что путь не выходит за пределы пакета.
|
|
124
|
+
if (relativePath.startsWith('..'))
|
|
125
|
+
throw BuildError.get('chunkOutsidePackage', chunkName, pkgDefinition.name, pkgDefinition.workspacePath);
|
|
126
|
+
return `node_modules/${pkgDefinition.name}/${relativePath}`;
|
|
1408
127
|
}
|
|
1409
128
|
|
|
1410
129
|
const packagesPattern = /(.*)node_modules[\\/]@?(.+)[\\/](.+)?/;
|
|
@@ -1472,28 +191,23 @@ function getEntryPath(filePath) {
|
|
|
1472
191
|
?? filePath;
|
|
1473
192
|
}
|
|
1474
193
|
|
|
1475
|
-
const
|
|
1476
|
-
const isProduction =
|
|
1477
|
-
const
|
|
1478
|
-
es5: 'dist/es5',
|
|
1479
|
-
};
|
|
194
|
+
const mode = process.env.NODE_ENV ?? 'development';
|
|
195
|
+
const isProduction = mode === 'production';
|
|
196
|
+
const outDir = 'dist/es5';
|
|
1480
197
|
/**
|
|
1481
|
-
*
|
|
1482
|
-
* Обрабатывает входные файлы, плагины и настройку выходных путей.
|
|
1483
|
-
*
|
|
1484
|
-
* @param options - опции конфигурации
|
|
1485
|
-
* @returns Объект RollupOptions
|
|
198
|
+
* Собирает Rollup-конфигурацию для сборки runtime-кода проекта с учётом монорепозитория и подстановки переменных окружения.
|
|
1486
199
|
*
|
|
200
|
+
* @param options - Параметры: `cwd` — рабочая директория проекта; `external` — список внешних зависимостей; `envLoader` — опции загрузчика окружения; `plugins` — дополнительные Rollup-плагины
|
|
201
|
+
* @returns Сконфигурированный объект RollupOptions, готовый для сборки в каталог `dist/es5`
|
|
1487
202
|
* @since 0.3.0
|
|
1488
|
-
|
|
1489
|
-
**/
|
|
203
|
+
*/
|
|
1490
204
|
async function defineRuntimeConfig(options = {}) {
|
|
1491
|
-
const { cwd = process.cwd(), external,
|
|
1492
|
-
const monorepoContext = await
|
|
205
|
+
const { cwd = process.cwd(), external, envLoader: envLoaderOptions, plugins = [], } = options;
|
|
206
|
+
const monorepoContext = await resolveMonorepoContextAsync(cwd);
|
|
1493
207
|
const defaultPlugins = [
|
|
1494
208
|
// Очистка директории dist перед сборкой
|
|
1495
209
|
del({
|
|
1496
|
-
targets:
|
|
210
|
+
targets: outDir,
|
|
1497
211
|
}),
|
|
1498
212
|
// Поддержка множественных входных файлов
|
|
1499
213
|
multi({
|
|
@@ -1503,15 +217,21 @@ async function defineRuntimeConfig(options = {}) {
|
|
|
1503
217
|
// Поиск зависимостей в node_modules
|
|
1504
218
|
nodeResolve(),
|
|
1505
219
|
// Транспиляция TypeScript
|
|
1506
|
-
ts
|
|
220
|
+
ts({ clean: true }),
|
|
1507
221
|
// Обработка импортов для wb-rules
|
|
1508
222
|
wbRulesImports(),
|
|
1509
|
-
//
|
|
1510
|
-
dotenv(dotenvOptions),
|
|
1511
|
-
// Замена условных флагов в коде
|
|
223
|
+
// Подстановка переменных окружения
|
|
1512
224
|
replace({
|
|
1513
225
|
preventAssignment: true,
|
|
1514
226
|
values: {
|
|
227
|
+
// Загрузка переменных окружения
|
|
228
|
+
...loadEnvReplacements({
|
|
229
|
+
...envLoaderOptions,
|
|
230
|
+
mode,
|
|
231
|
+
cwd,
|
|
232
|
+
rootDir: monorepoContext.rootDir,
|
|
233
|
+
}),
|
|
234
|
+
// Признак сборки в режиме разработки
|
|
1515
235
|
__DEV__: JSON.stringify(!isProduction),
|
|
1516
236
|
// Автоматически меняется в процессе тестирования
|
|
1517
237
|
__TEST__: 'false',
|
|
@@ -1527,7 +247,7 @@ async function defineRuntimeConfig(options = {}) {
|
|
|
1527
247
|
}),
|
|
1528
248
|
// Очистка виртуальных файлов после сборки
|
|
1529
249
|
del({
|
|
1530
|
-
targets:
|
|
250
|
+
targets: `${outDir}/_virtual`,
|
|
1531
251
|
hook: 'closeBundle',
|
|
1532
252
|
}),
|
|
1533
253
|
];
|
|
@@ -1541,14 +261,19 @@ async function defineRuntimeConfig(options = {}) {
|
|
|
1541
261
|
output: {
|
|
1542
262
|
format: 'cjs',
|
|
1543
263
|
strict: false,
|
|
1544
|
-
dir:
|
|
264
|
+
dir: outDir,
|
|
1545
265
|
preserveModules: true,
|
|
1546
|
-
entryFileNames(
|
|
1547
|
-
|
|
266
|
+
entryFileNames(chunk) {
|
|
267
|
+
// Относительный путь от корня репозитория.
|
|
268
|
+
let chunkName = chunk.name;
|
|
1548
269
|
// Адаптация путей при сборке в монорепозитории.
|
|
1549
|
-
if (monorepoContext) {
|
|
270
|
+
if (monorepoContext.packages.length !== 0) {
|
|
1550
271
|
const { rootDir } = monorepoContext;
|
|
1551
|
-
|
|
272
|
+
// Преобразуем chunkName (относительный путь от корня монорепозитория)
|
|
273
|
+
// в абсолютный путь для корректного сравнения с cwd (абсолютным путём
|
|
274
|
+
// к текущему пакету).
|
|
275
|
+
//
|
|
276
|
+
const absolutePath = nodePath.resolve(rootDir, chunkName);
|
|
1552
277
|
if (absolutePath.startsWith(cwd)) {
|
|
1553
278
|
// Путь в текущем проекте, не требует встраивания отдельным пакетом.
|
|
1554
279
|
chunkName = nodePath
|
|
@@ -1557,9 +282,9 @@ async function defineRuntimeConfig(options = {}) {
|
|
|
1557
282
|
}
|
|
1558
283
|
else {
|
|
1559
284
|
// Ищем пакет монорепозитория, в котором находится указанный путь.
|
|
1560
|
-
const pkgDefinition =
|
|
285
|
+
const pkgDefinition = findPackageByChunkName(monorepoContext, chunkName);
|
|
1561
286
|
if (pkgDefinition)
|
|
1562
|
-
chunkName =
|
|
287
|
+
chunkName = toVirtualModulePath(chunkName, pkgDefinition);
|
|
1563
288
|
}
|
|
1564
289
|
}
|
|
1565
290
|
return getEntryPath(chunkName);
|
|
@@ -1568,4 +293,4 @@ async function defineRuntimeConfig(options = {}) {
|
|
|
1568
293
|
};
|
|
1569
294
|
}
|
|
1570
295
|
|
|
1571
|
-
export { defineRuntimeConfig as
|
|
296
|
+
export { defineRuntimeConfig as d };
|