Skip to content

Commit 738577a

Browse files
committed
feat:hooks
1 parent f1d1363 commit 738577a

File tree

2 files changed

+395
-0
lines changed

2 files changed

+395
-0
lines changed
Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
---
2+
title: Hooks
3+
description: A Guide to using CLI hooks.
4+
contributors:
5+
- jcassidyav
6+
---
7+
## Overview
8+
9+
NativeScript hooks are executable pieces of code or Node.js scripts that can be added by application or plugin developers to customize the execution of particular NativeScript commands. They provide the power to perform special activities by plugging into different parts of the build process of your application.
10+
11+
Hooks are added to the `hooks/` folder of a project by plugins, or are specified in the `nativescript.config.ts` by an application.
12+
13+
For example, when `ns prepare ...` is executed, all script files in the `hooks/before-prepare/` and `hooks/after-prepare/` folders are executed as well.
14+
15+
Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), you can create hooks as either CommonJS with extension `.js` or ECMAScript Modules (ESM) with extension `.mjs`.
16+
17+
For versions of the NativeScript CLI < 9.0 hooks must be written in CommonJS with extension `.js`.
18+
19+
## Hook Execution Types
20+
21+
The NativeScript CLI supports two different ways of executing hooks:
22+
23+
**1. In-Process Execution**
24+
- Available only for JavaScript hooks
25+
- Executes within the CLI process
26+
- Provides access to NativeScript CLI services via dependency injection
27+
- Determined by the presence of `module.exports` statement
28+
- **Recommended approach** for writing hooks
29+
30+
**2. Spawned Execution**
31+
- Executed via Node.js's `child_process.spawn` function
32+
- Run from the project's root directory
33+
- Cannot access NativeScript CLI services
34+
- If a hook returns a non-zero exit code, the NativeScript command will throw an exception
35+
36+
## In-Process Hooks
37+
38+
**Basic Module Definition**
39+
40+
To write an in-process hook, use the following module definition:
41+
42+
```javascript
43+
module.exports = function() {
44+
// Hook implementation
45+
}
46+
```
47+
48+
**Using hookArgs**
49+
50+
The hook function can accept a special argument named `hookArgs`, which is an object containing all arguments passed to the hooked method.
51+
52+
**Example:**
53+
54+
If the NativeScript CLI has a method `prepareJSApp` defined as:
55+
56+
```typescript
57+
@hook("prepareJSApp")
58+
public async prepareJSApp(projectData: IProjectData, platformData: IPlatformData) { }
59+
```
60+
61+
Then `hookArgs` will have the following structure:
62+
63+
```json
64+
{
65+
"projectData": {...},
66+
"platformData": {...}
67+
}
68+
```
69+
70+
**Using hookArgs in your hook:**
71+
72+
```javascript
73+
module.exports = function(hookArgs) {
74+
console.log(hookArgs.projectData);
75+
}
76+
```
77+
78+
**Dependency Injection**
79+
80+
NativeScript CLI is built with Dependency Injection and executes in-process hooks in a way that allows you to use any registered service from the injector.
81+
82+
***Approach 1: Direct Service Injection***
83+
84+
```javascript
85+
module.exports = function($logger, $fs, $projectDataService, hookArgs) {
86+
$logger.info("Executing hook");
87+
// Use $fs, $projectDataService, etc.
88+
}
89+
```
90+
91+
***Approach 2: Injector Resolution***
92+
93+
```javascript
94+
module.exports = function($injector, hookArgs) {
95+
const $logger = $injector.resolve("$logger");
96+
const $fs = $injector.resolve("$fs");
97+
98+
$logger.info("Executing hook");
99+
}
100+
```
101+
102+
**Important Notes:**
103+
- Injected dependencies are resolved by name
104+
- If you inject a non-existent service (e.g., `$logger1`), the CLI won't execute the hook and will show a warning
105+
- When using `$injector` directly, no warning is shown for incorrect service names, and an error will be thrown during execution
106+
107+
### Async Code in Hooks
108+
109+
NativeScript CLI supports asynchronous code in hooks. If executing async code, you must return a Promise:
110+
111+
```javascript
112+
var mkdirp = require('mkdirp');
113+
114+
module.exports = function($logger) {
115+
return new Promise(function(resolve, reject) {
116+
mkdirp('somedir', function(err) {
117+
if (err) {
118+
reject(err);
119+
} else {
120+
resolve();
121+
}
122+
});
123+
});
124+
}
125+
```
126+
127+
## Spawned Hooks
128+
129+
Spawned hooks are executed via Node's `child_process.spawn` from the project's root directory. All options are passed to the script using environment variables:
130+
131+
| Environment Variable | Description |
132+
|---------------------|-------------|
133+
| `TNS-VERSION` | The version of the NativeScript CLI |
134+
| `TNS-HOOK_FULL_PATH` | The full path to the executed hook |
135+
| `TNS-COMMANDLINE` | The exact command-line arguments passed to NativeScript CLI (e.g., `tns run ios --emulator`) |
136+
137+
If a spawned hook returns a non-zero exit code, NativeScript CLI will throw an error and abort the command's execution.
138+
139+
## Hook Types and Behaviors
140+
141+
**Before/After Hooks**
142+
143+
Hooks can execute code before or after a specific action:
144+
145+
```javascript
146+
module.exports = function(hookArgs) {
147+
if (hookArgs.prepareData.release) {
148+
console.log("Before executing release build.");
149+
}
150+
}
151+
```
152+
153+
**Replacement Hooks**
154+
155+
Hooks can replace the original CLI function (use sparingly):
156+
157+
```javascript
158+
module.exports = function(hookArgs, $logger) {
159+
return () => {
160+
$logger.info("Replaced the original CLI function.");
161+
}
162+
}
163+
```
164+
165+
**Note:** Replacement hooks should only be used in specific, rare cases. Before/after hooks are strongly recommended.
166+
167+
## Adding Hooks to Plugins
168+
169+
To add hooks to your plugin, follow these steps:
170+
171+
**1. Install the Hook Module**
172+
173+
```bash
174+
npm install nativescript-hook --save
175+
```
176+
177+
For NativeScript 7+, use:
178+
```bash
179+
npm install @nativescript/hook --save
180+
```
181+
182+
**2. Create postinstall.js**
183+
184+
Create `postinstall.js` at the root folder of your plugin:
185+
186+
```javascript
187+
var hook = require("nativescript-hook")(__dirname);
188+
hook.postinstall();
189+
190+
// For NativeScript 7+:
191+
// require('@nativescript/hook')(__dirname).postinstall();
192+
```
193+
194+
**3. Create preuninstall.js**
195+
196+
Create `preuninstall.js` at the root folder of your plugin:
197+
198+
```javascript
199+
var hook = require("nativescript-hook")(__dirname);
200+
hook.preuninstall();
201+
202+
// For NativeScript 7+:
203+
// require('@nativescript/hook')(__dirname).preuninstall();
204+
```
205+
206+
**4. Update package.json Scripts**
207+
208+
Add the postinstall and preuninstall scripts:
209+
210+
```json
211+
{
212+
"scripts": {
213+
"postinstall": "node postinstall.js",
214+
"preuninstall": "node preuninstall.js"
215+
}
216+
}
217+
```
218+
219+
**5. Declare Hooks in package.json**
220+
221+
Define your hooks under the `nativescript` property:
222+
223+
```json
224+
{
225+
"nativescript": {
226+
"hooks": [
227+
{
228+
"type": "before-prepare",
229+
"script": "lib/before-prepare.js"
230+
},
231+
{
232+
"type": "after-prepare",
233+
"script": "lib/after-prepare.js",
234+
"inject": true
235+
}
236+
]
237+
}
238+
}
239+
```
240+
241+
### Hook Configuration Properties
242+
243+
#### type (Required)
244+
Specifies when the hook should execute. Format: `before-<hookName>` or `after-<hookName>`
245+
246+
#### script (Required)
247+
The relative path from the plugin root to the hook implementation file.
248+
249+
#### inject (Optional)
250+
Boolean property indicating whether the hook should be executed in-process (`true`) or spawned (`false`). When `inject: true`, the hook can access NativeScript CLI services.
251+
252+
#### name (Optional)
253+
Custom name for the hook. Defaults to the plugin package name.
254+
255+
**Example with custom name:**
256+
257+
```json
258+
{
259+
"nativescript": {
260+
"hooks": [
261+
{
262+
"type": "after-prepare",
263+
"script": "lib/after-prepare.js",
264+
"name": "my-custom-hook"
265+
}
266+
]
267+
}
268+
}
269+
```
270+
271+
## Adding Hooks to Applications
272+
273+
You can define project-persistent hooks in your `nativescript.config.ts` file:
274+
275+
```typescript
276+
import { NativeScriptConfig } from '@nativescript/core'
277+
278+
export default {
279+
id: 'org.nativescript.app',
280+
appPath: 'app',
281+
appResourcesPath: 'App_Resources',
282+
hooks: [
283+
{
284+
type: 'before-prepare',
285+
script: './scripts/hooks/before-prepare.js'
286+
},
287+
{
288+
type: 'after-prepare',
289+
script: './scripts/hooks/after-prepare.js'
290+
}
291+
]
292+
} as NativeScriptConfig
293+
```
294+
295+
## Configuration Reference
296+
297+
### Hooks Configuration in nativescript.config.ts
298+
299+
```typescript
300+
hooks: [
301+
{
302+
type: 'before-<hookName>' | 'after-<hookName>',
303+
script: './path/to/script.js',
304+
},
305+
]
306+
```
307+
308+
## Available Hook Types
309+
310+
The following hook types are available (prefix with `before-` or `after-`):
311+
312+
| Hook Name | Description | Execution Context |
313+
|-----------|-------------|-------------------|
314+
| `buildAndroidPlugin` | Builds aar file for Android plugin | Runs during `prepareNativeApp` |
315+
| `buildAndroid` | Builds Android app | During Android build process |
316+
| `buildIOS` | Builds iOS app | During iOS build process |
317+
| `checkEnvironment` | Validates project environment | Runs during `ns doctor`, `ns clean`, and most build commands |
318+
| `checkForChanges` | Detects changes during watch | NativeScript CLI checks application state to decide if rebuild/reinstall/restart is needed |
319+
| `install` | Application installed to device/emulator | After app installation |
320+
| `prepare` | Compiles webpack and prepares native app | Prepares the application in platforms folder |
321+
| `prepareNativeApp` | Prepares the actual native app | Runs during `prepare`/`watch` hook |
322+
| `resolveCommand` | Resolves command and arguments | Runs before all CLI commands |
323+
| `watch` | Sets up watchers for live sync | During `prepare` hook for live development |
324+
| `watchPatterns` | Sets up watch patterns | During `watch` hook |
325+
326+
### Hook Execution Examples
327+
328+
**Example: Release Build Check**
329+
330+
```javascript
331+
module.exports = function(hookArgs) {
332+
if (hookArgs.prepareData.release) {
333+
console.log("Executing release build");
334+
// Modify API keys, enable production features, etc.
335+
}
336+
}
337+
```
338+
339+
**Example: Using NativeScript Services**
340+
341+
```javascript
342+
module.exports = function($logger, $fs, hookArgs) {
343+
$logger.info("Starting custom hook");
344+
345+
const configPath = hookArgs.projectData.projectDir + '/config.json';
346+
if ($fs.exists(configPath)) {
347+
const config = $fs.readJson(configPath);
348+
$logger.info(`Loaded config: ${JSON.stringify(config)}`);
349+
}
350+
}
351+
```
352+
353+
**Example: ESM Hook**
354+
In a file named `<name>.mjs`
355+
```javascript
356+
export default function (hookArgs) {
357+
console.log(
358+
"MJS executing release build.",
359+
JSON.stringify(hookArgs.prepareData)
360+
);
361+
}
362+
363+
```
364+
365+
## Hooks CLI Command
366+
367+
Starting with NativeScript 9.0 CLI (`npm install -g nativescript`), a new commmand `ns hooks` is available.
368+
369+
As described above `postinstall` scripts are used by plugins to install hooks to the correct location for execution, this is not compatible with users
370+
using `npm install --ignore-scripts` ( or other settings which prevent script execution ) given that plugin that requires hooks utilises post install events to install the hooks.
371+
372+
The new `ns hooks` command resolves this by providing a mechanism to install plugin hooks after a `npm install`.
373+
374+
**Commands**
375+
376+
| Command | Description |
377+
| ---------- | ------------ |
378+
| `ns hooks` | Lists the hooks that are in the installed plugins ( also `ns hooks list` ). |
379+
| `ns hooks install` | Installs the hooks. |
380+
| `ns hooks lock` | Creates a `nativescript-lock.json` file, this is a list of hooks per plugin and a hash of the script for each hook. |
381+
| `ns hooks verify` | Compares the hooks in the plugins with what is specified in the `nativescript-lock.json` file, failing if a hook is not listed or the hash is not the same. |
382+
383+
**Example usage**
384+
385+
* Modify/Create `.npmrc` in the project root adding `ignore-scripts=true`
386+
* After `npm i` run `ns hooks install`
387+
388+
For extra peace of mind:
389+
390+
Run `ns hooks lock` and `ns hooks install` will fail if any of the hooks have changed.
391+

0 commit comments

Comments
 (0)