Ofusca un build de Parcel

@afterpack/parcel-optimizer ofusca en memoria cada bundle empaquetado como un Optimizer de Parcel 2, y hace fallar el build de forma segura antes que publicar un chunk que da 404 en producción.

@afterpack/parcel-optimizer es un Optimizer de Parcel 2, registrado en .parcelrc. Ofusca cada bundle empaquetado en memoria, durante la propia fase de optimización de Parcel, así que el bundle nunca llega a disco en claro.

No es @afterpack/parcel

El paquete es @afterpack/parcel-optimizer.

Instalación

$ npm install -D @afterpack/parcel-optimizer
// .parcelrc
{
  "extends": "@parcel/config-default",
  "optimizers": {
    "*.{js,mjs,cjs}": ["...", "@afterpack/parcel-optimizer"]
  }
}

El spread "..." importa: conserva los optimizers por defecto de Parcel (@parcel/optimizer-swc) y coloca AfterPack el último. AfterPack se ejecuta después de la minificación. Dependencia peer: Parcel >=2.9.0.

Build

Las entradas de .parcelrc son nombres de paquete sin opciones en línea, así que las opciones salen de afterpack.json en la raíz de tu proyecto, el único archivo de configuración que leen todas las integraciones de AfterPack:

{
  "seed": "git",
  "preset": "medium"
}

Solo se generan artefactos en la raíz del proyecto; nada va junto a tus bundles:

  • Un protectionMap.html por bundle, en .afterpack/<bundle>.<id>.protectionMap.html.
  • Un source map, cuando la política de sourceMap lo activa. Se devuelve a Parcel en lugar de escribirse directamente, así que Parcel le pone nombre y lo emite con su propio hash de contenido.

.afterpack/ lleva su propio .gitignore; el plugin nunca edita el de tu proyecto.

Verificación

Como no hay un informe combinado, revisa directamente el bundle que te interesa: .afterpack/<bundle-name>.<id>.protectionMap.html para cada entrada o chunk diferido que Parcel empaquetó. Los mapas están desactivados en producción por defecto; pon "sourceMap": { "enabled": true } en afterpack.json para cambiarlo. Cada informe contiene todo tu código fuente. Nunca lo subas al repositorio ni lo sirvas.

Para comprobar un despliegue, añade el reporter del plugin al comando de build. Cada build escribe entonces un recibo de protección en .afterpack/receipts/, en la raíz de tu proyecto, uno por cada directorio de salida de destino (.afterpack/receipts/dist.json para dist). afterpack verify falla cuando un archivo cambió después de ofuscarse o cuando no hay recibo:

parcel build index.html --reporter @afterpack/parcel-optimizer/reporter
npx afterpack@latest verify dist

Opciones

Ahí se puede establecer cada clave de configuración, por ejemplo { "preset": "hard", "complexity": 40 }. protectionMap.enabled depende de si el bundle lleva un source map, no del modo de build, y siempre se guarda en .afterpack/, ignorado por git. Este plugin no tiene opción build.backup. Establecerla muestra una advertencia. Las opciones explícitas siempre tienen prioridad sobre los valores por defecto de producción.

Directivas

El plugin lee las directivas de tu código fuente. Sin un source map utilizable, la pasada no tiene nada que recuperar y lo indica.

Una directiva en tu módulo de entrada no se recupera. La pasada informa de DIAG_DIRECTIVE_SOURCE_UNRESOLVED e indica el módulo. Las directivas en cualquier otro módulo que no sea de entrada funcionan con normalidad en cuanto los source maps están activados. Mueve el código protegido a un módulo importado si necesitas una directiva en el módulo de entrada.

Casos límite

Los placeholders de hash de contenido pueden hacer fallar tu build

Con el hash de contenido activado (el valor por defecto de parcel build) y un target sin ESM nativo, los placeholders de hash de contenido de Parcel acaban dentro de cadenas ofuscadas, y cada chunk dividido da 404 en producción.

Este plugin detecta la colisión y hace fallar el build en lugar de publicarlo:

AfterPack obfuscated 1 Parcel content-hash placeholder(s) in app.[hash].js (HASH_REF_...).
  hint: Run `parcel build --no-content-hash` (chunk URLs stop being content-addressed).
  hint: Or set "complexity": 0 in afterpack.json to ship minify-only.

La salida ESM por defecto de Parcel no se ve afectada.

Conviene saber

  • Una pasada por bundle, en procesos worker. Parcel ejecuta los optimizers por bundle, y los bundles empaquetados en procesos worker distintos no pueden compartir estado. Los bundles empaquetados en el mismo proceso comparten una semilla. Para fijar una sola semilla en todos los workers, establece seed de forma explícita o exporta AFTERPACK_seed.
  • Parcel cachea la salida de los optimizers. Un rebuild con .parcel-cache caliente y la entrada sin cambios no vuelve a ejecutar AfterPack ni rota una semilla sin fijar. Borra .parcel-cache para una pasada nueva; en CI se compila de todos modos con la caché fría.
  • Dos advertencias inofensivas. Parcel imprime ES module dependencies are experimental (todos los paquetes de AfterPack son ESM) e informa de que el plugin contains non-statically analyzable dependencies (la resolución en tiempo de ejecución del binario nativo del motor). Ninguna afecta al build.

Sin el plugin: la CLI

Compila con Parcel como siempre y luego apunta AfterPack a la salida:

$ npx parcel build
$ npx afterpack@latest dist/ --preset=medium --seed=git

Sin un argumento de ruta, la CLI detecta dist/, la salida por defecto de Parcel; indica el directorio si el tuyo es otro. AfterPack recorre el directorio, recopila cada .js/.mjs/.cjs y los ofusca en el sitio. Una ejecución de la CLI no tiene la protección del hash de contenido, y genera un único protectionMap.html combinado para todo el directorio en lugar de uno por bundle. El fallo seguro (fail closed) se mantiene: un directorio de build inexistente, la ausencia de archivos JS, un diagnóstico error/critical o un archivo que el motor no pudo ofuscar terminan todos con 1.

Siguiente

  • Frameworks: la matriz completa.
  • esbuild: otro plugin que recupera las directivas de un source map después de la minificación.
  • Referencia de la CLI: los flags completos de la vía alternativa.
  • Builds y CI: semillas, desarrollo frente a producción, promocionar los mismos bytes.
  • Protection Map: cómo leer el informe.
  • Configuración: cada opción y qué superficie puede establecerla.
  • Presets: la escala de cinco peldaños.