Crie seu pacote de template npm personalizado

Quer agilizar o início dos seus projetos? Já pensou em criar um scaffolding personalizado? Essa técnica pode te poupar um tempão, automatizando várias etapas repetitivas. Imagine não ter que instalar sempre as mesmas dependências, configurar roteadores ou adicionar seus componentes básicos favoritos a cada novo projeto. Com um scaffolding personalizado com npm, tudo isso pode ser automatizado, permitindo que você comece a codificar muito mais rápido.

Neste artigo, vamos explorar como criar seu próprio scaffolding, inspirado em ferramentas como o npm create vite@latest. Vamos abordar desde a criação de um repositório de template até a construção de um pacote npm que gera projetos estruturados com tudo o que você precisa. Prepare-se para otimizar seu fluxo de trabalho e dar um boost na sua produtividade!

O que você realmente quer?

O objetivo principal é reduzir o tempo gasto para iniciar um novo projeto. Pense em quantas vezes você repete os mesmos passos: instalar bibliotecas, configurar ferramentas e adicionar componentes básicos. Para exemplificar, vamos usar o Vue:

  • Instalar Vue
  • Selecionar pacotes extras (Vitest, Vue Router, Pinia)
  • Adicionar Bootstrap (ou Tailwind)
  • Adicionar Sass
  • Adicionar Vuex (se não quiser Pinia)
  • Adicionar gerenciamento de estado simples (se não quiser Vuex)
  • Remover componentes indesejados
  • Adicionar componentes básicos (ex: HomePage para Vue Router)
  • Configurar Vue Router
  • E assim por diante…

Em vez de repetir tudo isso, o ideal seria ter tudo pronto em cada novo projeto. E mais: manter o scaffolding sempre atualizado. Mas como fazer isso?

Antes de começar

Este artigo não é um guia passo a passo, mas sim uma visão geral de como criar um scaffolding personalizado com npm. O código está disponível no GitHub e os pacotes npm também. Experimente os pacotes antes de se aprofundar no tutorial. Veja se essa abordagem atende às suas necessidades e, se sim, continue lendo. Lembre-se: o objetivo é aprender “do jeito difícil”, enfrentando os erros e desafios, para realmente dominar o processo.

Experimente o pacote

Execute o seguinte comando para instalar um template VueJS personalizado para o seu projeto:

npm create @ricciodev/templar@latest

A maneira mais fácil (repositório de template)

Uma forma de criar um scaffolding é criar um repositório no GitHub e marcá-lo como um “template repository“. Isso é feito na aba “Settings“, logo abaixo do nome do repositório.

Agora, você pode criar um novo repositório no GitHub a partir desse template. O novo repositório terá todos os arquivos do template desde o início.

Essa é a maneira mais fácil de ter um scaffolding básico pronto para usar. Os prós são a facilidade de criação e manutenção. O contra é que, se você precisar de muitos scaffoldings diferentes, a gestão pode se tornar um pouco complicada. Mas é possível ir além!

OBSERVAÇÃO
Voltaremos a este método mais tarde.

O jeito npm create

Podemos criar um pacote npm com o único propósito de gerar um projeto já estruturado com todos os arquivos e dependências que precisamos. Vite, Vue e Astro fazem isso. Já usamos essa abordagem antes, agora é a hora de criar nosso próprio pacote!

O comando npm create

O comando npm create <package-spec> é apenas um atalho para npm init <package-spec>. Todas as informações estão na documentação do npm.

Resumindo, a documentação do npm init diz:

npm init <initializer> pode ser usado para configurar um novo ou existente pacote npm. initializer neste caso é um pacote npm chamado create-<initializer>, que será instalado por npm-exec, e então terá seu bin principal executado — presumivelmente criando ou atualizando package.json e executando quaisquer outras operações relacionadas à inicialização.

Isso significa que o nome do nosso pacote terá o prefixo create-. Por exemplo: create-scaffolding-vue ou create-custom-react. Para organizar melhor, podemos publicar o pacote sob um scope para evitar conflitos de nomes (@myorganization/create-react).

Requisitos do pacote

Precisamos definir algumas opções no arquivo package.json.

main / exports

Primeiro, definimos o ponto de entrada usando a chave main. O valor será o caminho para o nosso arquivo principal (geralmente index.js). Mais adiante, vamos substituir main pelo campo exports.

bin

Também precisamos definir a chave bin, que aponta para o nosso arquivo executável. Para o nosso pacote, podemos usar algo assim:

{
  "bin": {
    "create-mypackage-name": "./index.js"
  }
}

Um pacote realmente básico

Para começar, inicialize um projeto usando o comando npm init. Isso criará o scaffolding básico para o nosso pacote e poderemos definir os requisitos que vimos antes.

OBSERVAÇÃO
Se quisermos criar um pacote com scope, este é o momento de informar ao npm, adicionando o nome da organização antes do nome do pacote.
I.E.
@myorg/create-mypackage-name em vez de create-mypackage-name

OBSERVAÇÃO
Ao criar um pacote com scope, o NPM definirá a visibilidade do pacote como privada por padrão. Para publicar o pacote como público, use npm publish –access public.
Mais informações sobre isso podem ser encontradas nas notas no final deste artigo.

Após a inicialização, adicione um novo arquivo chamado index.js (ou main.js, ou qualquer convenção de nomenclatura que você use). Em seguida, defina as chaves main e bin no package.json.

Ambas as chaves precisam ser definidas no nível raiz do seu package.json. A chave main deve apontar para o arquivo que acabamos de criar:

{
    "main": "./index.js"
}

Em seguida, defina a propriedade bin, que aponta para o arquivo executável principal do nosso pacote. Os arquivos definidos aqui serão instalados pelo npm no PATH e estarão disponíveis para o pacote e para o usuário.

A chave bin é apenas um mapa do nome do comando para o nome do arquivo local.

De acordo com a documentação oficial, se tivermos apenas um único executável e seu nome deve ser o nome do pacote, podemos usar uma string como valor para bin:

{
    "bin": "./index.js"
}

Se quisermos ser mais detalhados ou acharmos que precisaremos adicionar mais executáveis no futuro, podemos fornecer um objeto em vez de uma string, com uma chave nomeada como nosso pacote (sem o prefixo @org).

{
    "bin": {
        "create-mypackage-name": "./index.js"
    }
}

Ambos os exemplos são equivalentes no nosso contexto.

Um simples console.log

No nosso index.js, adicione o seguinte código:

#!/usr/bin/env node
console.log("Hello World!");

OBSERVAÇÃO
Certifique-se de que o arquivo referenciado em bin comece com #!/usr/bin/env node, caso contrário, os scripts não serão executados pelo node!

Disponibilizando o pacote globalmente na sua máquina local

Não precisamos publicar nosso pacote para testá-lo. Execute o comando npm link na raiz do projeto para disponibilizar o pacote globalmente no seu sistema.

Em seguida, crie um novo diretório em outro caminho (ex: C:\test) e execute o comando npm link create-mypackage-name ou npm link @myorg/create-mypackage-name se você criou um pacote com scope. Agora, nosso pacote estará disponível no diretório atual.

Para garantir que tudo funciona, digite o seguinte comando no terminal (na pasta de teste): npx create-mypackage-name.

OBSERVAÇÃO
npx não é um erro de digitação, é o comando para executar comandos arbitrários de pacotes locais ou remotos.
Documentação do NPM – npx

O nome do comando é o mesmo que definimos na propriedade bin em package.json. Se você vir o console.log no terminal, tudo está funcionando!

OBSERVAÇÃO
Agora pode ser um bom momento para salvar nosso código em um repositório no GitHub e publicar nosso pacote.
Dessa forma, podemos tentar instalá-lo usando o comando npm create @myorg/mypackage-name@latest e ver se tudo está ok.
Se virmos a mensagem console.log após executar nosso comando, estamos no caminho certo!

Ao criar um pacote com scope, o NPM definirá a visibilidade do pacote como privada por padrão. Para publicar o pacote como público, use npm publish –access public.
Mais informações sobre isso podem ser encontradas nos seguintes links.

Neste tutorial, não abordaremos como publicar um pacote, mas os seguintes links certamente ajudarão você:
Documentação do NPM – Criando e publicando pacotes públicos sem scope
Documentação do NPM – Criando e publicando pacotes públicos com scope
freeCodeCamp – Como criar e publicar seu primeiro pacote NPM – um guia passo a passo

Como gerenciar um scaffolding mais complexo

Antes de criar o pacote de scaffolding, é importante entender como editar o package.json para organizar melhor o código e os arquivos.

Imagine que queremos criar uma função utilitária em um arquivo chamado toUppercase.js dentro da pasta utils.

Estrutura:

index.js
package.json
utils
    toUppercase.js

Queremos usar o módulo toUppercase.js dentro de index.js. Para isso, precisamos importar o arquivo no início de index.js, mas isso não será suficiente e gerará um erro ERR_MODULE_NOT_FOUND.

Se você publicou seu pacote ou o disponibilizou globalmente na sua máquina local, vá em frente e tente.

Para evitar esse erro, devemos atualizar nosso package.json para exportar todos os arquivos que precisamos e alterar a importação dentro de index.js para ser consistente com o nome do nosso pacote.

No package.json, adicione a propriedade exports:

{
    "exports": {
        ".": "./index.js",
        "./utils/*.js": "./utils/*.js"
    },
}

A documentação oficial afirma que:

O “exports” fornece uma alternativa moderna para “main”, permitindo que múltiplos pontos de entrada sejam definidos, suporte à resolução condicional de entrada entre ambientes e impedindo quaisquer outros pontos de entrada além daqueles definidos em “exports”.

Documentação do NPM – exports

Isso significa que não precisaremos mais do campo main. No campo exports, definimos:

  • o ponto de entrada padrão index.js
  • todos os outros pontos de entrada dentro da pasta utils

Em index.js, atualizamos nossa importação auto referenciando nosso pacote usando seu nome.
De:

import toUppercase from './utils/toUppercase.js';

Para:

import toUppercase from '@myorg/mypackage-name/utils/toUppercase.js';

Este não é um passo necessário e só funciona se definirmos o exports dentro de package.json.

Como definimos a propriedade em exports com a extensão .js, devemos lembrar de declarar explicitamente a extensão ao importar nossos módulos.

Isso gerará um erro:

import toUppercase from '@myorg/mypackage-name/utils/toUppercase'; // <-- SEM EXTENSÃO

Isso funcionará:

import toUppercase from '@myorg/mypackage-name/utils/toUppercase.js'; // <-- COM EXTENSÃO

OBSERVAÇÃO
Se já publicamos nosso pacote, precisamos atualizar seu valor de patch e publicá-lo novamente.
npm version patch atualizará a versão (ex: de 0.0.1 para 0.0.2).
npm publish publicará a versão atualizada do pacote, para que possamos instalá-lo usando npm create @myorg/mypackage-name@latest ou npm create @myorg/[email protected]

Ainda podemos testar o pacote localmente usando o comando npm link como antes.

Um pacote de scaffolding simples

Agora é a hora de criar nosso pacote de scaffolding personalizado para iniciar nossos projetos! Precisamos dedicar alguns minutos para pensar em como queremos que nossos projetos sejam desenvolvidos:

  • Qual framework queremos usar?
    • Ex: React? Vue? Astro?
  • Quais são os pacotes que usamos com mais frequência?
    • Ex: Para Vue, sempre instalo Bootstrap, Sass, Vue Router e Make JS Component
  • Quais são alguns componentes ou plugins comuns que sempre incluímos em nosso projeto?
    • Ex: Header, Footer, Navigation, um gerenciador de estado simples, algumas views

Crie um novo projeto

Quando tivermos uma ideia clara do que precisamos, o próximo passo é criar este projeto passo a passo, como fizemos até hoje. Crie tudo o que precisa e nada mais. Não queremos adicionar detalhes específicos a este projeto.

Uma encruzilhada

Neste momento, estamos em uma encruzilhada e temos duas maneiras de realizar a mesma coisa.

Uma nos permitirá ter tudo em um único pacote, fácil de manter, mas precisará de alguns ajustes para realmente brilhar (não vamos abordar esses detalhes agora).
Ela precisa de um pouco de conhecimento de NodeJS e JS para ser desenvolvida e mantida. Além disso, ainda é uma boa maneira de empacotar tudo.

A segunda maneira aproveita os métodos NodeJS para executar comandos, mas exige que criemos tantos repositórios quantas forem as variações de scaffolding de que precisamos.
Por outro lado, isso torna a atualização do scaffolding muito fácil e não precisa fazer alterações e atualizar o pacote principal para ter essas atualizações disponíveis para nós e nossos usuários.
Precisamos executar um comando usando NodeJS execSync, mas todo o processo é mais rápido e os starter kits são fáceis de manter.

Vamos explorar como cada método funciona.

OBSERVAÇÃO
Não vou, neste momento, me concentrar no significado de linhas de código únicas.
Provavelmente farei um novo tutorial dedicado a algumas das funções usadas nas partes a seguir.

Um pacote, muitos arquivos

Primeiro caminho, pronto para começar!

Prós:

  • único pacote para manter
  • com um pouco de conhecimento de NodeJS e NPM, pode realmente brilhar
  • tudo está em um único lugar
  • Vite também faz as coisas dessa forma

Contras:

  • precisamos copiar/colar os arquivos de um projeto esqueleto
  • se não quisermos copiar/colar, precisamos entender melhor como git, NodeJS e NPM funcionam
  • para cada alteração em nosso scaffolding, precisamos publicar uma nova versão do pacote

Atualizando o scaffolding do pacote npm

De volta ao nosso pacote! Queremos criar um diretório template e, para garantir que estamos preparados para o futuro, também devemos criar um diretório específico para o framework/biblioteca/tecnologia.
Neste exemplo, criaremos um diretório vue.

Esta é a estrutura atual do nosso pacote npm:

index.js
package.json
utils
    toUppercase.js
template
    vue

Dentro da pasta vue, copiaremos tudo do projeto que criamos na etapa anterior.

OBSERVAÇÃO
É claro que não copiaremos a pasta node_modules nem o arquivo package-lock.json.
Um esqueleto básico de um projeto novo será suficiente.

Como interagir com o usuário e copiar os arquivos de scaffolding

OBSERVAÇÃO
Como mencionado antes, esta parte será mais de copiar/colar por enquanto. Se você estiver interessado, farei um artigo futuro com foco em alguns dos métodos e pacotes que usei.

Precisamos perguntar aos nossos usuários sobre o projeto que nosso pacote criará.
Para realizar essa tarefa, precisamos do pacote inquirer (o código a seguir está referenciando a versão vinculada, mas sinta-se à vontade para usar a nova versão do inquirer e editar o exemplo de código).

Depois de instalar o inquirer, precisamos atualizar nosso ponto de entrada principal, index.js:

#!/usr/bin/env node

import toUppercase from '@myorg/mypackage-name/utils/toUppercase.js';
import inquirer from 'inquirer';

(async () => inquirer
    .prompt([
        {
            type: "input",
            name: "projectName",
            message: "Enter your project name",
            default: "obi-wan-kenobi"
        }
    ])
    .then((answers) => {
        console.log(toUppercase(answers.projectName));
    })
    .catch((error) => {
        if (error.isTtyError) {
            // Prompt couldn't be rendered in the current environment
            console.error("Cannot render the prompt...");
        } else {
            console.error(error.message);
        }
    }))();

Executar nosso projeto (localmente ou após publicar uma nova versão e instalá-la novamente) nos dará um prompt dentro do nosso terminal, pedindo o nome do nosso novo projeto brilhante.

Em vez de imprimir o nome do projeto todo em maiúsculas dentro do console, copiaremos os arquivos e pastas de template/vue dentro do diretório do projeto atual.

Devemos criar um novo arquivo dentro da pasta utils: createProject.js.
Dentro deste arquivo, adicionaremos duas funções:

  • uma função init que iniciará todo o processo
  • uma função copyTemplateFilesAndFolders que copiará recursivamente todos os arquivos e pastas do diretório de template
import fs from 'fs/promises';
import { fileURLToPath } from "node:url";
import path from "node:path";
import chalk from 'chalk';

export const copyTemplateFilesAndFolders = async (source,

Leave a Comment