GZAPPY
Published on

Como criar um pacote JavaScript para o NPM

Authors
  • avatar
    Name
    Luã Álvaro
    Twitter
Como criar um módulo para o universo javascript com o gerenciador de pacotes NPM.

INTRODUÇÃO

Se você é um desenvolvedor, eu tenho certeza que você já teve curiosidade ou até mesmo já tentou criar seu próprio pacote JavaScript e publicar no NPM.

Eu particularmente sempre fiquei curioso com o fato de utilizar pacotes gerados e mantidos por outros desenvolvedores, e como um bom desenvolvedor, eu também queria poder fazer aquilo. No início da minha carreira, de forma frustrada, eu tentei criar algum módulo e disponibilizar no gerenciador de pacotes NPM, que é o gerenciador de pacotes para o ambiente Node/JavaScript.

Neste artigo nós vamos, juntos, criar um pacote JavaScript e disponibilizar de forma pública no NPM.

VAMOS FAZER ALGO REALMENTE ÚTIL?

Para que não seja apenas um pacote de teste, com uma função "Hello World" embutida, nós vamos fazer algo realmente útil. Pelo menos útil para mim. Existem algumas funções utilitárias que sempre acabo utilizando na maioria dos projetos que desenvolvo/faço parte.

Por exemplo: Função para validar um CPF, função para validar um CNPJ, função para criar uma Máscara de Telefone e afins.

Como estou sempre replicando estes códigos entre meus projetos, vamos condensá-los em um único módulo, para que eu, ou qualquer pessoa do mundo, possa instalar nos seus projetos e utilizar-se das funções utilitárias que vamos criar.

CRIAÇÃO DO REPOSITÓRIO

Vamos começar o nosso pacote javascript com a criação de um repositório remoto no Github.

Print do formulário de criação de um repositório no github

Optei por criar um repositório público, chamado lual-utils, por ser público, isso quer dizer que você pode clonar, e inclusive abrir contribuições ao projeto!

CLONAR REPOSITÓRIO E INICIAR PROJETO NPM

Irei clonar este repositório localmente para iniciar o desenvolvimento do nosso módulo npm:

git clone https://github.com/luaalvaro/lual-utils.git
cd lual-utils

Com o repositório na minha máquina, irei iniciar um novo projeto NPM com o comando:

npm init -y

Será criado um arquivo package.json no diretório raiz do nosso projeto, e nele vamos fazer algumas alterações para que esteja em conformidade com o nosso objetivo.

CONFIGURAÇÃO DO PROJETO

Para começar eu alterei a propriedade version e description.

Também alterei a prop main para dist/index.js. Isso quer dizer que o arquivo principal do nosso módulo será o index.js que será "buildado" e disposto na pasta /dist. Além disso também adicionei outras duas propriedades que irá complementar requisitos do NPM para que nosso pacote javascript funcione:

package.json
{
  "main": "dist/index.js",
  "module": "dist/index.mjs"
}

Outros detalhes singelos para as propriedades repository e também keywords que preenchi para deixar nosso arquivo completo e bem descrito.

DEPENDÊNCIAS DE DESENVOLVIMENTO

Vamos adicionar ao nosso projeto 2 dependências de desenvolvimento, para que principalmente possamos configurar o Typescript no nosso módulo, isso irá facilitar o entendimento e usabilidade da nossa biblioteca.

npm install -D typescript tsup

E com estas dependências instaladas, nós podemos finalizar a configuração do nosso arquivo package.json

package.json
{
  "scripts": {
    "build":"tsup"
  }
}

O módulo TSUP irá se encarregar de fazer o build do nosso Typescript e criar todos os arquivos necessários para que possamos ter um módulo npm válido.

PACKAGE.JSON

Ao final da configuração temos o seguinte arquivo package.json

package.json
{
  "name": "lual-utils",
  "version": "0.0.1",
  "description": "This repository aims to guide a tutorial on how to create an npm module, and mainly to add useful functions that I use in my daily life in various projects.",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "repository": {
    "url": "git+https://github.com/luaalvaro/lual-utils"
  },
  "scripts": {
    "build": "tsup"
  },
  "keywords": ["helpers", "utils", "validation cpf", "whatsapp mask", "máscara whatsapp"],
  "author": "Luã Álvaro",
  "license": "MIT",
  "devDependencies": {
    "tsup": "^8.1.0",
    "typescript": "^5.5.2"
  }
}

VAMOS IGNORAR ALGUNS ARQUIVOS?

Neste momento, se você olhar para o contador do GIT, irá se espantar com a quantidade de arquivos que estão sendo rastreados pelo software. Vamos criar o tão famoso arquivo .gitignore e também será necessário criar um arquivo específico para o npm.

.gitignore
/dist
/node_modules

.env
.DS_Store

.env
.env.local
.npmignore
/src
/node_modules
/test

.env
.env.example
.DS_Store

tsconfig.json
tsup.config.js

.editorconfig
.prettierrc.json

jest.config.js

Perceba que estamos contemplando arquivos que ainda não temos no nosso projeto, mas é importante entender que temos 2 arquivos que irá ignorar outros arquivos.

O primeiro se trata do arquivo padrão e famoso .gitignore, ele serve para ignorar arquivos para serem enviados juntos com o commit para a linha do tempo no desenvolvimento.

O segundo arquivo segue a mesma linha que o primeiro, porém irá ignorar arquivos para ser enviado para o NPM, visto que esse deploy acontece de forma separada do github.

OPCIONAL, MAS DE EXTREMA IMPORTÂNCIA!

Este tópico é opcional para o tutorial, porém é de extrema importância e iremos adotar no nosso repositório. Iremos configurar o editor de código e o Prettier para que nosso projeto siga um padrão de desenvolvimento.

npm i -D prettier

Após a instalação do prettier precisamos adicionar 2 scripts ao nosso package.json, um para fazer o check do nosso repositório, procurando por arquivos que estejam fora do padrão definido. E um segundo para fazer o FIX desses arquivos que estiverem fora do padrão.

package.json
  "scripts": {
    "build": "tsup",
    "lint:check": "prettier --check .",
    "lint:fix": "prettier --write ."
  },

Também iremos criar o arquivo .editorconfig e o arquivo .prettierrc.json na raiz do nosso projeto

.editorconfig
root = true

[*]
indent_style = space
indent_size = 2
.prettierrc.json
{
  "trailingComma": "es5",
  "semi": false,
  "tabWidth": 2,
  "singleQuote": true,
  "jsxSingleQuote": true
}

ESTAMOS QUASE LÁ... VAMOS CONFIGURAR O TYPESCRIPT!

É bastante detalhe que estamos configurando, mas isso é de extrema importância para que nosso repositório e nosso módulo opere conforme padrões atuais de desenvolvimento.

Para configurar o nosso Typescript, vamos criar um arquivo de configuração e adicionar alguns valores padrões que irão definir versão, target, module e outras propriedades importantes:

tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "strictNullChecks": true,
    "target": "ES2022",
    "moduleResolution": "Node10",
    "module": "CommonJS",
    "declaration": true,
    "isolatedModules": true,
    "noEmit": true,
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

Com isso estamos a 1 passo de ter um base sólida para receber qualquer tipo de pacote para ser publicado no gerenciador de pacotes javascript.

Apenas vamos criar um arquivo de configuração para o tsup que vai lidar com a compilação e gerenciamento do nosso Typescript

tsup.config.js
import { defineConfig } from 'tsup'

export default defineConfig({
  format: ['cjs', 'esm'],
  entry: ['./src/index.ts'],
  dts: true,
  shims: true,
  skipNodeModulesBundle: true,
  clean: true,
})

Por mais que seja bastante detalhe, chegamos ao final das configurações necessárias para o nosso módulo javascript.

VAMOS PROGRAMAR O NOSSO MÓDULO NPM

Finalmente vamos partir para a parte do desenvolvimento em si, e não gostaria de decepcionar vocês, mas a parte mais difícil já foi feita! Que é a configuração inicial do nosso projeto, agora apenas iremos criar as funções que queremos disponibilizar no nosso pacote, fazer o build e a publicação para os servidores do npm.

./src/index.ts
const isCpfOrCnpjValid = (documentNumber: string) => {
  documentNumber = documentNumber.replace(/\D/g, "");

  if (documentNumber.length === 11) {
    if (/^(\d)\1+$/.test(documentNumber)) {
      return false;
    }

    let sum = 0;
    for (let i = 0; i < 9; i++) {
      sum += parseInt(documentNumber.charAt(i)) * (10 - i);
    }
    let rest = 11 - (sum % 11);
    let verifyingDigit = rest === 10 || rest === 11 ? 0 : rest;
    if (verifyingDigit !== parseInt(documentNumber.charAt(9))) {
      return false;
    }

    sum = 0;
    for (let i = 0; i < 10; i++) {
      sum += parseInt(documentNumber.charAt(i)) * (11 - i);
    }
    rest = 11 - (sum % 11);
    verifyingDigit = rest === 10 || rest === 11 ? 0 : rest;
    if (verifyingDigit !== parseInt(documentNumber.charAt(10))) {
      return false;
    }

    return true;
  }

  if (documentNumber.length === 14) {
    if (
      documentNumber == "00000000000000" ||
      documentNumber == "11111111111111" ||
      documentNumber == "22222222222222" ||
      documentNumber == "33333333333333" ||
      documentNumber == "44444444444444" ||
      documentNumber == "55555555555555" ||
      documentNumber == "66666666666666" ||
      documentNumber == "77777777777777" ||
      documentNumber == "88888888888888" ||
      documentNumber == "99999999999999"
    )
      return false;

    let docLength = documentNumber.length - 2;
    let docNumbers = documentNumber.substring(0, docLength);
    let docDigits = documentNumber.substring(docLength);
    let sum = 0;
    let pos = docLength - 7;
    for (let i = docLength; i >= 1; i--) {
      sum += Number(docNumbers.charAt(docLength - i)) * pos--;
      if (pos < 2) pos = 9;
    }
    let result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
    if (result != Number(docDigits.charAt(0))) return false;

    docLength = docLength + 1;
    docNumbers = documentNumber.substring(0, docLength);
    sum = 0;
    pos = docLength - 7;
    for (let i = docLength; i >= 1; i--) {
      sum += Number(docNumbers.charAt(docLength - i)) * pos--;
      if (pos < 2) pos = 9;
    }
    result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
    if (result != Number(docDigits.charAt(1))) return false;

    return true;
  }
};

export { isCpfOrCnpjValid };

Criei um diretório SRC na raiz do nosso projeto, que irá receber o nosso arquivo principal, e coloquei uma das funções que sempre acabo utilizando em meus projetos. Validação de CPF e CNPJ.

A partir de agora, ao iniciar um novo projeto eu simplesmente poderei instalar o módulo lual-utils e importar a função isCpfOrCnpjValid, isso irá facilitar demais a usabilidade do módulo, além de evitar um monte de código javascript solto e largado no nosso projeto.

GRAVANDO NOSSAS ALTERAÇÕES

Antes de partir para o build da nossa aplicação, vamos fazer o commit das nossas alterações com a primeira versão do nosso pacote

git add .
git commit -m "created mvp with 'isCpfOrCnpjValid' function"

BUILD DO PACOTE

Neste primeiro momento, nós iremos apenas configurar uma função que verifica e valida CPF e CNPJs, ao longo dos tempos irei adicionais mais funções que acabo mais utilizando nos projetos.

E como já comentei, o projeto fica open-source, e aceitando contribuições.

Para fazer o Build da nossa aplicação é tão simples quanto rodar o comando:

npm run build

Este comando irá rodar o tsup, buildar nossa aplicação, e criar uma pasta /dist com todos os arquivos necessários para publicação do nosso módulo npm.

PUBLICAÇÃO DO MÓDULO NPM

Para fazer a publicação do nosso módulo ao npm, será necessário ter uma conta criada. Para isso basta acessar https://www.npmjs.com/signup

Com a sua conta npm criada, vamos fazer login na nossa CLI com o comando npm login e em seguida fazer a publicação

Imagem mostrando o bash com o comando npm login

Neste momento podemos simplesmente rodar o comando de publicação oferecido pela cli npm e teremos feito a publicação deste módulo de forma pública em toda a internet.

npm publish
Imagem mostrando o bash com o comando npm publish executado e um retorno de sucesso com alguns detalhe a respeito da operação

E é isso! Parabéns!

Você tem um módulo criado e publicado no gerenciador de pacotes oficial e mais utilizado!

https://www.npmjs.com/package/lual-utils

UTILIZAÇÃO DO PACOTE CRIADO

Para validar se realmente deu tudo certo, vamos instalar o pacote criado em um outro projeto e testar alguns cpfs

npm i lual-utils
import { isCpfOrCnpjValid } from 'lual-utils'

const cpf = '12345678901'
const cnpj = '12345678901234'

console.log(isCpfOrCnpjValid(cpf)) // false
console.log(isCpfOrCnpjValid(cnpj)) // false

Desta maneira, finalizamos o nosso artigo por aqui. Espero que você tenha gostado, e tenha te ajudado de alguma forma.

Att e até a próxima.

Já pensou em integrar o seu sistema com o WhatsApp?

Eu sei que você, como desenvolvedor e dono de software, sofre com problemas de comunicação com seus clientes. Na grande maioria das vezes seus e-mails vão cair em caixas e spam e nunca serão lidas.

As mensagens enviadas através do WhatsApp são lidas em até 5 minutos. Então não perca tempo e dinheiro, e integre seu sistema com o WhatsApp com a GZAPPY API.

Crie sua primeira instância e comece a enviar e receber mensagens de forma automatizada com a API que foi feita pensada especialmente para você que é desenvolvedor!

Faça um teste gratuito hoje mesmo.