Skip to content

Commit

Permalink
feat(angular): Add Ivy-compatible Angular SDK package (#7264)
Browse files Browse the repository at this point in the history
Add a new SDK package to our monorepo: `@sentry/angular-ivy`. 

While this is technically a new SDK, its content and functionality is identical to `@sentry/angular`. Only the build configuration differs:

* The Ivy SDK is built with Angular 12, allowing for a compatibility of NG12-15
* The Ivy SDK is built with `compilationMode: partial`, enabeling a build format that is compatible with Angular's Ivy rendering engine. 
  * This means that `ngcc` no longer needs to step in at initial build time to up-compile the SDK. Which is good because `ngcc` will be removed in Angular 16 (angular/angular-cli#24720)
* The Ivy SDK's build output follows the Angular Package Format (APF) v12 standard ([spec](https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview)) which is very similar to APF 10 which we used before (see #4641 for more details)

Because functionality-wise there's no difference to `@sentry/angular`, I opted to symlink the source files instead of duplicating them. The only exception is `sdk.ts` which needed some adaption for the new package, like setting the SDK name and adjusting the min version check. For the same reason, this new package currently doesn't contain tests. We'll need to reconsider this approach (symlinking and testing) if we ever need to make package-specific adjustments.
  • Loading branch information
Lms24 committed Feb 27, 2023
1 parent 4b95c04 commit cca2514
Show file tree
Hide file tree
Showing 24 changed files with 3,309 additions and 129 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
},
"workspaces": [
"packages/angular",
"packages/angular-ivy",
"packages/browser",
"packages/core",
"packages/e2e-tests",
Expand Down
7 changes: 7 additions & 0 deletions packages/angular-ivy/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
env: {
browser: true,
},
extends: ['../../.eslintrc.js'],
ignorePatterns: ['scripts/**/*'],
};
14 changes: 14 additions & 0 deletions packages/angular-ivy/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
248 changes: 248 additions & 0 deletions packages/angular-ivy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
<p align="center">
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
</a>
</p>

# Official Sentry SDK for Angular with Ivy Compatibility

## Links

- [Official SDK Docs](https://docs.sentry.io/platforms/javascript/angular/)

## Angular Version Compatibility

**Note**: This SDK is still experimental and not yet stable.
We do not yet make guarantees in terms of breaking changes, version compatibilities or semver.
Please open a Github issue if you experience bugs or would like to share feedback.

This SDK officially supports Angular 12-15 with Angular's new rendering engine, Ivy.

If you're using Angular 10, 11 or a newer Angular version with View Engine instead of Ivy, please use [`@sentry/angular`](https://github.com/getsentry/sentry-javascript/blob/develop/packages/angular/README.md).

If you're using an older version of Angular and experience problems with the Angular SDK, we recommend downgrading the SDK to version 6.x.
Please note that we don't provide any support for Angular versions below 10.

## General

This package is a wrapper around `@sentry/browser`, with added functionality related to Angular. All methods available
in `@sentry/browser` can be imported from `@sentry/angular-ivy`.

To use this SDK, call `Sentry.init(options)` before you bootstrap your Angular application.

```javascript
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { init } from '@sentry/angular-ivy';

import { AppModule } from './app/app.module';

init({
dsn: '__DSN__',
// ...
});

// ...

enableProdMode();
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(success => console.log(`Bootstrap success`))
.catch(err => console.error(err));
```

### ErrorHandler

`@sentry/angular-ivy` exports a function to instantiate an ErrorHandler provider that will automatically send Javascript errors
captured by the Angular's error handler.

```javascript
import { NgModule, ErrorHandler } from '@angular/core';
import { createErrorHandler } from '@sentry/angular-ivy';

@NgModule({
// ...
providers: [
{
provide: ErrorHandler,
useValue: createErrorHandler({
showDialog: true,
}),
},
],
// ...
})
export class AppModule {}
```

Additionally, `createErrorHandler` accepts a set of options that allows you to configure its behavior. For more details
see `ErrorHandlerOptions` interface in `src/errorhandler.ts`.

### Tracing

`@sentry/angular-ivy` exports a Trace Service, Directive and Decorators that leverage the `@sentry/tracing` Tracing
integration to add Angular related spans to transactions. If the Tracing integration is not enabled, this functionality
will not work. The service itself tracks route changes and durations, where directive and decorators are tracking
components initializations.

#### Install

Registering a Trace Service is a 3-step process.

1. Register and configure the `BrowserTracing` integration from `@sentry/tracing`, including custom Angular routing
instrumentation:

```javascript
import { init, instrumentAngularRouting } from '@sentry/angular-ivy';
import { Integrations as TracingIntegrations } from '@sentry/tracing';

init({
dsn: '__DSN__',
integrations: [
new TracingIntegrations.BrowserTracing({
tracingOrigins: ['localhost', 'https://yourserver.io/api'],
routingInstrumentation: instrumentAngularRouting,
}),
],
tracesSampleRate: 1,
});
```

2. Register `SentryTrace` as a provider in Angular's DI system, with a `Router` as its dependency:

```javascript
import { NgModule } from '@angular/core';
import { Router } from '@angular/router';
import { TraceService } from '@sentry/angular-ivy';

@NgModule({
// ...
providers: [
{
provide: TraceService,
deps: [Router],
},
],
// ...
})
export class AppModule {}
```

3. Either require the `TraceService` from inside `AppModule` or use `APP_INITIALIZER` to force-instantiate Tracing.

```javascript
@NgModule({
// ...
})
export class AppModule {
constructor(trace: TraceService) {}
}
```

or

```javascript
import { APP_INITIALIZER } from '@angular/core';

@NgModule({
// ...
providers: [
{
provide: APP_INITIALIZER,
useFactory: () => () => {},
deps: [TraceService],
multi: true,
},
],
// ...
})
export class AppModule {}
```

#### Use

To track Angular components as part of your transactions, you have 3 options.

_TraceDirective:_ used to track a duration between `OnInit` and `AfterViewInit` lifecycle hooks in template:

```javascript
import { TraceModule } from '@sentry/angular-ivy';

@NgModule({
// ...
imports: [TraceModule],
// ...
})
export class AppModule {}
```

Then inside your components template (keep in mind that directive name attribute is required):

```html
<app-header trace="header"></app-header>
<articles-list trace="articles-list"></articles-list>
<app-footer trace="footer"></app-footer>
```

_TraceClassDecorator:_ used to track a duration between `OnInit` and `AfterViewInit` lifecycle hooks in components:

```javascript
import { Component } from '@angular/core';
import { TraceClassDecorator } from '@sentry/angular-ivy';

@Component({
selector: 'layout-header',
templateUrl: './header.component.html',
})
@TraceClassDecorator()
export class HeaderComponent {
// ...
}
```

_TraceMethodDecorator:_ used to track a specific lifecycle hooks as point-in-time spans in components:

```javascript
import { Component, OnInit } from '@angular/core';
import { TraceMethodDecorator } from '@sentry/angular-ivy';

@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
})
export class FooterComponent implements OnInit {
@TraceMethodDecorator()
ngOnInit() {}
}
```

You can also add your own custom spans by attaching them to the current active transaction using `getActiveTransaction`
helper. For example, if you'd like to track the duration of Angular boostraping process, you can do it as follows:

```javascript
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { init, getActiveTransaction } from '@sentry/angular-ivy';

import { AppModule } from './app/app.module';

// ...

const activeTransaction = getActiveTransaction();
const boostrapSpan =
activeTransaction &&
activeTransaction.startChild({
description: 'platform-browser-dynamic',
op: 'ui.angular.bootstrap',
});

platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => console.log(`Bootstrap success`))
.catch(err => console.error(err));
.finally(() => {
if (bootstrapSpan) {
boostrapSpan.finish();
}
})
```
30 changes: 30 additions & 0 deletions packages/angular-ivy/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* To learn more about this file see: https://angular.io/guide/workspace-config */
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1, // version of angular.json
"projects": {
"sentry-angular-ivy": {
"projectType": "library",
"root": ".",
"sourceRoot": "src",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "tsconfig.ngc.json"
},
"development": {
"tsConfig": "tsconfig.ngc.json"
}
},
"defaultConfiguration": "production"
}
}
}
},
"defaultProject": "sentry-angular-ivy"
}
13 changes: 13 additions & 0 deletions packages/angular-ivy/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "node_modules/ng-packagr/ng-package.schema.json",
"dest": "build",
"lib": {
"entryFile": "src/index.ts",
"umdModuleIds": {
"@sentry/browser": "Sentry",
"@sentry/utils": "Sentry.util"
}
},
"allowedNonPeerDependencies": ["@sentry/browser", "@sentry/utils", "@sentry/types", "tslib"],
"assets": ["README.md", "LICENSE"]
}
65 changes: 65 additions & 0 deletions packages/angular-ivy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"name": "@sentry/angular-ivy",
"version": "7.38.0",
"description": "Official Sentry SDK for Angular with full Ivy Support",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular-ivy",
"author": "Sentry",
"license": "MIT",
"engines": {
"node": ">=12"
},
"main": "build/bundles/sentry-angular.umd.js",
"module": "build/fesm2015/sentry-angular.js",
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"@angular/common": ">= 12.x <= 15.x",
"@angular/core": ">= 12.x <= 15.x",
"@angular/router": ">= 12.x <= 15.x",
"rxjs": "^6.5.5 || ^7.x"
},
"dependencies": {
"@sentry/browser": "7.38.0",
"@sentry/types": "7.38.0",
"@sentry/utils": "7.38.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "~12.2.18",
"@angular/cli": "~12.2.18",
"@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0",
"@angular/compiler-cli": "~12.2.0",
"@angular/core": "~12.2.0",
"@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~12.2.0",
"ng-packagr": "^12.1.1",
"typescript": "~4.3.5",
"zone.js": "~0.11.4"
},
"scripts": {
"build": "yarn build:syncSymlinks && yarn build:transpile",
"build:transpile": "ng build",
"build:dev": "yarn build",
"build:watch": "yarn build:syncSymlinks && yarn build:transpile:watch",
"build:dev:watch": "yarn build:watch",
"build:transpile:watch": "ng build --watch",
"build:tarball": "npm pack ./build",
"build:syncSymlinks": "ts-node ./scripts/syncSourceFiles.ts",
"circularDepCheck": "madge --circular src/index.ts",
"clean": "rimraf build coverage sentry-angular-ivy-*.tgz",
"fix": "run-s fix:eslint fix:prettier",
"fix:eslint": "eslint . --format stylish --fix",
"fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
"lint": "run-s lint:prettier lint:eslint",
"lint:eslint": "eslint . --format stylish",
"lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\""
},
"volta": {
"extends": "../../package.json"
},
"sideEffects": false
}
Loading

0 comments on commit cca2514

Please sign in to comment.