Desenvolva sua biblioteca ou aplicação Angular 8+ utilizando entry points secundários, um recurso que irá possibilitar a quebra de components em sua biblioteca e uma melhor organização.
Requisitos básicos
npm v8.0.0
Repositório
https://github.com/JeronimoTeixeira/entry-points
Criando o projeto Angular
Para esse artigo nós utilizaremos o nx.
Então vamos lá!
Para criar o projeto execute o comando abaixo.
npx create-nx-workspace@latest
E responda as perguntas conforme o padrão abaixo.
> NX Let's create a new workspace [https://nx.dev/getting-started/intro]
√ Choose your style · integrated
√ What to create in the new workspace · angular
√ Repository name · sub-entry-points
√ Application name · sub-entry-points
√ Default stylesheet format · scss
√ Enable distributed caching to make your CI faster · No
Olha que legal, agora você tem a seguinte estrutura de pastas.
sub-entry-points/
├── apps/
│ ├── sub-entry-points/
│ │ ├── src/
│ │ │ ├── app/
│ │ │ ├── assets/
│ │ │ ├── environments/
│ │ │ ├── favicon.ico
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ ├── polyfills.ts
│ │ │ ├── styles.scss
│ │ │ └── test-setup.ts
│ │ ├── .browserslistrc
│ │ ├── .eslintrc.json
│ │ ├── jest.config.ts
│ │ ├── project.json
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.editor.json
│ │ ├── tsconfig.json
│ │ └── tsconfig.spec.json
│ └── todos-e2e/
│ ├── src/
│ │ ├── fixtures/
│ │ │ └── example.json
│ │ ├── e2e/
│ │ │ └── app.cy.ts
│ │ └── support/
│ │ ├── app.po.ts
│ │ ├── commands.ts
│ │ └── e2e.ts
│ ├── cypress.config.ts
│ ├── project.json
│ └── tsconfig.json
├── libs/
├── tools/
├── .eslintrc.json
├── .prettierrc
├── angular.json
├── decorate-angular-cli.js
├── jest.config.ts
├── jest.preset.js
├── nx.json
├── package.json
├── README.md
└── tsconfig.base.json
Mas vamos focar mais especificamente na pasta libs. É nesta pasta que iremos criar nossos entry points.
Mas antes, o que é um sub entry point?
Para a nossa aplicação iremos criar uma biblioteca simples que contenha nela entry points secundários, mas primeiro vamos entender um pouco do que seria esses um entry point secundário.
Vamos supor que algum usuário queira utilizar nossa biblioteca por inteiro, então ele importará nosso modulo principal da seguinte forma.
import { EntryPointModule } from @entry-point-module
Desse modo ele obterá nosso pacote principal. Perceba que @entry-point-module é nosso entry point principal, e não um entry point secundário.
Só que agora em outra aplicação outro usuário só quer utilizar o nosso modulo feature, então ele irá importar da seguinte forma.
import { EntryPointTesteModule } from @entry-point-module/feature
Prontinho, o usuário está utilizando nosso entry point secundário. Aqui nós temos @entry-point-module/teste e como nosso entry point secundário.
Agora vamos voltar a por a mão na massa!!!
Criando entry points
Criando a biblioteca principal
Então o primeiro passo é criar a biblioteca principal com o seguinte comando.
npx nx g lib <nome-lib> --publishable --importPath <import-path>
No qual, iremos preencher da seguinte forma
npx nx g lib entry-points --publishable --importPath @entry-points
publishable
Se sua intenção é distribuir sua biblioteca fora do monorepo, por exemplo, se desejar publicar sua biblioteca em algum lugar para ser instalada com npm install, utilize o publishable.
buildable
Se sua intenção é somente utilizar em sua aplicação e não publica-la, por exemplo, se desejar utiliza-la em algum monorepo, utilize buildable.
Perfeito!! Agora você tem uma biblioteca dentro de libs com a seguinte estrutura.
libs/
├── entry-points/
│ ├── src/
│ ├── ├── lib/
│ ├── ├── ├── entry-points.module.ts
│ ├── ├── index.ts
| ├── ng-package.json
└── └── ng-package.json
Agora veja em seu arquivo tsconfig.base.json temos o seu entry point dentro de paths @entry-points.
{
"compileOnSave": false,
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"module": "esnext",
"lib": ["es2017", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@entry-points": ["libs/entry-points/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
}
E também em index.ts temos o seguinte codigo.
export * from './lib/entry-points.module';
Nele encontra-se todos os arquivos que serão expostos ao importar o entry point @entry-points. Neste arquivo podemos colocar components, services, directivas, entre outros, mas vamos utilizar somente a module para facilitar o entendimento.
Veja também o arquivo ng-package.json, nele temos referenciado o arquivo de exportação index.ts utilizado para o entry point @entry-points. Este arquivo é importante pois é com ele que o build do Angular reconhece o arquivo index.ts.
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/libs/entry-points",
"lib": {
"entryFile": "src/index.ts"
}
}
Criando o primeiro sub entry point
Utilize o comando a seguir para criar sua lib secundária, vamos chama-la de feature.
npx nx g lib entry-points/feature --importPath @entry-points/feature
Perfeito, agora temos um sub entry point que será buildado ao mesmo tempo que o entry point principal.
Veja que agora a estrutura de pastas ficou a seguinte
libs/
├── entry-points/
│ ├── feature/
│ ├── ├── src/
│ ├── ├── ├── lib/
│ ├── ├── ├── ├── entry-points-feature.module.ts
│ ├── ├── ├── index.ts
│ ├── ├── project.json
│ ├── src/
│ ├── ├── lib/
│ ├── ├── ├── entry-points.module.ts
│ ├── ├── index.ts
| ├── ng-package.json
└── └── project.json
É importante entender que caso esteja criando o entry point secundário fora da biblioteca principal, você poderá receber um erro em momento de build pois com esse método você não está mais criando um entry point secundário e sim outra biblioteca principal com um entry point principal. O intuito do entry point secundário é ter diferentes modulos da biblioteca integradas na biblioteca principal ou então disponíveis no momento que ocorrer a instalação da biblioteca principal. Vamos entender isso nos próximos tópicos.
Criando um component para sua feature
Agora iremos criar um component na feature para que possamos utilizar na biblioteca principal, para isso utilize o comando abaixo.
npx nx g c component --project entry-points-feature
Agora vamos expor esse component no index.ts e também na module da feature da seguinte forma.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ComponentComponent } from './component/component.component';
@NgModule({
imports: [CommonModule],
declarations: [ComponentComponent],
exports: [ComponentComponent]
})
export class EntryPointsFeatureModule {}
export * from './lib/entry-points-feature.module';
export * from './lib/component/component.component';
E por fim vamos importar esse component dentro da nossa biblioteca principal. Desse modo, dentro de entry-points.module.ts faça o seguinte.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EntryPointsFeatureModule } from '@entry-points/feature';
@NgModule({
imports: [CommonModule, EntryPointsFeatureModule],
})
export class EntryPointsModule {}
Também crie um component na biblioteca principal para englobar o component criado dentro de feature.
npx nx g c component-principal --project entry-points
No arquivo component-principal.component.html coloque a tag do component criado dentro de feature da seguinte forma.
<sub-entry-point-component></sub-entry-point-component>
Hora do build
Certooo!! Está acabando, vamos buildar.
npx nx build --project entry-points
Infelizmente ocorreu o seguinte erro:
Building Angular Package
> NX Entry point @entry-points/feature which is required by @entry-points doesn't exists.
Pass --verbose to see the stacktrace.
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Ran target build for project entry-points (2s)
× 1/1 failed
√ 0/1 succeeded [0 read from cache]
Esse erro ocorre pois está faltando um arquivo crucial para o build dos entry points secundários. Então vá até sua feature e crie o arquivo ng-package.json com o seguinte conteúdo.
{
"lib": {
"entryFile": "src/index.ts"
}
}
E vamos buildar novamente.
Building Angular Package
------------------------------------------------------------------------------
Building entry point '@entry-points/feature'
------------------------------------------------------------------------------
✔ Compiling with Angular sources in Ivy partial compilation mode.
✔ Writing FESM bundles
✔ Built @entry-points/feature
------------------------------------------------------------------------------
Building entry point '@entry-points'
------------------------------------------------------------------------------
✔ Compiling with Angular sources in Ivy partial compilation mode.
✔ Writing FESM bundles
✔ Copying assets
✔ Writing package manifest
✔ Built @entry-points
------------------------------------------------------------------------------
Built Angular Package
- from: C:\Users\Jeronimo\Desktop\teste\sub-entry-point\libs\entry-points
- to: C:\Users\Jeronimo\Desktop\teste\sub-entry-point\dist\libs\entry-points
------------------------------------------------------------------------------
Build at: 2022-11-18T02:35:59.607Z - Time: 2018ms
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
> NX Successfully ran target build for project entry-points (4s)
Veja que primeiro ocorre o build do entry point secundário e por fim o build da biblioteca principal. Caso o entry point esteja fora da biblioteca principal, o build entenderá que é uma dependencia e não uma biblioteca/ entry point secundário.
Com esse padrão é possível reutilizar componentes, services, diretivas de um certo entry point secundário em outro entry point secundário ou até mesmo no entry point principal.
Além disso, o usuário que utilizar essa biblioteca pode optar por usar somente um dos entry point secundários e não a biblioteca por inteira.
Conclusão
Bom, você decide qual padrão é melhor para ser usado. Tudo vai depender da sua aplicação e do que está buscando criar.
Mas é isso!! Obrigado pela atenção!!!