-
Notifications
You must be signed in to change notification settings - Fork 87
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 sfc components (#420)
* feat: add migration schematics to convert components to Single File Components * fix(plugin): linting errors
- Loading branch information
Showing
10 changed files
with
573 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
58 changes: 58 additions & 0 deletions
58
docs/src/content/docs/utilities/Migrations/sfc-migration.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,58 @@ | ||
--- | ||
title: Convert to SFC components migration | ||
description: Schematics for converting Angular components to SFC components | ||
entryPoint: convert-to-sfc | ||
badge: stable | ||
contributors: ['enea-jahollari'] | ||
--- | ||
|
||
Angular components can have inline templates or have a separate template file. The inline templates are called SFC (Single File Components) and are a common practice in modern Angular applications. | ||
This schematic helps you convert your Angular components to SFC components. | ||
|
||
### How it works? | ||
|
||
The moment you run the schematics, it will look for all the components in your project and will convert them to SFC components. | ||
|
||
- It will move the template from the `templateUrl` to the `template` property. | ||
- It will move the styles from the `styleUrls` to the `styles` property. | ||
- The maximum lines length for the template is set to 200 lines. If the template has more than 200 lines, it will be skipped. | ||
|
||
In order to change the maximum line length, you can pass the `--max-inline-template-lines` param to the schematics. For styles, you can pass the `--max-inline-style-lines` param. | ||
|
||
``bash | ||
|
||
### 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-sfc | ||
``` | ||
|
||
If you want to specify the project name you can pass the `--project` param. | ||
|
||
```bash | ||
ng g ngxtension:convert-to-sfc --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-sfc --path=<path-to-ts-file> | ||
``` | ||
|
||
If you want to change the maximum line length for the template or styles you can pass the `--max-inline-template-lines` param or `--max-inline-style-lines` param. | ||
|
||
```bash | ||
ng g ngxtension:convert-to-sfc --max-inline-template-lines=100 --max-inline-style-lines=100 | ||
``` | ||
|
||
### Usage with Nx | ||
|
||
To use the schematics on a Nx monorepo you just swap `ng` with `nx` | ||
|
||
Example: | ||
|
||
```bash | ||
nx g ngxtension:convert-to-sfc --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
88 changes: 88 additions & 0 deletions
88
libs/plugin/src/generators/convert-to-sfc/__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,88 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`convertToSFCGenerator should convert properly for templateUrl 1`] = ` | ||
" | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
" | ||
`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl 2`] = `null`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl and styleUrl 1`] = ` | ||
" | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
\`, | ||
styles: \` | ||
h1 { | ||
color: red; | ||
} | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
" | ||
`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl and styleUrl 2`] = `null`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl and styleUrl 3`] = ` | ||
"<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1>" | ||
`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl and styleUrls 1`] = ` | ||
" | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1> | ||
\`, | ||
styles: \` | ||
h1 { | ||
color: red; | ||
} | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
" | ||
`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl and styleUrls 2`] = `null`; | ||
exports[`convertToSFCGenerator should convert properly for templateUrl and styleUrls 3`] = ` | ||
"<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1>" | ||
`; | ||
exports[`convertToSFCGenerator should skip components with inline templates 1`] = ` | ||
" | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<router-outlet></router-outlet> | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
" | ||
`; | ||
exports[`convertToSFCGenerator should skip components with inline templates 2`] = `undefined`; |
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); |
154 changes: 154 additions & 0 deletions
154
libs/plugin/src/generators/convert-to-sfc/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,154 @@ | ||
import { Tree } from '@nx/devkit'; | ||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; | ||
|
||
import convertToSFCGenerator from './generator'; | ||
import { ConvertToSFCGeneratorSchema } from './schema'; | ||
|
||
const template = `<div>Hello</div> | ||
<app-my-cmp1>123</app-my-cmp1>`; | ||
|
||
const styles = `h1 { | ||
color: red; | ||
}`; | ||
|
||
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 { | ||
} | ||
`, | ||
componentWithTemplateUrlAndStyleUrls: ` | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
templateUrl: './my-file.html', | ||
styleUrls: ['./my-file.css'] | ||
}) | ||
export class MyCmp { | ||
} | ||
`, | ||
componentWithTemplateUrlAndStyleUrl: ` | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
templateUrl: './my-file.html', | ||
styleUrl: './my-file.css' | ||
}) | ||
export class MyCmp { | ||
} | ||
`, | ||
componentWithInlineTemplate: ` | ||
import { Component, Input } from '@angular/core'; | ||
@Component({ | ||
template: \` | ||
<router-outlet></router-outlet> | ||
\` | ||
}) | ||
export class MyCmp { | ||
} | ||
`, | ||
} as const; | ||
|
||
describe('convertToSFCGenerator', () => { | ||
let tree: Tree; | ||
const options: ConvertToSFCGeneratorSchema = { | ||
path: 'libs/my-file.ts', | ||
moveStyles: true, | ||
maxInlineTemplateLines: 10, | ||
maxInlineStyleLines: 10, | ||
}; | ||
|
||
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]); | ||
tree.write(`libs/my-file.css`, styles); | ||
|
||
if ( | ||
file === 'componentWithTemplateUrl' || | ||
file === 'componentWithTemplateUrlAndStyleUrls' || | ||
file === 'componentWithTemplateUrlAndStyleUrl' | ||
) { | ||
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, | ||
tree.read('libs/my-file.css', 'utf8'), | ||
]; | ||
}; | ||
} | ||
|
||
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 convertToSFCGenerator(tree, options); | ||
const [updated, original] = readContent(); | ||
expect(updated).toEqual(original); | ||
}); | ||
|
||
it('should not do anything if no template', async () => { | ||
const readContent = setup('componentNoTemplate'); | ||
await convertToSFCGenerator(tree, options); | ||
const [updated, original] = readContent(); | ||
expect(updated).toEqual(original); | ||
}); | ||
|
||
it('should convert properly for templateUrl', async () => { | ||
const readContent = setup('componentWithTemplateUrl'); | ||
await convertToSFCGenerator(tree, options); | ||
const [updated, , updatedHtml] = readContent(); | ||
expect(updated).toMatchSnapshot(); | ||
expect(updatedHtml).toMatchSnapshot(); | ||
}); | ||
|
||
it('should convert properly for templateUrl and styleUrls', async () => { | ||
const readContent = setup('componentWithTemplateUrlAndStyleUrls'); | ||
await convertToSFCGenerator(tree, options); | ||
const [updated, , updatedHtml, updatedStyles] = readContent(); | ||
expect(updated).toMatchSnapshot(); | ||
expect(updatedHtml).toMatchSnapshot(); | ||
expect(updatedStyles).toMatchSnapshot(); | ||
}); | ||
|
||
it('should convert properly for templateUrl and styleUrl', async () => { | ||
const readContent = setup('componentWithTemplateUrlAndStyleUrl'); | ||
await convertToSFCGenerator(tree, options); | ||
const [updated, , updatedHtml, updatedStyles] = readContent(); | ||
expect(updated).toMatchSnapshot(); | ||
expect(updatedHtml).toMatchSnapshot(); | ||
expect(updatedStyles).toMatchSnapshot(); | ||
}); | ||
|
||
it('should skip components with inline templates', async () => { | ||
const readContent = setup('componentWithInlineTemplate'); | ||
await convertToSFCGenerator(tree, options); | ||
const [updated, , updatedHtml] = readContent(); | ||
expect(updated).toMatchSnapshot(); | ||
expect(updatedHtml).toMatchSnapshot(); | ||
}); | ||
}); |
Oops, something went wrong.