or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdcollection.mdconfiguration.mdhooks.mdindex.mdinstrumentation.mdreporting.mdstorage.mdtree-summarizer.mdutilities.md
tile.json

hooks.mddocs/

0

# Runtime Hooks

1

2

Runtime hooks intercept Node.js module loading and script execution to instrument code transparently at runtime. This enables coverage tracking without pre-instrumenting files on disk.

3

4

## Capabilities

5

6

### Module Loading Hooks

7

8

Hook into Node.js require() calls to instrument modules as they are loaded.

9

10

```javascript { .api }

11

const hook = {

12

/**

13

* Hooks require() to transform modules as they are loaded

14

* @param {Function} matcher - Function that returns true for files to instrument

15

* @param {Function} transformer - Function that instruments the code

16

* @param {Object} options - Hook configuration options

17

*/

18

hookRequire(matcher: (filename: string) => boolean, transformer: (code: string, filename: string) => string, options?: HookOptions): void;

19

20

/**

21

* Restores original require() behavior and unhooks instrumentation

22

*/

23

unhookRequire(): void;

24

25

/**

26

* Hooks vm.createScript() for instrumenting dynamically created scripts

27

* @param {Function} matcher - Function that returns true for scripts to instrument

28

* @param {Function} transformer - Function that instruments the code

29

* @param {Object} opts - Hook configuration options

30

*/

31

hookCreateScript(matcher: (filename: string) => boolean, transformer: (code: string, filename: string) => string, opts?: HookOptions): void;

32

33

/**

34

* Restores original vm.createScript() behavior

35

*/

36

unhookCreateScript(): void;

37

38

/**

39

* Hooks vm.runInThisContext() for instrumenting eval-like code execution

40

* @param {Function} matcher - Function that returns true for code to instrument

41

* @param {Function} transformer - Function that instruments the code

42

* @param {Object} opts - Hook configuration options

43

*/

44

hookRunInThisContext(matcher: (filename: string) => boolean, transformer: (code: string, filename: string) => string, opts?: HookOptions): void;

45

46

/**

47

* Restores original vm.runInThisContext() behavior

48

*/

49

unhookRunInThisContext(): void;

50

51

/**

52

* Removes modules from require cache that match the given matcher

53

* @param {Function} matcher - Function that returns true for modules to unload

54

*/

55

unloadRequireCache(matcher: (filename: string) => boolean): void;

56

};

57

58

interface HookOptions {

59

/** Enable verbose output for hook operations (defaults to false) */

60

verbose?: boolean;

61

62

/** Array of file extensions to process (defaults to ['.js']) */

63

extensions?: string[];

64

65

/** Function called after loading and transforming each module */

66

postLoadHook?: (file: string) => void;

67

68

/** Additional options passed to transformer */

69

[key: string]: any;

70

}

71

```

72

73

**Usage Examples:**

74

75

```javascript

76

const { hook, Instrumenter, matcherFor } = require('istanbul');

77

78

// Create instrumenter

79

const instrumenter = new Instrumenter();

80

81

// Create matcher for JavaScript files (excludes node_modules)

82

matcherFor({

83

root: process.cwd(),

84

includes: ['**/*.js'],

85

excludes: ['**/node_modules/**', '**/test/**']

86

}, (err, matcher) => {

87

if (err) throw err;

88

89

// Hook require with instrumentation

90

hook.hookRequire(matcher, (code, filename) => {

91

return instrumenter.instrumentSync(code, filename);

92

});

93

94

// Now all matching required modules will be instrumented

95

const myModule = require('./my-module'); // This gets instrumented

96

97

// Later, unhook to restore normal behavior

98

hook.unhookRequire();

99

});

100

```

101

102

### File Matching

103

104

Use Istanbul's built-in matcher creation for flexible file selection:

105

106

```javascript

107

const { matcherFor } = require('istanbul');

108

109

// Basic matcher for all JS files except node_modules

110

matcherFor({}, (err, matcher) => {

111

hook.hookRequire(matcher, transformer);

112

});

113

114

// Custom matcher with specific includes/excludes

115

matcherFor({

116

root: '/path/to/project',

117

includes: ['src/**/*.js', 'lib/**/*.js'],

118

excludes: ['**/*.test.js', '**/node_modules/**', 'build/**']

119

}, (err, matcher) => {

120

hook.hookRequire(matcher, transformer);

121

});

122

123

// Custom matcher function

124

function customMatcher(filename) {

125

return filename.includes('/src/') &&

126

filename.endsWith('.js') &&

127

!filename.includes('.test.js');

128

}

129

130

hook.hookRequire(customMatcher, transformer);

131

```

132

133

### VM Script Hooks

134

135

For applications that use vm.createScript() or eval-like constructs:

136

137

```javascript

138

const vm = require('vm');

139

140

// Hook vm.createScript

141

hook.hookCreateScript(matcher, (code, filename) => {

142

console.log('Instrumenting script:', filename);

143

return instrumenter.instrumentSync(code, filename);

144

});

145

146

// Now vm.createScript calls will be instrumented

147

const script = vm.createScript('console.log("Hello World");', 'dynamic-script.js');

148

script.runInThisContext();

149

150

// Hook vm.runInThisContext for direct eval-like execution

151

hook.hookRunInThisContext(matcher, transformer);

152

153

// This will be instrumented if it matches

154

vm.runInThisContext('function test() { return 42; }', 'eval-code.js');

155

```

156

157

### Complete Hook Setup

158

159

Typical setup for comprehensive coverage tracking:

160

161

```javascript

162

const { hook, Instrumenter, matcherFor } = require('istanbul');

163

164

function setupCoverageHooks(callback) {

165

// Initialize coverage tracking

166

global.__coverage__ = {};

167

168

// Create instrumenter

169

const instrumenter = new Instrumenter({

170

coverageVariable: '__coverage__',

171

embedSource: false,

172

preserveComments: false

173

});

174

175

// Create file matcher

176

matcherFor({

177

root: process.cwd(),

178

includes: ['**/*.js'],

179

excludes: [

180

'**/node_modules/**',

181

'**/test/**',

182

'**/tests/**',

183

'**/*.test.js',

184

'**/*.spec.js',

185

'**/coverage/**'

186

]

187

}, (err, matcher) => {

188

if (err) return callback(err);

189

190

// Transformer function

191

const transformer = (code, filename) => {

192

try {

193

return instrumenter.instrumentSync(code, filename);

194

} catch (error) {

195

console.warn('Failed to instrument:', filename, error.message);

196

return code; // Return original code if instrumentation fails

197

}

198

};

199

200

// Hook all the things

201

hook.hookRequire(matcher, transformer);

202

hook.hookCreateScript(matcher, transformer);

203

hook.hookRunInThisContext(matcher, transformer);

204

205

callback(null);

206

});

207

}

208

209

// Setup hooks before loading application code

210

setupCoverageHooks((err) => {

211

if (err) {

212

console.error('Failed to setup coverage hooks:', err);

213

process.exit(1);

214

}

215

216

console.log('Coverage hooks installed');

217

218

// Now load and run your application

219

require('./app');

220

221

// After application runs, unhook and generate reports

222

process.on('exit', () => {

223

hook.unhookRequire();

224

hook.unhookCreateScript();

225

hook.unhookRunInThisContext();

226

227

// Generate coverage reports

228

const { Collector, Reporter } = require('istanbul');

229

const collector = new Collector();

230

collector.add(global.__coverage__);

231

232

const reporter = new Reporter();

233

reporter.addAll(['text-summary', 'html']);

234

reporter.write(collector, true, () => {

235

console.log('Coverage reports generated');

236

});

237

});

238

});

239

```

240

241

### Cache Management

242

243

Manage Node.js require cache for accurate coverage:

244

245

```javascript

246

// Unload modules to ensure fresh instrumentation

247

hook.unloadRequireCache(matcher);

248

249

// Example: Unload specific modules

250

hook.unloadRequireCache((filename) => {

251

return filename.includes('/src/') && !filename.includes('node_modules');

252

});

253

254

// Clear entire cache (use with caution)

255

Object.keys(require.cache).forEach(key => {

256

delete require.cache[key];

257

});

258

```

259

260

### Hook Lifecycle Management

261

262

Proper hook management for test suites:

263

264

```javascript

265

describe('My Test Suite', () => {

266

let originalRequire;

267

268

before((done) => {

269

// Setup hooks

270

matcherFor({}, (err, matcher) => {

271

if (err) return done(err);

272

273

hook.hookRequire(matcher, (code, filename) => {

274

return instrumenter.instrumentSync(code, filename);

275

});

276

277

done();

278

});

279

});

280

281

after(() => {

282

// Clean up hooks

283

hook.unhookRequire();

284

hook.unhookCreateScript();

285

hook.unhookRunInThisContext();

286

});

287

288

beforeEach(() => {

289

// Reset coverage for each test

290

global.__coverage__ = {};

291

});

292

293

it('should track coverage', () => {

294

const myModule = require('./my-module');

295

myModule.someFunction();

296

297

// Coverage should be populated

298

expect(global.__coverage__).to.have.property('./my-module.js');

299

});

300

});

301

```

302

303

### Error Handling and Debugging

304

305

Handle instrumentation errors gracefully:

306

307

```javascript

308

function robustTransformer(code, filename) {

309

try {

310

return instrumenter.instrumentSync(code, filename);

311

} catch (error) {

312

// Log instrumentation failures

313

console.warn(`Instrumentation failed for ${filename}:`, error.message);

314

315

// Return original code to avoid breaking the application

316

return code;

317

}

318

}

319

320

// Enable debug mode for troubleshooting

321

const debugInstrumenter = new Instrumenter({

322

debug: true,

323

walkDebug: true

324

});

325

326

// Custom matcher with debugging

327

function debugMatcher(filename) {

328

const shouldInstrument = filename.endsWith('.js') &&

329

!filename.includes('node_modules');

330

331

if (shouldInstrument) {

332

console.log('Will instrument:', filename);

333

}

334

335

return shouldInstrument;

336

}

337

```

338

339

### Performance Considerations

340

341

Optimize hook performance for large applications:

342

343

```javascript

344

// Cache instrumented results to avoid re-instrumentation

345

const instrumentationCache = new Map();

346

347

function cachedTransformer(code, filename) {

348

const cacheKey = filename + ':' + require('crypto')

349

.createHash('md5')

350

.update(code)

351

.digest('hex');

352

353

if (instrumentationCache.has(cacheKey)) {

354

return instrumentationCache.get(cacheKey);

355

}

356

357

const instrumented = instrumenter.instrumentSync(code, filename);

358

instrumentationCache.set(cacheKey, instrumented);

359

return instrumented;

360

}

361

362

// Optimize matcher for performance

363

function optimizedMatcher(filename) {

364

// Quick checks first

365

if (!filename.endsWith('.js')) return false;

366

if (filename.includes('node_modules')) return false;

367

if (filename.includes('.test.')) return false;

368

369

// More expensive checks last

370

return filename.includes('/src/') || filename.includes('/lib/');

371

}

372

```

373

374

### Integration with Test Frameworks

375

376

Common integration patterns:

377

378

```javascript

379

// Mocha integration

380

function setupMochaCoverage() {

381

before(function(done) {

382

this.timeout(10000); // Increase timeout for instrumentation

383

384

matcherFor({}, (err, matcher) => {

385

if (err) return done(err);

386

hook.hookRequire(matcher, transformer);

387

done();

388

});

389

});

390

391

after(() => {

392

hook.unhookRequire();

393

});

394

}

395

396

// Jest integration (in setup file)

397

const setupJestCoverage = () => {

398

if (process.env.NODE_ENV === 'test') {

399

matcherFor({}, (err, matcher) => {

400

if (!err) {

401

hook.hookRequire(matcher, transformer);

402

}

403

});

404

}

405

};

406

407

// Manual integration for custom test runners

408

function runTestsWithCoverage(testFunction) {

409

return new Promise((resolve, reject) => {

410

matcherFor({}, (err, matcher) => {

411

if (err) return reject(err);

412

413

hook.hookRequire(matcher, transformer);

414

415

Promise.resolve(testFunction())

416

.then(resolve)

417

.catch(reject)

418

.finally(() => {

419

hook.unhookRequire();

420

});

421

});

422

});

423

}

424

```