@cloudcare/hvigor-ohos-plugin 0.1.0 → 0.1.2-alpha.1

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.
@@ -5,41 +5,47 @@
5
5
  * the compiler's in-memory source. Application files are never modified.
6
6
  */
7
7
 
8
- declare const require: {
9
- main: {
10
- require(moduleName: string): any;
11
- };
12
- };
8
+ import { hasRuntimeDependency } from './ft-transform-context';
9
+ import { createLexicalResolver } from './ft-lexical-scope';
10
+ import { allocateName as selectIdentifierName, collectIdentifierNames } from './ft-generated-names';
11
+
12
+ declare const require: any;
13
13
  declare const __dirname: string;
14
14
 
15
15
  type TypeScriptApi = any;
16
- type AbilityClassKind = 'stage' | 'ui' | 'other';
16
+ type AbilityClassKind = 'stage' | 'ui' | 'other' | 'intent' | 'legacyIntent';
17
+ type SourceReader = (fileName: string) => string | undefined;
17
18
 
18
- interface CompilePluginContext {
19
- share: {
20
- projectConfig: {
21
- etsLoaderPath: string;
22
- };
23
- };
19
+ interface AbilityClassInfo {
20
+ kind: AbilityClassKind | null;
21
+ applicationBase: boolean;
22
+ base?: any;
23
+ }
24
+
25
+ interface AbilityBase {
26
+ kind: AbilityClassKind | null;
27
+ declaration?: any;
24
28
  }
25
29
 
26
- interface SharedSourceTransformRegistry {
27
- originalReadFile: (fileName: string, options?: any) => any;
28
- buffer: any;
29
- transforms: Map<string, (fileName: string, code: string) => string>;
30
+ interface LaunchParameterTypes {
31
+ want: string;
32
+ abilityConstant: string;
33
+ needed: boolean;
30
34
  }
31
35
 
32
36
  interface ColdLaunchInjection {
33
37
  position: number;
38
+ end?: number;
34
39
  content: string;
35
40
  }
36
41
 
42
+ interface ColdLaunchInjections {
43
+ injections: ColdLaunchInjection[];
44
+ hasUIAbility: boolean;
45
+ }
46
+
37
47
  const COLD_LAUNCH_BRIDGE_IMPORT = '@guancecloud/ft_sdk/Index';
38
48
  const COLD_LAUNCH_TRANSFORM_MARKER = '/* guance-cold-launch-transform:instrumented */';
39
- const SOURCE_TRANSFORM_REGISTRY_KEY = '__guanceHvigorSourceTransformRegistry';
40
- const TRANSFORM_ID = 'guance-cold-launch-transform';
41
-
42
- let registeredFileSystem: any = null;
43
49
 
44
50
  export function isApplicationArkTsSource(id: string): boolean {
45
51
  const normalizedId = id.replace(/\\/g, '/');
@@ -53,27 +59,6 @@ function visit(ts: TypeScriptApi, node: any, visitor: (current: any) => void): v
53
59
  ts.forEachChild(node, (child: any) => visit(ts, child, visitor));
54
60
  }
55
61
 
56
- function collectIdentifierNames(ts: TypeScriptApi, sourceFile: any): Set<string> {
57
- const identifiers = new Set<string>();
58
- visit(ts, sourceFile, (node: any) => {
59
- if (ts.isIdentifier(node)) {
60
- identifiers.add(node.text);
61
- }
62
- });
63
- return identifiers;
64
- }
65
-
66
- function selectBridgeName(identifiers: Set<string>): string {
67
- const baseName = '__ftColdLaunchBridge';
68
- let bridgeName = baseName;
69
- let suffix = 1;
70
- while (identifiers.has(bridgeName)) {
71
- bridgeName = `${baseName}${suffix}`;
72
- suffix++;
73
- }
74
- return bridgeName;
75
- }
76
-
77
62
  function getPluginVersion(): string {
78
63
  try {
79
64
  const fileSystem = require.main.require('fs');
@@ -100,7 +85,57 @@ function findImportInsertionOffset(ts: TypeScriptApi, sourceFile: any): number {
100
85
  return offset;
101
86
  }
102
87
 
88
+ /** Only opt in when a readable HAP manifest explicitly has no module entry. */
89
+ function needsUIAbilityModuleFallback(ts: TypeScriptApi, id: string, readSource?: SourceReader): boolean {
90
+ const normalized = id.replace(/\\/g, '/');
91
+ const sourceIndex = normalized.lastIndexOf('/src/main/ets/');
92
+ if (sourceIndex < 0) {
93
+ return false;
94
+ }
95
+ const manifestPath = normalized.slice(0, sourceIndex) + '/src/main/module.json5';
96
+ try {
97
+ const nodeRequire = require.main && typeof require.main.require === 'function'
98
+ ? require.main.require.bind(require.main) : require;
99
+ const code = readSource ? readSource(manifestPath) : nodeRequire('fs').readFileSync(manifestPath, 'utf8');
100
+ if (typeof code !== 'string') {
101
+ return false;
102
+ }
103
+ // Parsing an object initializer accepts JSON5 comments, single quotes and
104
+ // unquoted keys without mistaking nested ability srcEntry fields for Stage.
105
+ const parsed = ts.createSourceFile(manifestPath, `const __manifest = ${code}`,
106
+ ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
107
+ if (parsed.parseDiagnostics.length > 0 || parsed.statements.length !== 1 ||
108
+ !ts.isVariableStatement(parsed.statements[0])) {
109
+ return false;
110
+ }
111
+ const root = parsed.statements[0].declarationList.declarations[0].initializer;
112
+ const property = (object: any, name: string): any | undefined => {
113
+ if (!object || !ts.isObjectLiteralExpression(object)) {
114
+ return undefined;
115
+ }
116
+ const matches = object.properties.filter((item: any) => ts.isPropertyAssignment(item) &&
117
+ (ts.isIdentifier(item.name) || ts.isStringLiteral(item.name)) && item.name.text === name);
118
+ return matches.length > 0 ? matches[matches.length - 1].initializer : undefined;
119
+ };
120
+ const module = property(root, 'module');
121
+ const type = property(module, 'type');
122
+ if (!type || !ts.isStringLiteral(type) || (type.text !== 'entry' && type.text !== 'feature')) {
123
+ return false;
124
+ }
125
+ const stageEntry = property(module, 'srcEntry');
126
+ return stageEntry === undefined || (ts.isStringLiteral(stageEntry) && stageEntry.text.trim().length === 0);
127
+ } catch (_) {
128
+ return false;
129
+ }
130
+ }
131
+
103
132
  function classifyAbilityBaseName(name: string): AbilityClassKind | null {
133
+ if (name === 'InsightIntentEntryExecutor') {
134
+ return 'intent';
135
+ }
136
+ if (name === 'InsightIntentExecutor') {
137
+ return 'legacyIntent';
138
+ }
104
139
  if (name === 'AbilityStage') {
105
140
  return 'stage';
106
141
  }
@@ -113,54 +148,218 @@ function classifyAbilityBaseName(name: string): AbilityClassKind | null {
113
148
  return null;
114
149
  }
115
150
 
116
- function collectAbilityBindings(ts: TypeScriptApi, sourceFile: any): Map<string, AbilityClassKind> {
117
- const bindings = new Map<string, AbilityClassKind>();
118
- sourceFile.statements.forEach((statement: any) => {
119
- if (!ts.isImportDeclaration(statement) || !statement.moduleSpecifier ||
120
- !ts.isStringLiteral(statement.moduleSpecifier) || !statement.importClause) {
121
- return;
122
- }
123
- const moduleName = statement.moduleSpecifier.text;
124
- if (!moduleName.startsWith('@ohos.app.ability/') && moduleName !== '@kit.AbilityKit') {
125
- return;
151
+ function bindingContains(ts: TypeScriptApi, binding: any, name: string): boolean {
152
+ if (ts.isIdentifier(binding)) {
153
+ return binding.text === name;
154
+ }
155
+ return !!binding.elements && binding.elements.some((element: any) =>
156
+ ts.isBindingElement(element) && bindingContains(ts, element.name, name));
157
+ }
158
+
159
+ function findValueDeclaration(ts: TypeScriptApi, location: any, name: string): any | null {
160
+ for (let scope = location.parent; scope; scope = scope.parent) {
161
+ if (ts.isClassDeclaration(scope) && scope.name && scope.name.text === name) {
162
+ return scope;
126
163
  }
127
- const importClause = statement.importClause;
128
- if (importClause.name) {
129
- const defaultKind = classifyAbilityBaseName(moduleName.substring(moduleName.lastIndexOf('.') + 1));
130
- if (defaultKind) {
131
- bindings.set(importClause.name.text, defaultKind);
164
+ if (ts.isFunctionLike(scope)) {
165
+ const parameter = scope.parameters.find((item: any) => bindingContains(ts, item.name, name));
166
+ if (parameter) {
167
+ return parameter;
132
168
  }
133
169
  }
134
- if (!importClause.namedBindings || !ts.isNamedImports(importClause.namedBindings)) {
135
- return;
136
- }
137
- importClause.namedBindings.elements.forEach((element: any) => {
138
- const importedName = element.propertyName ? element.propertyName.text : element.name.text;
139
- const kind = classifyAbilityBaseName(importedName);
140
- if (kind) {
141
- bindings.set(element.name.text, kind);
170
+ for (const statement of scope.statements || []) {
171
+ if (ts.isImportDeclaration(statement) && statement.importClause) {
172
+ const clause = statement.importClause;
173
+ if (clause.name && clause.name.text === name) {
174
+ return clause;
175
+ }
176
+ if (clause.namedBindings) {
177
+ if (ts.isNamespaceImport(clause.namedBindings) && clause.namedBindings.name.text === name) {
178
+ return clause.namedBindings;
179
+ }
180
+ if (ts.isNamedImports(clause.namedBindings)) {
181
+ const imported = clause.namedBindings.elements.find((item: any) => item.name.text === name);
182
+ if (imported) {
183
+ return imported;
184
+ }
185
+ }
186
+ }
142
187
  }
143
- });
144
- });
145
- return bindings;
188
+ if ((ts.isClassDeclaration(statement) || ts.isFunctionDeclaration(statement) ||
189
+ ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement)) &&
190
+ statement.name && statement.name.text === name) {
191
+ return statement;
192
+ }
193
+ if (ts.isVariableStatement(statement)) {
194
+ const variable = statement.declarationList.declarations.find((item: any) =>
195
+ bindingContains(ts, item.name, name));
196
+ if (variable) {
197
+ return variable;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ return null;
146
203
  }
147
204
 
148
- function classAbilityKind(ts: TypeScriptApi, classDeclaration: any,
149
- abilityBindings: Map<string, AbilityClassKind>): AbilityClassKind | null {
150
- if (!classDeclaration.heritageClauses) {
205
+ /** Resolve source-visible inheritance without reading through our own transform hook. */
206
+ function createAbilityClassResolver(ts: TypeScriptApi, sourceFile: any, readSource?: SourceReader):
207
+ (declaration: any) => AbilityClassInfo {
208
+ const nodeRequire = require.main && typeof require.main.require === 'function'
209
+ ? require.main.require.bind(require.main) : require;
210
+ const path = nodeRequire('path');
211
+ const sources = new Map<string, any | null>();
212
+ sources.set(path.resolve(sourceFile.fileName), sourceFile);
213
+ const classCache = new Map<any, AbilityClassInfo>();
214
+ const resolvingClasses = new Set<any>();
215
+ const resolvingExports = new Set<string>();
216
+ const unknown: AbilityBase = { kind: null };
217
+
218
+ const loadRelativeSource = (from: any, moduleName: string): any | null => {
219
+ if (!moduleName.startsWith('.')) {
220
+ return null;
221
+ }
222
+ const base = path.resolve(path.dirname(from.fileName), moduleName);
223
+ const candidates = /\.(ets|ts)$/.test(base) ? [base] :
224
+ [base + '.ets', base + '.ts', path.join(base, 'Index.ets'), path.join(base, 'index.ets'),
225
+ path.join(base, 'Index.ts'), path.join(base, 'index.ts')];
226
+ for (const fileName of candidates) {
227
+ if (!isApplicationArkTsSource(fileName)) {
228
+ continue;
229
+ }
230
+ if (!sources.has(fileName)) {
231
+ let parsed: any | null = null;
232
+ try {
233
+ const code = readSource ? readSource(fileName) : nodeRequire('fs').readFileSync(fileName, 'utf8');
234
+ if (typeof code === 'string') {
235
+ parsed = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
236
+ }
237
+ } catch (_) {
238
+ // A missing or unresolved source is not evidence of an Ability base.
239
+ }
240
+ sources.set(fileName, parsed);
241
+ }
242
+ if (sources.get(fileName)) {
243
+ return sources.get(fileName);
244
+ }
245
+ }
151
246
  return null;
152
- }
153
- for (const clause of classDeclaration.heritageClauses) {
154
- if (clause.token !== ts.SyntaxKind.ExtendsKeyword || clause.types.length === 0) {
155
- continue;
247
+ };
248
+
249
+ const resolveModule = (from: any, moduleName: string, exportedName: string): AbilityBase => {
250
+ const name = exportedName === 'default' ? moduleName.slice(moduleName.lastIndexOf('.') + 1) : exportedName;
251
+ const kind = classifyAbilityBaseName(name);
252
+ if (moduleName === '@kit.AbilityKit' || moduleName.startsWith('@ohos.app.ability.') ||
253
+ (kind === 'other' && (moduleName.startsWith('@kit.') || moduleName.startsWith('@ohos.')))) {
254
+ return { kind };
156
255
  }
157
- const expression = clause.types[0].expression;
158
- if (!ts.isIdentifier(expression)) {
159
- continue;
256
+ const imported = loadRelativeSource(from, moduleName);
257
+ return imported ? resolveExport(imported, exportedName) : unknown;
258
+ };
259
+
260
+ const resolveBinding = (declaration: any, member?: string): AbilityBase => {
261
+ if (!declaration) {
262
+ return unknown;
160
263
  }
161
- return abilityBindings.get(expression.text) || classifyAbilityBaseName(expression.text);
162
- }
163
- return null;
264
+ if (ts.isClassDeclaration(declaration)) {
265
+ return { kind: resolveClass(declaration).kind, declaration };
266
+ }
267
+ let importDeclaration = declaration;
268
+ while (importDeclaration && !ts.isImportDeclaration(importDeclaration)) {
269
+ importDeclaration = importDeclaration.parent;
270
+ }
271
+ if (!importDeclaration || importDeclaration.importClause.isTypeOnly || declaration.isTypeOnly) {
272
+ return unknown;
273
+ }
274
+ let exportedName: string;
275
+ if (ts.isImportClause(declaration)) {
276
+ exportedName = 'default';
277
+ } else if (ts.isImportSpecifier(declaration)) {
278
+ exportedName = (declaration.propertyName || declaration.name).text;
279
+ } else if (ts.isNamespaceImport(declaration) && member) {
280
+ exportedName = member;
281
+ } else {
282
+ return unknown;
283
+ }
284
+ return resolveModule(importDeclaration.getSourceFile(), importDeclaration.moduleSpecifier.text, exportedName);
285
+ };
286
+
287
+ const resolveExpression = (expression: any): AbilityBase => {
288
+ if (ts.isIdentifier(expression)) {
289
+ return resolveBinding(findValueDeclaration(ts, expression, expression.text));
290
+ }
291
+ if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
292
+ return resolveBinding(findValueDeclaration(ts, expression, expression.expression.text), expression.name.text);
293
+ }
294
+ return unknown;
295
+ };
296
+
297
+ const resolveExport = (source: any, name: string): AbilityBase => {
298
+ const key = `${source.fileName}:${name}`;
299
+ if (resolvingExports.has(key)) {
300
+ return unknown;
301
+ }
302
+ resolvingExports.add(key);
303
+ try {
304
+ for (const statement of source.statements) {
305
+ if (ts.isClassDeclaration(statement)) {
306
+ const modifiers = statement.modifiers || [];
307
+ const isDefault = modifiers.some((item: any) => item.kind === ts.SyntaxKind.DefaultKeyword);
308
+ const isExported = modifiers.some((item: any) => item.kind === ts.SyntaxKind.ExportKeyword);
309
+ if ((name === 'default' && isDefault) ||
310
+ (name !== 'default' && isExported && statement.name && statement.name.text === name)) {
311
+ return resolveBinding(statement);
312
+ }
313
+ }
314
+ if (name === 'default' && ts.isExportAssignment(statement) && !statement.isExportEquals) {
315
+ return resolveExpression(statement.expression);
316
+ }
317
+ if (!ts.isExportDeclaration(statement) || statement.isTypeOnly) {
318
+ continue;
319
+ }
320
+ if (statement.exportClause && ts.isNamedExports(statement.exportClause)) {
321
+ const exported = statement.exportClause.elements.find((item: any) => item.name.text === name);
322
+ if (exported && !exported.isTypeOnly) {
323
+ const localName = (exported.propertyName || exported.name).text;
324
+ return statement.moduleSpecifier
325
+ ? resolveModule(source, statement.moduleSpecifier.text, localName)
326
+ : resolveBinding(findValueDeclaration(ts, statement, localName));
327
+ }
328
+ } else if (!statement.exportClause && statement.moduleSpecifier && name !== 'default') {
329
+ const resolved = resolveModule(source, statement.moduleSpecifier.text, name);
330
+ if (resolved.kind) {
331
+ return resolved;
332
+ }
333
+ }
334
+ }
335
+ return unknown;
336
+ } finally {
337
+ resolvingExports.delete(key);
338
+ }
339
+ };
340
+
341
+ const resolveClass = (declaration: any): AbilityClassInfo => {
342
+ const cached = classCache.get(declaration);
343
+ if (cached) {
344
+ return cached;
345
+ }
346
+ if (resolvingClasses.has(declaration)) {
347
+ return { kind: null, applicationBase: false };
348
+ }
349
+ resolvingClasses.add(declaration);
350
+ let base: AbilityBase = unknown;
351
+ for (const clause of declaration.heritageClauses || []) {
352
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.length > 0) {
353
+ base = resolveExpression(clause.types[0].expression);
354
+ break;
355
+ }
356
+ }
357
+ const info: AbilityClassInfo = { kind: base.kind, applicationBase: !!base.declaration, base: base.declaration };
358
+ resolvingClasses.delete(declaration);
359
+ classCache.set(declaration, info);
360
+ return info;
361
+ };
362
+ return resolveClass;
164
363
  }
165
364
 
166
365
  function methodIndentation(code: string, method: any): string {
@@ -184,6 +383,50 @@ function blockIndentation(code: string, block: any): string {
184
383
  return (match ? match[0] : '') + ' ';
185
384
  }
186
385
 
386
+ function intentDecoratorString(ts: TypeScriptApi, declaration: any, field: string): string {
387
+ const decorators = ts.canHaveDecorators && ts.canHaveDecorators(declaration)
388
+ ? (ts.getDecorators(declaration) || []) : (declaration.decorators || []);
389
+ for (const decorator of decorators) {
390
+ const call = decorator.expression;
391
+ if (!ts.isCallExpression(call) || call.arguments.length === 0 ||
392
+ !ts.isObjectLiteralExpression(call.arguments[0])) {
393
+ continue;
394
+ }
395
+ for (const property of call.arguments[0].properties) {
396
+ if (ts.isPropertyAssignment(property) && property.name && property.name.text === field &&
397
+ ts.isStringLiteral(property.initializer)) {
398
+ return JSON.stringify(property.initializer.text);
399
+ }
400
+ }
401
+ }
402
+ return "''";
403
+ }
404
+
405
+ function callableMember(ts: TypeScriptApi, member: any): any | undefined {
406
+ if (ts.isMethodDeclaration(member) && member.body) return member;
407
+ if (ts.isPropertyDeclaration(member) && member.initializer &&
408
+ (ts.isArrowFunction(member.initializer) || ts.isFunctionExpression(member.initializer))) return member.initializer;
409
+ return undefined;
410
+ }
411
+
412
+ function memberName(ts: TypeScriptApi, member: any): string | undefined {
413
+ return member.name && (ts.isIdentifier(member.name) || ts.isStringLiteral(member.name)) ? member.name.text : undefined;
414
+ }
415
+
416
+ /** Insert into the actual parameter list, ignoring parentheses in return types/comments. */
417
+ function addLaunchParameters(ts: TypeScriptApi, sourceFile: any, callable: any,
418
+ added: string[], injections: ColdLaunchInjection[]): void {
419
+ const close = callable.getChildren(sourceFile).find((child: any) => child.kind === ts.SyntaxKind.CloseParenToken);
420
+ if (close) {
421
+ const separator = callable.parameters.length > 0 && !callable.parameters.hasTrailingComma ? ', ' : '';
422
+ injections.push({ position: close.getStart(sourceFile), content: separator + added.join(', ') });
423
+ } else {
424
+ // A single unparenthesized arrow parameter must become a parameter list.
425
+ injections.push({ position: callable.parameters[0].getStart(sourceFile), content: '(' });
426
+ injections.push({ position: callable.parameters.end, content: ', ' + added.join(', ') + ')' });
427
+ }
428
+ }
429
+
187
430
  function collectWindowStageParameterNames(ts: TypeScriptApi, sourceFile: any, method: any): Set<string> {
188
431
  const names = new Set<string>();
189
432
  method.parameters.forEach((parameter: any) => {
@@ -199,65 +442,221 @@ function collectWindowStageParameterNames(ts: TypeScriptApi, sourceFile: any, me
199
442
  }
200
443
 
201
444
  function createFirstFrameRetryInjections(ts: TypeScriptApi, sourceFile: any, code: string,
202
- method: any, bridgeName: string, windowStageNames: Set<string>): ColdLaunchInjection[] {
203
- const injections: ColdLaunchInjection[] = [];
445
+ method: any, bridgeName: string, windowStageNames: Set<string>, identifiers: Set<string>,
446
+ resolve: (location: any, name: string) => any | undefined): ColdLaunchInjection[] {
447
+ const candidates: any[] = [];
204
448
  visit(ts, method.body, (node: any) => {
205
- if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) {
206
- return;
207
- }
449
+ if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression) ||
450
+ (ts.isOptionalChain && ts.isOptionalChain(node))) return;
208
451
  const delegate = node.expression.expression;
209
452
  if (!ts.isIdentifier(delegate) || !windowStageNames.has(delegate.text) ||
210
- node.expression.name.text !== 'loadContent' || node.arguments.length === 0) {
211
- return;
212
- }
213
- const windowStageName = delegate.text;
453
+ node.expression.name.text !== 'loadContent' || node.arguments.length < 2 || node.arguments.length > 3) return;
454
+ const parameter = resolve(delegate, delegate.text);
455
+ if (!parameter || !method.parameters.includes(parameter)) return;
214
456
  const callback = node.arguments[node.arguments.length - 1];
215
- if ((!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || !ts.isBlock(callback.body)) {
216
- return;
457
+ if ((!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) || !ts.isBlock(callback.body)) return;
458
+ candidates.push({ node, callback, parameter,
459
+ stage: selectIdentifierName(identifiers, '__ftWindowStage'),
460
+ arguments: node.arguments.slice(0, -1).map((_arg: any, index: number) =>
461
+ selectIdentifierName(identifiers, index === 0 ? '__ftContentUrl' : '__ftContentStorage')) });
462
+ });
463
+ const start = (candidate: any): number => candidate.node.getStart(sourceFile);
464
+ const contains = (outer: any, inner: any): boolean => outer !== inner &&
465
+ start(outer) <= start(inner) && outer.node.end >= inner.node.end;
466
+ const render = (from: number, to: number): string => {
467
+ const included = candidates.filter(item => start(item) >= from && item.node.end <= to);
468
+ const direct = included.filter(item => !included.some(outer => contains(outer, item)))
469
+ .sort((left, right) => start(right) - start(left));
470
+ let output = code.slice(from, to);
471
+ for (const candidate of direct) {
472
+ const rendered = renderCandidate(candidate);
473
+ output = output.slice(0, start(candidate) - from) + rendered + output.slice(candidate.node.end - from);
217
474
  }
475
+ return output;
476
+ };
477
+ const renderCandidate = (candidate: any): string => {
478
+ const { node, callback, parameter, stage } = candidate;
479
+ const bodyStart = callback.body.getStart(sourceFile) + 1;
218
480
  const indentation = blockIndentation(code, callback.body);
219
- const errorParameter = callback.parameters.length > 0 && ts.isIdentifier(callback.parameters[0].name)
481
+ const error = callback.parameters.length > 0 && ts.isIdentifier(callback.parameters[0].name)
220
482
  ? callback.parameters[0].name.text : null;
221
- const retry = `${bridgeName}.observeFirstFrame(${windowStageName});`;
222
- const content = errorParameter === null
223
- ? `\n${indentation}${retry}`
224
- : `\n${indentation}if (!${errorParameter} || !${errorParameter}.code) {\n` +
225
- `${indentation} ${retry}\n${indentation}}`;
226
- injections.push({ position: callback.body.getStart(sourceFile) + 1, content });
227
- });
228
- return injections;
483
+ const retry = `${bridgeName}.observeFirstFrame(${stage});`;
484
+ const hook = error === null ? `\n${indentation}${retry}` :
485
+ `\n${indentation}if (!${error} || !${error}.code) {\n${indentation} ${retry}\n${indentation}}`;
486
+ const callbackCode = code.slice(callback.getStart(sourceFile), bodyStart) + hook + render(bodyStart, callback.end);
487
+ const argumentNames: string[] = candidate.arguments;
488
+ const parameterType = parameter.type ? parameter.type.getText(sourceFile) : 'Object';
489
+ const declarations = [`${stage}: ${parameterType}`, ...argumentNames.map((name: string, index: number) =>
490
+ `${name}: ${index === 0 ? 'string' : 'LocalStorage'}`)];
491
+ // Evaluate the receiver and non-callback arguments at the original call site.
492
+ // Each execution captures a separate stage; callbacks cannot shadow or overwrite it.
493
+ const values = [node.expression.expression.getText(sourceFile), ...node.arguments.slice(0, -1)
494
+ .map((argument: any) => render(argument.getStart(sourceFile), argument.end))];
495
+ const statement = node.parent;
496
+ const separator = ts.isExpressionStatement(statement) && (ts.isBlock(statement.parent) ||
497
+ ts.isSourceFile(statement.parent) || ts.isCaseClause(statement.parent) || ts.isDefaultClause(statement.parent)) ? ';' : '';
498
+ return `${separator}((${declarations.join(', ')}) => ${stage}.loadContent(${argumentNames.join(', ')}, ` +
499
+ `${callbackCode}))(${values.join(', ')})`;
500
+ };
501
+ return candidates.filter(item => !candidates.some(outer => contains(outer, item)))
502
+ .map(item => ({ position: start(item), end: item.node.end, content: renderCandidate(item) }));
229
503
  }
230
504
 
231
505
  function createColdLaunchInjections(ts: TypeScriptApi, sourceFile: any, code: string,
232
- bridgeName: string, pluginVersion: string): ColdLaunchInjection[] {
506
+ bridgeName: string, pluginVersion: string, identifiers: Set<string>,
507
+ launchTypes: LaunchParameterTypes, readSource?: SourceReader): ColdLaunchInjections {
233
508
  const injections: ColdLaunchInjection[] = [];
234
- const abilityBindings = collectAbilityBindings(ts, sourceFile);
509
+ let hasUIAbility = false;
510
+ const resolve = createLexicalResolver(ts, sourceFile);
511
+ const resolveClass = createAbilityClassResolver(ts, sourceFile, readSource);
235
512
  const pluginVersionArgument = pluginVersion.length > 0 ? `, ${JSON.stringify(pluginVersion)}` : '';
513
+ const availability = new Map<string, boolean>();
514
+ const inheritedState = (declaration: any, method: string): { covered: boolean; member?: any } => {
515
+ const seen = new Set<any>();
516
+ for (let base = resolveClass(declaration).base; base && !seen.has(base); base = resolveClass(base).base) {
517
+ seen.add(base);
518
+ const id = base.getSourceFile().fileName;
519
+ if (!availability.has(id)) availability.set(id, hasRuntimeDependency(ts, id, '@guancecloud/ft_sdk', readSource));
520
+ // An instrumented intermediate class either owns the hook or supplies a forwarding hook.
521
+ if (availability.get(id)) return { covered: true };
522
+ const member = base.members.find((item: any) => memberName(ts, item) === method &&
523
+ !(item.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.StaticKeyword));
524
+ if (member) return { covered: false, member };
525
+ }
526
+ return { covered: false };
527
+ };
528
+ const wrapInherited = (declaration: any, name: string, hook: string, launch: boolean, inherited: any): void => {
529
+ const original = selectIdentifierName(identifiers, '__ftInheritedCall');
530
+ const want = selectIdentifierName(identifiers, '__ftInheritedWant');
531
+ const param = selectIdentifierName(identifiers, '__ftInheritedLaunch');
532
+ if (launch) launchTypes.needed = true;
533
+ const baseType = declaration.heritageClauses.find((item: any) => item.token === ts.SyntaxKind.ExtendsKeyword)
534
+ .types[0].getText(sourceFile);
535
+ const callable = callableMember(ts, inherited);
536
+ const signature = callable || (inherited.type && ts.isFunctionTypeNode(inherited.type) ? inherited.type : undefined);
537
+ const count = signature && !signature.parameters.some((parameter: any) => parameter.dotDotDotToken)
538
+ ? signature.parameters.length : 2;
539
+ const parameters = launch ? `${want}?: ${launchTypes.want}, ${param}?: ${launchTypes.abilityConstant}.LaunchParam` : '';
540
+ const passed = launch ? [`${want} as ${launchTypes.want}`, `${param} as ${launchTypes.abilityConstant}.LaunchParam`]
541
+ .slice(0, count).join(', ') : '';
542
+ const hookText = hook.replace(/\$want/g, want).replace(/\$launch/g, param);
543
+ // Store the original callable on the same instance. Calling this slot keeps
544
+ // its receiver and concrete return type, including inherited function fields,
545
+ // without bind's dynamic return type or unsupported generic inference.
546
+ injections.push({ position: declaration.end - 1,
547
+ content: `\n private ${original} = (this as Object as ${baseType}).${name};\n` +
548
+ ` ${name} = (${parameters}) => { ${hookText} return this.${original}(${passed}); };\n` });
549
+ };
550
+
236
551
  visit(ts, sourceFile, (node: any) => {
237
552
  if (!ts.isClassDeclaration(node)) {
238
553
  return;
239
554
  }
240
- const kind = classAbilityKind(ts, node, abilityBindings);
555
+ const classification = resolveClass(node);
556
+ const kind = classification.kind;
241
557
  if (!kind) {
242
558
  return;
243
559
  }
560
+ if (kind === 'ui') {
561
+ hasUIAbility = true;
562
+ }
244
563
  let hasOnCreate = false;
245
- node.members.forEach((member: any) => {
246
- if (!ts.isMethodDeclaration(member) || !member.body || !member.name || !ts.isIdentifier(member.name)) {
564
+ let hasOnForeground = false;
565
+ let hasOnBackground = false;
566
+ let hasOnNewWant = false;
567
+ node.members.forEach((declaration: any) => {
568
+ if ((declaration.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.StaticKeyword)) return;
569
+ const name = memberName(ts, declaration);
570
+ if (name === 'onCreate') hasOnCreate = true;
571
+ if (name === 'onForeground') hasOnForeground = true;
572
+ if (name === 'onBackground') hasOnBackground = true;
573
+ if (name === 'onNewWant') hasOnNewWant = true;
574
+ const member = callableMember(ts, declaration);
575
+ if (!member || !name) return;
576
+ const indentation = methodIndentation(code, declaration);
577
+ const block = ts.isBlock(member.body);
578
+ const position = member.body.getStart(sourceFile) + (block ? 1 : 0);
579
+ // Expression-bodied lifecycle properties retain their expression result.
580
+ if (!block) {
581
+ const recognized = kind === 'intent' ? name === 'onExecute' : kind === 'legacyIntent'
582
+ ? ['onExecuteInUIAbilityForegroundMode', 'onExecuteInUIAbilityBackgroundMode'].includes(name)
583
+ : name === 'onCreate' || (kind === 'ui' &&
584
+ (['onForeground', 'onBackground', 'onNewWant'].includes(name) ||
585
+ (name === 'onWindowStageCreate' && member.parameters.length > 0 && ts.isIdentifier(member.parameters[0].name))));
586
+ if (recognized) {
587
+ injections.push({ position, content: '{' });
588
+ injections.push({ position: member.body.end, content: '; }' });
589
+ }
590
+ }
591
+ const finish = (): void => {
592
+ if (!block) injections.push({ position, content: '\n' + indentation + 'return ' });
593
+ };
594
+ if (kind === 'intent' || kind === 'legacyIntent') {
595
+ let mode: string | null = null;
596
+ if (kind === 'intent' && name === 'onExecute') {
597
+ mode = 'this.executeMode';
598
+ } else if (kind === 'legacyIntent' && name === 'onExecuteInUIAbilityForegroundMode') {
599
+ mode = '0';
600
+ } else if (kind === 'legacyIntent' && name === 'onExecuteInUIAbilityBackgroundMode') {
601
+ mode = '1';
602
+ }
603
+ if (mode !== null) {
604
+ const intentName = kind === 'intent' ? intentDecoratorString(ts, node, 'intentName') :
605
+ (member.parameters.length > 0 && ts.isIdentifier(member.parameters[0].name)
606
+ ? member.parameters[0].name.text : "''");
607
+ const abilityName = kind === 'intent' ? intentDecoratorString(ts, node, 'abilityName') : "''";
608
+ injections.push({ position, content: `\n${indentation}${bridgeName}.recordInsightIntentExecute(` +
609
+ `${mode}, ${intentName}, this.context, ${abilityName});` });
610
+ }
611
+ if (mode !== null) finish();
247
612
  return;
248
613
  }
249
- const indentation = methodIndentation(code, member);
250
- const position = member.body.getStart(sourceFile) + 1;
251
- if (member.name.text === 'onCreate') {
614
+ if (name === 'onCreate') {
252
615
  hasOnCreate = true;
253
- let content = `\n${indentation}${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});`;
616
+ let content = kind === 'ui' ? '' :
617
+ `\n${indentation}${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});`;
254
618
  if (kind === 'ui') {
255
- content += `\n${indentation}${bridgeName}.recordFirstUIAbilityCreate();`;
619
+ const parameters = member.parameters;
620
+ let wantName = parameters.length > 0 && ts.isIdentifier(parameters[0].name)
621
+ ? parameters[0].name.text : 'undefined';
622
+ let launchParamName = parameters.length > 1 && ts.isIdentifier(parameters[1].name)
623
+ ? parameters[1].name.text : 'undefined';
624
+ // Even an onCreate() that ignores both parameters must pass the
625
+ // actual system reason to the SDK. Add typed optional parameters
626
+ // to the in-memory source without changing the application's file.
627
+ if (parameters.length < 2) {
628
+ launchTypes.needed = true;
629
+ const added: string[] = [];
630
+ if (parameters.length === 0) {
631
+ wantName = selectIdentifierName(identifiers, '__ftLaunchWant');
632
+ added.push(`${wantName}?: ${launchTypes.want}`);
633
+ }
634
+ launchParamName = selectIdentifierName(identifiers, '__ftLaunchParam');
635
+ added.push(`${launchParamName}?: ${launchTypes.abilityConstant}.LaunchParam`);
636
+ addLaunchParameters(ts, sourceFile, member, added, injections);
637
+ }
638
+ content += `\n${indentation}${bridgeName}.recordFirstUIAbilityCreate(${launchParamName}, ${wantName}, this);`;
256
639
  }
257
640
  injections.push({ position, content });
641
+ finish();
258
642
  return;
259
643
  }
260
- if (kind !== 'ui' || member.name.text !== 'onWindowStageCreate' || member.parameters.length === 0) {
644
+ if (kind === 'ui' && (name === 'onForeground' || name === 'onBackground' ||
645
+ name === 'onNewWant')) {
646
+ if (name === 'onForeground') {
647
+ hasOnForeground = true;
648
+ } else if (name === 'onBackground') {
649
+ hasOnBackground = true;
650
+ } else {
651
+ hasOnNewWant = true;
652
+ }
653
+ const hook = name === 'onForeground' ? 'recordUIAbilityForeground' :
654
+ (name === 'onBackground' ? 'recordUIAbilityBackground' : 'recordUIAbilityNewWant');
655
+ injections.push({ position, content: `\n${indentation}${bridgeName}.${hook}(this);` });
656
+ finish();
657
+ return;
658
+ }
659
+ if (kind !== 'ui' || name !== 'onWindowStageCreate' || member.parameters.length === 0) {
261
660
  return;
262
661
  }
263
662
  const windowStageParameter = member.parameters[0];
@@ -267,121 +666,125 @@ function createColdLaunchInjections(ts: TypeScriptApi, sourceFile: any, code: st
267
666
  const windowStageName = windowStageParameter.name.text;
268
667
  injections.push({
269
668
  position,
270
- content: `\n${indentation}${bridgeName}.observeFirstFrame(${windowStageName});`
669
+ content: `\n${indentation}if (${windowStageName}) { ${bridgeName}.observeFirstFrame(${windowStageName}); }`
271
670
  });
671
+ finish();
272
672
  });
273
673
  if (kind === 'ui') {
274
- node.members.forEach((member: any) => {
275
- if (!ts.isMethodDeclaration(member) || !member.body) {
276
- return;
277
- }
674
+ node.members.forEach((declaration: any) => {
675
+ if ((declaration.modifiers || []).some((modifier: any) => modifier.kind === ts.SyntaxKind.StaticKeyword)) return;
676
+ const member = callableMember(ts, declaration);
677
+ if (!member) return;
278
678
  const windowStageNames = collectWindowStageParameterNames(ts, sourceFile, member);
279
679
  if (windowStageNames.size > 0) {
280
680
  injections.push(...createFirstFrameRetryInjections(
281
- ts, sourceFile, code, member, bridgeName, windowStageNames));
681
+ ts, sourceFile, code, member, bridgeName, windowStageNames, identifiers, resolve));
282
682
  }
283
683
  });
284
684
  }
285
685
  if (kind === 'stage' && !hasOnCreate) {
286
- const indentation = classIndentation(code, node);
287
- injections.push({
288
- position: node.end - 1,
289
- content: `\n${indentation} onCreate(): void {\n` +
290
- `${indentation} ${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});\n` +
291
- `${indentation} }\n`
292
- });
686
+ const inherited = inheritedState(node, 'onCreate');
687
+ if (!inherited.covered) {
688
+ const hook = `${bridgeName}.recordApplicationInit(this.context${pluginVersionArgument});`;
689
+ if (inherited.member) wrapInherited(node, 'onCreate', hook, false, inherited.member);
690
+ else injections.push({ position: node.end - 1,
691
+ content: `\n onCreate(): void { ${hook} }\n` });
692
+ }
693
+ }
694
+ if (kind === 'ui' && !hasOnCreate) {
695
+ const inherited = inheritedState(node, 'onCreate');
696
+ if (!inherited.covered) {
697
+ launchTypes.needed = true;
698
+ if (inherited.member) {
699
+ wrapInherited(node, 'onCreate', `${bridgeName}.recordFirstUIAbilityCreate($launch, $want, this);`, true, inherited.member);
700
+ } else {
701
+ const wantName = selectIdentifierName(identifiers, '__ftLaunchWant');
702
+ const paramName = selectIdentifierName(identifiers, '__ftLaunchParam');
703
+ injections.push({ position: node.end - 1, content: `\n onCreate(${wantName}: ${launchTypes.want}, ` +
704
+ `${paramName}: ${launchTypes.abilityConstant}.LaunchParam): void {\n` +
705
+ ` ${bridgeName}.recordFirstUIAbilityCreate(${paramName}, ${wantName}, this);\n }\n` });
706
+ }
707
+ }
708
+ }
709
+ if (kind === 'ui') {
710
+ for (const [method, hook, exists] of [
711
+ ['onForeground', 'recordUIAbilityForeground', hasOnForeground],
712
+ ['onBackground', 'recordUIAbilityBackground', hasOnBackground],
713
+ ['onNewWant', 'recordUIAbilityNewWant', hasOnNewWant]
714
+ ]) {
715
+ if (exists) continue;
716
+ const inherited = inheritedState(node, method as string);
717
+ if (inherited.covered) continue;
718
+ const call = `${bridgeName}.${hook}(this);`;
719
+ if (inherited.member) wrapInherited(node, method as string, call, method === 'onNewWant', inherited.member);
720
+ else injections.push({ position: node.end - 1, content: `\n ${method}(): void { ${call} }\n` });
721
+ }
293
722
  }
294
723
  });
295
- return injections;
724
+ return { injections, hasUIAbility };
296
725
  }
297
726
 
298
727
  function applyColdLaunchInjections(code: string, injections: ColdLaunchInjection[]): string {
299
728
  const contentsByPosition = new Map<number, string[]>();
729
+ const ends = new Map<number, number>();
300
730
  injections.forEach((injection: ColdLaunchInjection) => {
301
731
  const contents = contentsByPosition.get(injection.position) || [];
302
732
  contents.push(injection.content);
303
733
  contentsByPosition.set(injection.position, contents);
734
+ ends.set(injection.position, Math.max(ends.get(injection.position) || injection.position, injection.end || injection.position));
304
735
  });
305
736
  const positions = Array.from(contentsByPosition.keys()).sort((left: number, right: number) => right - left);
306
737
  let transformedCode = code;
307
738
  positions.forEach((position: number) => {
308
739
  const content = (contentsByPosition.get(position) || []).join('');
309
- transformedCode = transformedCode.slice(0, position) + content + transformedCode.slice(position);
740
+ transformedCode = transformedCode.slice(0, position) + content + transformedCode.slice(ends.get(position));
310
741
  });
311
742
  return transformedCode;
312
743
  }
313
744
 
314
- function rewriteSource(ts: TypeScriptApi, id: string, code: string): string {
315
- if (!isApplicationArkTsSource(id) || code.includes(COLD_LAUNCH_TRANSFORM_MARKER)) {
745
+ export function transformColdLaunchSource(ts: TypeScriptApi, id: string, code: string,
746
+ readSource?: SourceReader): string {
747
+ if (!isApplicationArkTsSource(id) || code.includes(COLD_LAUNCH_TRANSFORM_MARKER) ||
748
+ !hasRuntimeDependency(ts, id, '@guancecloud/ft_sdk', readSource)) {
316
749
  return code;
317
750
  }
318
751
  const sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
319
- const bridgeName = selectBridgeName(collectIdentifierNames(ts, sourceFile));
752
+ const identifiers = collectIdentifierNames(ts, sourceFile);
753
+ const bridgeName = selectIdentifierName(identifiers, '__ftColdLaunchBridge');
754
+ const launchTypes: LaunchParameterTypes = {
755
+ want: selectIdentifierName(identifiers, '__ftLaunchWantType'),
756
+ abilityConstant: selectIdentifierName(identifiers, '__ftLaunchAbilityConstant'),
757
+ needed: false
758
+ };
320
759
  const version = getPluginVersion();
321
- const injections = createColdLaunchInjections(ts, sourceFile, code, bridgeName, version);
322
- if (injections.length === 0) {
760
+ const result = createColdLaunchInjections(ts, sourceFile, code, bridgeName, version, identifiers,
761
+ launchTypes, readSource);
762
+ const injections = result.injections;
763
+ const moduleFallback = result.hasUIAbility && needsUIAbilityModuleFallback(ts, id, readSource);
764
+ if (injections.length === 0 && !moduleFallback) {
323
765
  return code;
324
766
  }
325
- const insertionOffset = findImportInsertionOffset(ts, sourceFile);
767
+ // Keep generated imports separate from initialization calls: ArkTS requires
768
+ // imports before other statements, while the no-Stage fallback must still
769
+ // run before executable application code.
770
+ const insertionOffset = moduleFallback
771
+ ? (code.startsWith('#!') ? code.indexOf('\n') + 1 : 0)
772
+ : findImportInsertionOffset(ts, sourceFile);
773
+ const firstApplicationStatement = sourceFile.statements.find((statement: any) => !ts.isImportDeclaration(statement));
774
+ const initializationOffset = moduleFallback && firstApplicationStatement
775
+ ? firstApplicationStatement.getStart(sourceFile) : insertionOffset;
776
+ const installBridgeName = selectIdentifierName(identifiers, '__ftColdLaunchInstallBridge');
777
+ const sdkName = selectIdentifierName(identifiers, '__ftColdLaunchSDK');
326
778
  const bridgeImport = `\n${COLD_LAUNCH_TRANSFORM_MARKER}\n` +
327
- `import { FTColdLaunchBridge as ${bridgeName} } from '${COLD_LAUNCH_BRIDGE_IMPORT}';\n`;
328
- const transformedCode = applyColdLaunchInjections(code, injections);
329
- return transformedCode.slice(0, insertionOffset) + bridgeImport + transformedCode.slice(insertionOffset);
779
+ `import { FTColdLaunchBridge as ${bridgeName}, FTConsoleLogBridge as ${installBridgeName}` +
780
+ (version.length > 0 ? `, FTSDK as ${sdkName}` : '') + ` } from '${COLD_LAUNCH_BRIDGE_IMPORT}';\n` +
781
+ (launchTypes.needed ? `import { Want as ${launchTypes.want}, AbilityConstant as ${launchTypes.abilityConstant} } ` +
782
+ `from '@kit.AbilityKit';\n` : '');
783
+ const initialization = `\n${installBridgeName}.markCompilerPluginInstalled();\n` +
784
+ (version.length > 0 ? `${sdkName}.registerHvigorPluginVersion(${JSON.stringify(version)});\n` : '') +
785
+ (moduleFallback ? '// Application-init fallback: UIAbility module evaluation without an AbilityStage entry.\n' +
786
+ `${bridgeName}.recordApplicationInit(undefined, ${JSON.stringify(version)});\n` : '');
787
+ injections.push({ position: insertionOffset, content: bridgeImport });
788
+ injections.push({ position: initializationOffset, content: initialization });
789
+ return applyColdLaunchInjections(code, injections);
330
790
  }
331
-
332
- function installSourceReader(context: CompilePluginContext): void {
333
- if (registeredFileSystem !== null) {
334
- return;
335
- }
336
- const typeScriptPath = `${context.share.projectConfig.etsLoaderPath}/node_modules/typescript`;
337
- const ts = require.main.require(typeScriptPath);
338
- const fileSystem = require.main.require('fs');
339
- let registry = fileSystem[SOURCE_TRANSFORM_REGISTRY_KEY] as SharedSourceTransformRegistry | undefined;
340
- if (!registry) {
341
- registry = {
342
- originalReadFile: fileSystem.readFileSync as (fileName: string, options?: any) => any,
343
- buffer: require.main.require('buffer'),
344
- transforms: new Map<string, (fileName: string, code: string) => string>()
345
- };
346
- fileSystem[SOURCE_TRANSFORM_REGISTRY_KEY] = registry;
347
- fileSystem.readFileSync = (fileName: string, options?: any): any => {
348
- const content = registry!.originalReadFile(fileName, options);
349
- if (typeof fileName !== 'string' || !isApplicationArkTsSource(fileName)) {
350
- return content;
351
- }
352
- let transformedCode = typeof content === 'string' ? content : content.toString();
353
- registry!.transforms.forEach((transform: (id: string, code: string) => string) => {
354
- transformedCode = transform(fileName, transformedCode);
355
- });
356
- return typeof content === 'string' ? transformedCode : registry!.buffer.Buffer.from(transformedCode);
357
- };
358
- }
359
- registry.transforms.set(TRANSFORM_ID, (fileName: string, code: string) => rewriteSource(ts, fileName, code));
360
- registeredFileSystem = fileSystem;
361
- }
362
-
363
- function restoreSourceReader(): void {
364
- if (registeredFileSystem === null) {
365
- return;
366
- }
367
- const registry = registeredFileSystem[SOURCE_TRANSFORM_REGISTRY_KEY] as SharedSourceTransformRegistry | undefined;
368
- if (registry) {
369
- registry.transforms.delete(TRANSFORM_ID);
370
- if (registry.transforms.size === 0) {
371
- registeredFileSystem.readFileSync = registry.originalReadFile;
372
- delete registeredFileSystem[SOURCE_TRANSFORM_REGISTRY_KEY];
373
- }
374
- }
375
- registeredFileSystem = null;
376
- }
377
-
378
- export default {
379
- name: 'guance-cold-launch-transform',
380
- order: 'first',
381
- buildStart(this: CompilePluginContext): void {
382
- installSourceReader(this);
383
- },
384
- buildEnd(): void {
385
- restoreSourceReader();
386
- }
387
- };