-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add migration for self-closing tags (#416)
docs: add docs for self-closing-tags migration Co-authored-by: Chau Tran <[email protected]>
- Loading branch information
Showing
10 changed files
with
640 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
docs/src/content/docs/utilities/Migrations/self-closing-tags.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
--- | ||
title: Self Closing Tags Migration | ||
description: Schematics for migrating non-self-closing tags to self-closing tags. | ||
entryPoint: convert-to-self-closing-tag | ||
badge: stable | ||
contributors: ['enea-jahollari'] | ||
--- | ||
|
||
Angular supports self-closing tags. This means that you can write tags like `<app-component />` instead of `<app-component></app-component>`. | ||
This is a feature that was introduced in Angular 16. | ||
|
||
### How it works? | ||
|
||
The moment you run the schematics, it will look for all the tags that are not self-closing and convert them to self-closing tags. | ||
|
||
- It will look for all the tags that don't have any content inside them. | ||
- It will only convert components that have "-" in their name. | ||
|
||
### Usage | ||
|
||
In order to run the schematics for all the project in the app you have to run the following script: | ||
|
||
```bash | ||
ng g ngxtension:convert-to-self-closing-tag | ||
``` | ||
|
||
If you want to specify the project name you can pass the `--project` param. | ||
|
||
```bash | ||
ng g ngxtension:convert-to-self-closing-tag --project=<project-name> | ||
``` | ||
|
||
If you want to run the schematic for a specific component or directive you can pass the `--path` param. | ||
|
||
```bash | ||
ng g ngxtension:convert-to-self-closing-tag --path=<path-to-ts-file> | ||
``` | ||
|
||
### Usage with Nx | ||
|
||
To use the schematics on a Nx monorepo you just swap `ng` with `nx` | ||
|
||
Example: | ||
|
||
```bash | ||
nx g ngxtension:convert-to-self-closing-tag --project=<project-name> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
libs/plugin/src/generators/convert-to-self-closing-tag/__snapshots__/generator.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`convertToSelfClosingTagGenerator should convert properly for inline template 1`] = ` | ||
" | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<router-outlet /> | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
" | ||
`; | ||
exports[`convertToSelfClosingTagGenerator should convert properly for inline template 2`] = `undefined`; | ||
exports[`convertToSelfClosingTagGenerator should convert properly for templateUrl 1`] = ` | ||
" | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
templateUrl: './my-file.html' | ||
}) | ||
export class MyCmp { | ||
} | ||
" | ||
`; | ||
exports[`convertToSelfClosingTagGenerator should convert properly for templateUrl 2`] = ` | ||
" | ||
<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
<app-my-cmp2 test="hello">123</app-my-cmp2> | ||
<app-my-cmp3 | ||
test="hello"> | ||
123 | ||
</app-my-cmp3> | ||
<app-my-cmp4 | ||
test="hello" | ||
> | ||
123 | ||
</app-my-cmp4> | ||
<app-my-cmp5 | ||
test="hello" | ||
> | ||
123 | ||
</app-my-cmp5> | ||
<app-my-cmp10 test="hello" | ||
[test]="hello" | ||
(test)="hello()" | ||
/> | ||
<app-my-cmp11 test="hello" | ||
[test]="hello" | ||
(test)="hello()" | ||
/> | ||
<app-my-cmp12 test="hello" | ||
/> | ||
<input type="text" /> | ||
<app-my-cmp6 /> | ||
<app-my-cmp7 test="hello" /> | ||
<hello-world /> | ||
<pagination count="1" [test]="hello" (test)="test"></pagination> | ||
<pagination count="1" /> | ||
<hello-world /> | ||
<hello-world12> | ||
<hello-world13> | ||
<hello-world14 count="1" [test]="hello" (test)="test" /> | ||
<hello-world15> | ||
<hello-world16 count="1" [test]="hello" (test)="test" /> | ||
<hello-world17 count="1" [test]="hello" (test)="test" /> | ||
<hello-world18 count="1" [test]="hello" | ||
(test)="test" | ||
/> | ||
</hello-world15> | ||
</hello-world13> | ||
</hello-world12> | ||
<app-management *ngIf=" | ||
categoryList && | ||
((test1 && test1.length > 0) || | ||
(test && test.length > 0)) | ||
" | ||
[test]="test > 2" | ||
[test]="test" | ||
(testEvent)="test.length > 0 ? test($event) : null" | ||
(testEvent2)="test1($event)" /> | ||
" | ||
`; |
4 changes: 4 additions & 0 deletions
4
libs/plugin/src/generators/convert-to-self-closing-tag/compat.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { convertNxGenerator } from '@nx/devkit'; | ||
import convertGenerator from './generator'; | ||
|
||
export default convertNxGenerator(convertGenerator); |
178 changes: 178 additions & 0 deletions
178
libs/plugin/src/generators/convert-to-self-closing-tag/generator.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { Tree } from '@nx/devkit'; | ||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; | ||
|
||
import convertToSelfClosingTagGenerator from './generator'; | ||
import { ConvertToSelfClosingTagGeneratorSchema } from './schema'; | ||
|
||
const template = ` | ||
<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
<app-my-cmp2 test="hello">123</app-my-cmp2> | ||
<app-my-cmp3 | ||
test="hello"> | ||
123 | ||
</app-my-cmp3> | ||
<app-my-cmp4 | ||
test="hello" | ||
> | ||
123 | ||
</app-my-cmp4> | ||
<app-my-cmp5 | ||
test="hello" | ||
> | ||
123 | ||
</app-my-cmp5> | ||
<app-my-cmp10 test="hello" | ||
[test]="hello" | ||
(test)="hello()" | ||
> | ||
</app-my-cmp10> | ||
<app-my-cmp11 | ||
test="hello" | ||
[test]="hello" | ||
(test)="hello()" | ||
> | ||
</app-my-cmp11> | ||
<app-my-cmp12 test="hello" | ||
> | ||
</app-my-cmp12> | ||
<input type="text" /> | ||
<app-my-cmp6 /> | ||
<app-my-cmp7 test="hello" /> | ||
<hello-world></hello-world> | ||
<pagination count="1" [test]="hello" (test)="test"></pagination> | ||
<pagination count="1" /> | ||
<hello-world></hello-world> | ||
<hello-world12> | ||
<hello-world13> | ||
<hello-world14 count="1" [test]="hello" (test)="test" ></hello-world14> | ||
<hello-world15> | ||
<hello-world16 count="1" [test]="hello" (test)="test" /> | ||
<hello-world17 count="1" [test]="hello" (test)="test" ></hello-world17> | ||
<hello-world18 | ||
count="1" [test]="hello" | ||
(test)="test" | ||
> | ||
</hello-world18> | ||
</hello-world15> | ||
</hello-world13> | ||
</hello-world12> | ||
<app-management | ||
*ngIf=" | ||
categoryList && | ||
((test1 && test1.length > 0) || | ||
(test && test.length > 0)) | ||
" | ||
[test]="test > 2" | ||
[test]="test" | ||
(testEvent)="test.length > 0 ? test($event) : null" | ||
(testEvent2)="test1($event)"></app-management> | ||
`; | ||
|
||
const filesMap = { | ||
notComponent: ` | ||
import { Injectable } from '@angular/core'; | ||
@Injectable() | ||
export class MyService {} | ||
`, | ||
componentNoTemplate: ` | ||
import { Component } from '@angular/core'; | ||
@Component({}) | ||
export class MyCmp {} | ||
`, | ||
|
||
componentWithTemplateUrl: ` | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
templateUrl: './my-file.html' | ||
}) | ||
export class MyCmp { | ||
} | ||
`, | ||
|
||
componentWithInlineTemplate: ` | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<router-outlet></router-outlet> | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
`, | ||
} as const; | ||
|
||
describe('convertToSelfClosingTagGenerator', () => { | ||
let tree: Tree; | ||
const options: ConvertToSelfClosingTagGeneratorSchema = { | ||
path: 'libs/my-file.ts', | ||
}; | ||
|
||
function setup(file: keyof typeof filesMap) { | ||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); | ||
tree.write('package.json', `{"dependencies": {"@angular/core": "17.1.0"}}`); | ||
tree.write(`libs/my-file.ts`, filesMap[file]); | ||
|
||
if (file === 'componentWithTemplateUrl') { | ||
tree.write(`libs/my-file.html`, template); | ||
return () => { | ||
return [ | ||
tree.read('libs/my-file.ts', 'utf8'), | ||
filesMap[file], | ||
tree.read('libs/my-file.html', 'utf8'), | ||
template, | ||
]; | ||
}; | ||
} | ||
|
||
return () => { | ||
return [tree.read('libs/my-file.ts', 'utf8'), filesMap[file]]; | ||
}; | ||
} | ||
|
||
it('should not do anything if not component/directive', async () => { | ||
const readContent = setup('notComponent'); | ||
await convertToSelfClosingTagGenerator(tree, options); | ||
const [updated, original] = readContent(); | ||
expect(updated).toEqual(original); | ||
}); | ||
|
||
it('should not do anything if no template', async () => { | ||
const readContent = setup('componentNoTemplate'); | ||
await convertToSelfClosingTagGenerator(tree, options); | ||
const [updated, original] = readContent(); | ||
expect(updated).toEqual(original); | ||
}); | ||
|
||
it('should convert properly for templateUrl', async () => { | ||
const readContent = setup('componentWithTemplateUrl'); | ||
await convertToSelfClosingTagGenerator(tree, options); | ||
const [updated, , updatedHtml] = readContent(); | ||
expect(updated).toMatchSnapshot(); | ||
expect(updatedHtml).toMatchSnapshot(); | ||
}); | ||
|
||
it('should convert properly for inline template', async () => { | ||
const readContent = setup('componentWithInlineTemplate'); | ||
await convertToSelfClosingTagGenerator(tree, options); | ||
const [updated, , updatedHtml] = readContent(); | ||
expect(updated).toMatchSnapshot(); | ||
expect(updatedHtml).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.