Integrações e bundlers
Um arquivo .node é uma biblioteca compartilhada carregada pelo runtime
JavaScript. Ele não é JavaScript e não deve ser transformado, concatenado em um
bundle nem enviado para um navegador. A maioria dos problemas de integração
desaparece quando o loader gerado é mantido intacto e executado pelo Node em
runtime.
Entenda o loader gerado
Com napi build --platform, o NAPI-RS gera um loader JavaScript que:
- Detecta
process.platform,process.arche a libc do Linux. - Tenta um arquivo local como
addon.linux-x64-gnu.node. - Tenta o pacote opcional publicado separadamente correspondente, como
@scope/addon-linux-x64-gnu. - Faz fallback para o binding WASI configurado quando o carregamento nativo falha.
- Lança um erro cujo encadeamento
causecontém as falhas dos candidatos nativos. Falhas comuns do fallback WASI não são adicionadas a essa cadeia; useNAPI_RS_FORCE_WASI=errorpara diagnosticar WASI especificamente.
O loader também reconhece dois controles de diagnóstico:
NAPI_RS_NATIVE_LIBRARY_PATH=/absolute/addon.nodesubstitui a seleção normal de plataforma e pacote nativos por uma biblioteca explícita. Se esse carregamento falhar, o loader registra o erro e pode seguir para um fallback WASI configurado, mas não tenta os candidatos nativos normais.NAPI_RS_ENFORCE_VERSION_CHECK=1rejeita um pacote de plataforma publicado separadamente cuja versão difira da do pacote raiz.
Mantenha o loader fora do bundle da aplicação sempre que possível. Ele precisa
ser capaz de fazer sua detecção em runtime e resolver dependências opcionais a
partir de uma árvore real de node_modules.
Escolha CommonJS ou ESM deliberadamente
A biblioteca nativa em si não tem formato de módulo. Apenas o loader JavaScript gerado é CommonJS ou ESM.
Pacote CommonJS
napi build --platform --js index.cjs
{
"main": "./index.cjs",
"types": "./index.d.ts"
}
const { add } = require('@scope/addon')
Use a extensão .cjs se o pacote tiver "type": "module"; caso contrário, o
Node fará parse de um loader CommonJS como ESM.
Pacote ESM
napi build --platform --esm --js index.js
{
"type": "module",
"main": "./index.js",
"types": "./index.d.ts"
}
import { add } from '@scope/addon'
O loader ESM gerado usa createRequire internamente porque o Node ainda carrega
bibliotecas .node por meio de require. --esm altera o wrapper exportado
para exportações ESM nomeadas estáticas reais; ele não converte o binário
nativo.
Exports duais CommonJS e ESM
Gere ambos os loaders a partir do mesmo artefato nativo:
{
"type": "module",
"main": "./index.cjs",
"module": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js",
"require": "./index.cjs"
}
},
"scripts": {
"build": "napi build --platform --js index.cjs && napi build --platform --esm --js index.js"
}
}
Teste import() e require() em CI. Runners de teste que transpilem ESM podem
exercitar um caminho diferente do Node puro, e é por isso que um erro só de
Jest em ESM não prova que a biblioteca nativa falhou ao carregar.
Estratégia recomendada para bundlers: externalizar
A build de aplicação mais robusta deixa o pacote raiz do addon como external. A implantação então contém:
- o loader gerado;
- os metadados do pacote raiz;
- o pacote opcional de plataforma correspondente e seu arquivo
.node.
Este é o modelo de empacotamento recomendado para a solicitação aberta sobre
bundlers (napi-rs#1948). Ele
evita nomes de asset com hash, __dirname realocado e bundlers seguindo
ansiosamente cada branch específica de plataforma em require.
WARNING
Marcar uma dependência como external significa que o runtime implantado ainda precisa conseguir resolvê-la. Copie as dependências de produção, instale-as na imagem de implantação ou forneça-as por uma camada serverless. Externalização sozinha não empacota o addon.
esbuild
import * as esbuild from 'esbuild'
await esbuild.build({
entryPoints: ['src/server.js'],
bundle: true,
platform: 'node',
format: 'esm',
outdir: 'dist',
external: ['@scope/addon', '@scope/addon-*'],
})
Copie/instale @scope/addon e sua dependência opcional correspondente ao lado
do bundle. Não use um loader file/copy a menos que você tenha escolhido
deliberadamente um pacote inline de binário único por plataforma e verificado
os caminhos relativos finais.
webpack
module.exports = {
target: 'node',
externals: {
'@scope/addon': 'commonjs @scope/addon',
},
}
Se o nome importado for calculado ou encapsulado, use uma função/plugin de
externals que mantenha o pacote inteiro do addon como external. node-loader
pode copiar um import direto de .node, mas por si só ele não preserva o fluxo
de controle do loader gerado para plataforma e pacote opcional.
Um binário inline de plataforma única
Às vezes uma aplicação interna distribui apenas um target conhecido e mantém o
arquivo .node no pacote raiz, em vez de pacotes opcionais separados. Nesse
caso:
- Mantenha o wrapper gerado como um arquivo external.
- Copie o arquivo
.nodesem hash de conteúdo. - Preserve o caminho relativo esperado pelo wrapper.
- Faça a build falhar se mais de um target puder chegar à implantação.
- Teste a partir do arquivo/imagem final, não da árvore-fonte.
Esta é uma otimização específica de implantação, não um pacote npm portátil.
Vite SSR e Astro
Addons nativos são dependências apenas do lado do servidor. Mantenha-os fora da otimização de dependências do Vite e do SSR bundling:
import { defineConfig } from 'vite'
export default defineConfig({
optimizeDeps: {
exclude: ['@scope/addon'],
},
ssr: {
external: ['@scope/addon'],
},
})
Importe o addon apenas de módulos de servidor. Um componente empacotado para o
navegador não consegue carregar uma biblioteca nativa .node.
Astro usa Vite, então a mesma externalização se aplica pela configuração
vite. Quando um pacote CommonJS não expõe exports nomeados ao Rollup, gere o
wrapper NAPI-RS com --esm ou carregue o pacote CommonJS no código de
servidor:
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const { add } = require('@scope/addon')
O relatório de integração com Astro continua aberto como napi-rs#2206. Verifique a saída final do servidor do adapter, porque ele pode executar outra etapa de bundling depois do Vite.
Next.js
Use o addon apenas no runtime Node.js: route handlers, server actions ou server components que não sejam atribuídos ao runtime Edge. Externalize o pacote do bundle de servidor:
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: ['@scope/addon'],
}
export default nextConfig
export const runtime = 'nodejs'
import { add } from '@scope/addon'
export function GET() {
return Response.json({ value: add(20, 22) })
}
Não importe o addon de um Client Component, middleware usando o runtime Edge ou código compartilhado com qualquer um deles. Garanta que sua plataforma de implantação copie o pacote external e sua dependência binária opcional.
Electron
O Electron pode carregar um addon Node-API no processo principal e em contextos preload/renderer com Node habilitado. Prefira carregá-lo no processo principal ou em um script de preload e expor uma API IPC estreita; habilitar integração Node irrestrita em um renderer amplia a fronteira de segurança da aplicação.
Para aplicações empacotadas:
- compile/instale o binário para o sistema operacional e CPU reais do Electron;
- mantenha arquivos
.nodefora da compressão ASAR (asarUnpack) ou use o suporte do empacotador para desempacotar módulos nativos; - mantenha pacotes opcionais de plataforma nas dependências de produção;
- teste um artefato instalado/empacotado, incluindo recarregamento e desligamento de janela;
- teste cada arquitetura Electron que você distribui.
O Node-API reduz a dependência de um ABI específico do V8, mas não faz um binário Linux x64 carregar no Windows ou no macOS. Se outra dependência usar o ABI de addon V8 em vez de Node-API, ela ainda poderá precisar de rebuilds específicos para Electron.
Serverless e containers
Compile e instale para o runtime de implantação, não para o laptop do desenvolvedor. Por exemplo, uma implantação Linux em Lambda precisa de um binário Linux para a arquitetura x64 ou arm64 da função e com uma versão de glibc compatível.
Um fluxo confiável é:
- Compilar/publicar pacotes de plataforma separados a partir da CI.
- Instalar dependências de produção para a plataforma de implantação.
- Externalizar o addon do bundle JavaScript.
- Copiar os pacotes raiz e de plataforma opcional para a imagem, função ou layer.
- Iniciar o artefato final na imagem base do provedor e chamar uma exportação nativa como smoke test.
Para seleção de libc Linux e target, siga Compilação cruzada. Se o provedor não permitir addons nativos, mas oferecer os recursos de runtime WASI exigidos, considere o fallback WASI documentado e teste explicitamente esse host.
Diagnostique uma implantação com bundle
Execute estas sondagens dentro do ambiente final do container/arquivo:
node -p "process.execPath"
node -p "process.platform + ' ' + process.arch"
node -p "process.report?.getReport?.().header.glibcVersionRuntime || 'no glibc version reported'"
npm ls @scope/addon
Depois importe o pacote external com Node puro. Se ele funciona antes do
bundling, mas não a partir do bundle final, inspecione se o bundler realocou o
loader, renomeou o arquivo .node, removeu uma dependência opcional ou
selecionou um runtime Edge/browser. O guia de solução de
problemas mostra como imprimir as falhas nativas
no encadeamento cause e como forçar um diagnóstico WASI separado.