Skip to content

Commit 23c07aa

Browse files
authored
feat(hooks): support ES Modules (#5843)
- Hooks now support .js, .cjs and .mjs Any project can use the CLI with @nativescript/hooks v3+ to write/consume es modules hooks.
1 parent 431c133 commit 23c07aa

File tree

2 files changed

+63
-45
lines changed

2 files changed

+63
-45
lines changed

lib/common/services/hooks-service.ts

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import { color } from "../../color";
2424
import { memoize } from "../decorators";
2525

2626
class Hook implements IHook {
27-
constructor(public name: string, public fullPath: string) {}
27+
constructor(
28+
public name: string,
29+
public fullPath: string,
30+
) {}
2831
}
2932

3033
export class HooksService implements IHooksService {
@@ -45,7 +48,7 @@ export class HooksService implements IHooksService {
4548
private $projectHelper: IProjectHelper,
4649
private $options: IOptions,
4750
private $performanceService: IPerformanceService,
48-
private $projectConfigService: IProjectConfigService
51+
private $projectConfigService: IProjectConfigService,
4952
) {}
5053

5154
public get hookArgsName(): string {
@@ -69,12 +72,12 @@ export class HooksService implements IHooksService {
6972

7073
if (projectDir) {
7174
this.hooksDirectories.push(
72-
path.join(projectDir, HooksService.HOOKS_DIRECTORY_NAME)
75+
path.join(projectDir, HooksService.HOOKS_DIRECTORY_NAME),
7376
);
7477
}
7578

7679
this.$logger.trace(
77-
"Hooks directories: " + util.inspect(this.hooksDirectories)
80+
"Hooks directories: " + util.inspect(this.hooksDirectories),
7881
);
7982

8083
const customHooks = this.$projectConfigService.getValue("hooks", []);
@@ -91,7 +94,7 @@ export class HooksService implements IHooksService {
9194

9295
public executeBeforeHooks(
9396
commandName: string,
94-
hookArguments?: IDictionary<any>
97+
hookArguments?: IDictionary<any>,
9598
): Promise<void> {
9699
const beforeHookName = `before-${HooksService.formatHookName(commandName)}`;
97100
const traceMessage = `BeforeHookName for command ${commandName} is ${beforeHookName}`;
@@ -100,7 +103,7 @@ export class HooksService implements IHooksService {
100103

101104
public executeAfterHooks(
102105
commandName: string,
103-
hookArguments?: IDictionary<any>
106+
hookArguments?: IDictionary<any>,
104107
): Promise<void> {
105108
const afterHookName = `after-${HooksService.formatHookName(commandName)}`;
106109
const traceMessage = `AfterHookName for command ${commandName} is ${afterHookName}`;
@@ -110,7 +113,7 @@ export class HooksService implements IHooksService {
110113
private async executeHooks(
111114
hookName: string,
112115
traceMessage: string,
113-
hookArguments?: IDictionary<any>
116+
hookArguments?: IDictionary<any>,
114117
): Promise<any> {
115118
if (this.$config.DISABLE_HOOKS || !this.$options.hooks) {
116119
return;
@@ -135,8 +138,8 @@ export class HooksService implements IHooksService {
135138
await this.executeHooksInDirectory(
136139
hooksDirectory,
137140
hookName,
138-
hookArguments
139-
)
141+
hookArguments,
142+
),
140143
);
141144
}
142145

@@ -148,8 +151,8 @@ export class HooksService implements IHooksService {
148151
this.$projectHelper.projectDir,
149152
hookName,
150153
hook,
151-
hookArguments
152-
)
154+
hookArguments,
155+
),
153156
);
154157
}
155158
} catch (err) {
@@ -160,11 +163,16 @@ export class HooksService implements IHooksService {
160163
return _.flatten(results);
161164
}
162165

166+
private isESModule(hook: IHook): boolean {
167+
const ext = path.extname(hook.fullPath).toLowerCase();
168+
return ext === ".mjs";
169+
}
170+
163171
private async executeHook(
164172
directoryPath: string,
165173
hookName: string,
166174
hook: IHook,
167-
hookArguments?: IDictionary<any>
175+
hookArguments?: IDictionary<any>,
168176
): Promise<any> {
169177
hookArguments = hookArguments || {};
170178

@@ -173,15 +181,22 @@ export class HooksService implements IHooksService {
173181
const relativePath = path.relative(directoryPath, hook.fullPath);
174182
const trackId = relativePath.replace(
175183
new RegExp("\\" + path.sep, "g"),
176-
AnalyticsEventLabelDelimiter
184+
AnalyticsEventLabelDelimiter,
177185
);
186+
const isESM = this.isESModule(hook);
178187
let command = this.getSheBangInterpreter(hook);
179188
let inProc = false;
180189
if (!command) {
181190
command = hook.fullPath;
182-
if (path.extname(hook.fullPath).toLowerCase() === ".js") {
191+
if (
192+
[".mjs", ".cjs", ".js"].includes(
193+
path.extname(hook.fullPath).toLowerCase(),
194+
)
195+
) {
183196
command = process.argv[0];
184-
inProc = this.shouldExecuteInProcess(this.$fs.readText(hook.fullPath));
197+
inProc = isESM
198+
? true
199+
: this.shouldExecuteInProcess(this.$fs.readText(hook.fullPath));
185200
}
186201
}
187202

@@ -190,24 +205,30 @@ export class HooksService implements IHooksService {
190205
this.$logger.trace(
191206
"Executing %s hook at ___location %s in-process",
192207
hookName,
193-
hook.fullPath
208+
hook.fullPath,
194209
);
195-
const hookEntryPoint = require(hook.fullPath);
210+
let hookEntryPoint;
211+
if (isESM) {
212+
const { default: hookFn } = await import(hook.fullPath);
213+
hookEntryPoint = hookFn;
214+
} else {
215+
hookEntryPoint = require(hook.fullPath);
216+
}
196217

197218
this.$logger.trace(`Validating ${hookName} arguments.`);
198219

199220
const invalidArguments = this.validateHookArguments(
200221
hookEntryPoint,
201-
hook.fullPath
222+
hook.fullPath,
202223
);
203224

204225
if (invalidArguments.length) {
205226
this.$logger.warn(
206227
`${
207228
hook.fullPath
208229
} will NOT be executed because it has invalid arguments - ${color.grey(
209-
invalidArguments.join(", ")
210-
)}.`
230+
invalidArguments.join(", "),
231+
)}.`,
211232
);
212233
return;
213234
}
@@ -220,14 +241,13 @@ export class HooksService implements IHooksService {
220241
const projectDataHookArg =
221242
hookArguments["hookArgs"] && hookArguments["hookArgs"]["projectData"];
222243
if (projectDataHookArg) {
223-
hookArguments["projectData"] = hookArguments[
224-
"$projectData"
225-
] = projectDataHookArg;
244+
hookArguments["projectData"] = hookArguments["$projectData"] =
245+
projectDataHookArg;
226246
}
227247

228248
const maybePromise = this.$injector.resolve(
229249
hookEntryPoint,
230-
hookArguments
250+
hookArguments,
231251
);
232252
if (maybePromise) {
233253
this.$logger.trace("Hook promises to signal completion");
@@ -255,15 +275,15 @@ export class HooksService implements IHooksService {
255275
"Executing %s hook at ___location %s with environment ",
256276
hookName,
257277
hook.fullPath,
258-
environment
278+
environment,
259279
);
260280

261281
const output = await this.$childProcess.spawnFromEvent(
262282
command,
263283
[hook.fullPath],
264284
"close",
265285
environment,
266-
{ throwError: false }
286+
{ throwError: false },
267287
);
268288
result = output;
269289

@@ -275,7 +295,7 @@ export class HooksService implements IHooksService {
275295
"Finished executing %s hook at ___location %s with environment ",
276296
hookName,
277297
hook.fullPath,
278-
environment
298+
environment,
279299
);
280300
}
281301
const endTime = this.$performanceService.now();
@@ -289,7 +309,7 @@ export class HooksService implements IHooksService {
289309
private async executeHooksInDirectory(
290310
directoryPath: string,
291311
hookName: string,
292-
hookArguments?: IDictionary<any>
312+
hookArguments?: IDictionary<any>,
293313
): Promise<any[]> {
294314
hookArguments = hookArguments || {};
295315
const results: any[] = [];
@@ -301,7 +321,7 @@ export class HooksService implements IHooksService {
301321
directoryPath,
302322
hookName,
303323
hook,
304-
hookArguments
324+
hookArguments,
305325
);
306326

307327
if (result) {
@@ -316,14 +336,14 @@ export class HooksService implements IHooksService {
316336
const hooks: IHook[] = [];
317337
const customHooks: INsConfigHooks[] = this.$projectConfigService.getValue(
318338
"hooks",
319-
[]
339+
[],
320340
);
321341

322342
for (const cHook of customHooks) {
323343
if (cHook.type === hookName) {
324344
const fullPath = path.join(
325345
this.$projectHelper.projectDir,
326-
cHook.script
346+
cHook.script,
327347
);
328348
const isFile = this.$fs.getFsStats(fullPath).isFile();
329349

@@ -332,8 +352,8 @@ export class HooksService implements IHooksService {
332352
hooks.push(
333353
new Hook(
334354
this.getBaseFilename(fileNameParts[fileNameParts.length - 1]),
335-
fullPath
336-
)
355+
fullPath,
356+
),
337357
);
338358
}
339359
}
@@ -346,10 +366,10 @@ export class HooksService implements IHooksService {
346366
const allBaseHooks = this.getHooksInDirectory(directoryPath);
347367
const baseHooks = _.filter(
348368
allBaseHooks,
349-
(hook) => hook.name.toLowerCase() === hookName.toLowerCase()
369+
(hook) => hook.name.toLowerCase() === hookName.toLowerCase(),
350370
);
351371
const moreHooks = this.getHooksInDirectory(
352-
path.join(directoryPath, hookName)
372+
path.join(directoryPath, hookName),
353373
);
354374
return baseHooks.concat(moreHooks);
355375
}
@@ -385,13 +405,11 @@ export class HooksService implements IHooksService {
385405
const clientName = this.$staticConfig.CLIENT_NAME.toUpperCase();
386406

387407
const environment: IStringDictionary = {};
388-
environment[util.format("%s-COMMANDLINE", clientName)] = process.argv.join(
389-
" "
390-
);
408+
environment[util.format("%s-COMMANDLINE", clientName)] =
409+
process.argv.join(" ");
391410
environment[util.format("%s-HOOK_FULL_PATH", clientName)] = hookFullPath;
392-
environment[
393-
util.format("%s-VERSION", clientName)
394-
] = this.$staticConfig.version;
411+
environment[util.format("%s-VERSION", clientName)] =
412+
this.$staticConfig.version;
395413

396414
return {
397415
cwd: this.$projectHelper.projectDir,
@@ -463,7 +481,7 @@ export class HooksService implements IHooksService {
463481

464482
private validateHookArguments(
465483
hookConstructor: any,
466-
hookFullPath: string
484+
hookFullPath: string,
467485
): string[] {
468486
const invalidArguments: string[] = [];
469487

@@ -477,7 +495,7 @@ export class HooksService implements IHooksService {
477495
}
478496
} catch (err) {
479497
this.$logger.trace(
480-
`Cannot resolve ${argument} of hook ${hookFullPath}, reason: ${err}`
498+
`Cannot resolve ${argument} of hook ${hookFullPath}, reason: ${err}`,
481499
);
482500
invalidArguments.push(argument);
483501
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
},
5151
"keywords": [
5252
"nativescript",
53-
"telerik",
54-
"mobile"
53+
"typescript",
54+
"javascript"
5555
],
5656
"dependencies": {
5757
"@foxt/js-srp": "^0.0.3-patch2",

0 commit comments

Comments
 (0)