@bigbinary/neeto-pdf-frontend

中文文档
1.0.31发布于 2026/10/26UNLICENSEDTypeScriptESM + CJS近 30 天下载 2432

A repo acts as the source of truth for the new nano's structure, configs, data etc.

原始使用文档
文档目录15 节
  1. Contents
  2. What the nano provides
  3. What the host product must provide
  4. Quick start
  5. Backend engine
  6. Frontend package
  7. Components
  8. Documents page
  9. Editor
  10. Email attachment controls
  11. Server-side rendering
  12. API endpoints
  13. Testing
  14. Local development
  15. Publishing

neeto-pdf-nano

neeto-pdf-nano adds reusable PDF templates, field mapping, preview, download, and email attachments to a Rails and React application.

It ships as two packages:

  • neeto-pdf-engine: the Rails engine.
  • @bigbinary/neeto-pdf-frontend: the React component library.

Contents

What the nano provides

The Rails engine owns:

  • PDF document storage and Active Storage template attachments.
  • Tenant-scoped document APIs.
  • Mapping persistence and validation.
  • Server-side PDF rendering.
  • Document cloning and output filename templates.

The frontend package owns:

  • The PDF documents list and upload flow.
  • The field-mapping editor.
  • Preview and download controls.
  • Email attachment selection and preview components.

The examples below use the current @bigbinary/neeto-pdf-frontend/v2 entrypoint. The package also keeps its legacy root entrypoint for existing consumers.

What the host product must provide

The host supplies an owner for reusable templates and a data record whose values fill each PDF. It also owns:

  • Tenant authorization and product routes.
  • Field normalization and value formatting.
  • Preview-record pagination.
  • Email settings and attachment delivery.
  • Owner cloning and lifecycle cleanup when applicable.

The host must use Rails, Active Record, Active Storage, PostgreSQL, and NeetoCommonsBackend::Api::BaseController. The frontend package uses the peer dependencies declared in package.json, including React Query, Axios, i18next, Formik, Tailwind CSS, neetoAtoms, neetoUI, and neetoMolecules.

Quick start

Backend engine

1. Install the engine

source "NEETO_GEM_SERVER_URL" do
  gem "neeto-pdf-engine"
end
bundle install

2. Copy the document migration

db/migrate/20260706120000_create_neeto_pdf_engine_documents.rb

Give it a free host timestamp, then run:

bundle exec rails db:migrate

Do not run the general engine migration installer; this repository contains unrelated template migrations. owner_id is a UUID. Adapt and test the copied migration first if the owner uses another primary-key type.

3. Configure the owner and render context

Create config/initializers/neeto_pdf_engine.rb:

# frozen_string_literal: true

NeetoPdfEngine.owner_class = "DocumentOwner"
NeetoPdfEngine.render_context_resolver = lambda do |owner:, record_id:|
  PdfDocuments::RenderContextResolver.call(owner:, record_id:)
end

Replace DocumentOwner with the real model. It must be the current organization or support where(organization: current_organization). Add:

class DocumentOwner < ApplicationRecord
  belongs_to :organization

  has_many :neeto_pdf_documents,
    as: :owner,
    class_name: "NeetoPdfEngine::Document",
    dependent: :destroy
end

Implement the render context:

module PdfDocuments
  class RenderContextResolver
    def self.call(owner:, record_id:)
      record = owner.data_records.find(record_id) # Use the real association.

      {
        value_resolver: ValueResolver.new(owner:, record:),
        filename_context: { id: record.id }
      }
    end
  end
end

Always find the data record through the owner. The value resolver must respond to call(mapping):

module PdfDocuments
  class ValueResolver
    def initialize(owner:, record:)
      @owner = owner
      @record = record
    end

    def call(mapping)
      return system_value(mapping["field_slug"]) if mapping["source"] == "system"

      value_for(mapping["field_slug"])
    end

    private

      attr_reader :owner, :record

      def value_for(field_slug)
        # Return the host-formatted value.
      end

      def system_value(field_slug)
        case field_slug
        when "form_name" then owner.name
        when "submission_id" then record.id
        when "submission_date" then I18n.l(record.created_at, format: :long)
        else ""
        end
      end
  end
end

The built-in system keys are form_name, submission_id, and submission_date; the host defines their meaning. For an image, return:

{
  type: "image",
  data: attachment.download,
  content_type: attachment.blob.content_type
}

Use image/svg+xml for SVG data. Return text, a number, or "" for other mappings.

4. Mount and protect the engine

Mount the engine:

mount NeetoPdfEngine::Engine, at: "/neeto_pdf"
const PDF_API_BASE_URL = "/neeto_pdf/api/v1";

The engine authenticates and scopes owners to the current organization. The host must add server authorization to NeetoPdfEngine::Api::V1::DocumentsController, including the render action. canManage only changes the UI.

5. Handle host lifecycle rules

When applicable, add NeetoPdfEngine::Document to host incineration, clone PDF documents with a cloned owner, and remap saved record_id values to cloned fields.

Frontend package

1. Install the package and PDF runtimes

yarn add @bigbinary/neeto-pdf-frontend [email protected] [email protected]

The package uses the host's Axios case transforms, React Query provider, and i18next instance.

2. Include styles and translations

There is no PDF stylesheet. Keep neetoUI and neetoMolecules styles enabled, and make Tailwind scan @bigbinary packages. For Tailwind CSS 4, adjust this path relative to the host stylesheet:

@import "tailwindcss" important;
@source "../../../../node_modules/@bigbinary";

The standard neeto-commons build loads package translations. Other builds must merge @bigbinary/neeto-pdf-frontend/app/javascript/src/translations/en.json into their i18next resources.

3. Serve the PDF.js worker

Add:

{
  "scripts": {
    "sync:pdf-worker": "mkdir -p public && cp -f node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/pdf.worker.min.js"
  }
}

Run it before dev and build. Serve the result at:

/pdf.worker.min.js

The generated worker may be ignored in Git.

Components

Documents page

import { PdfDocumentsPage } from "@bigbinary/neeto-pdf-frontend/v2";

const PdfDocuments = ({ ownerId, emailAttachments }) => (
  <PdfDocumentsPage
    apiBaseUrl="/neeto_pdf/api/v1"
    canManage={canManagePdfDocuments}
    emailAttachments={emailAttachments}
    ownerId={ownerId}
    onOpenEditor={document => openEditor(document.id)}
  />
);

The page lists documents in a table. Uploading one opens the editor straight away, so mapping is the next step rather than a separate decision.

  • onOpenEditor receives the complete document object. It is called both when a row is opened and right after a successful upload.
  • emailAttachments describes the emails a document can be attached to, as [{ key, label, dataTestId, onSelect }]. It drives the "Attach PDF to email" menu in the row kebab and in the editor top bar. The items only navigate; attaching happens on those email pages, not here.
  • renderHeader and renderContent can fit the page into a host layout.
  • Pass canManage explicitly. It only changes the UI.

Uploads use neetoMolecules FileUpload. Active Storage must return signedId. Templates must be PDFs of 10 MB or less.

Editor

import { PdfEditorPage } from "@bigbinary/neeto-pdf-frontend/v2";

<PdfEditorPage
  apiBaseUrl="/neeto_pdf/api/v1"
  canManage={canManagePdfDocuments}
  documentId={documentId}
  emailAttachments={emailAttachments}
  fields={pdfFields}
  homeUrl={pdfDocumentsUrl}
  isPreviewLoading={isPreviewLoading}
  mode={mode}
  ownerId={ownerId}
  previewPage={previewPage}
  previewRecord={previewRecord}
  previewTotal={previewTotal}
  onBack={goToPdfDocuments}
  onModeChange={setMode}
  onPreviewPageChange={setPreviewPage}
/>;

Use a real homeUrl. mode and onModeChange are only needed for host-controlled mode state.

Field shape

const pdfField = {
  id: "field-id",
  key: "stable-field-key",
  recordId: "field-id",
  slug: "stable-field-key",
  label: "Display label",
  kind: "email",
  renderType: "text",
  icon: EmailIcon,
  typeLabel: "Email",
};

Use a stable recordId, plain-text labels, and renderType: "image" only for image data. Exclude fields that cannot contain values.

Preview record shape

const previewRecord = { id: "data-record-id" };

The editor sends this ID to the render endpoint. The engine then calls render_context_resolver, keeping preview and attachment rendering on the same value path.

Email attachment controls

import {
  PdfAttachmentSelector,
  PdfAttachmentsPreview,
} from "@bigbinary/neeto-pdf-frontend/v2";
import { Form, Formik } from "formik";

<Formik
  initialValues={{ pdfDocumentIds: persistedPdfDocumentIds }}
  onSubmit={saveEmailSettings}
>
  <Form>
    <PdfAttachmentSelector
      documents={pdfDocuments}
      maxTotalSize={25 * 1024 * 1024}
    />
    <PdfAttachmentsPreview documents={pdfDocuments} />
  </Form>
</Formik>;
Formik:       pdfDocumentIds
Frontend API: customFields.pdfDocumentIds
Rails:        custom_fields["pdf_document_ids"]

Permit custom_fields[:pdf_document_ids] as an array. Remove deleted or inaccessible IDs before saving. Fetch documents with the same owner ID used by the editor. Pass maxTotalSize in bytes to prevent selections whose combined template size would exceed the email attachment limit. When a selection would exceed the limit, the selector keeps it unchecked and explains the document size and remaining space.

Server-side rendering

render_context = PdfDocuments::RenderContextResolver.call(
  owner:,
  record_id: record.id
)

owner.neeto_pdf_documents.where(id: selected_pdf_document_ids).map do |document|
  NeetoPdfEngine::Documents::RendererService.new(
    document,
    **render_context
  ).process
end

Each result contains:

{
  filename: "document-record-id.pdf",
  mime_type: "application/pdf",
  content: "<binary PDF>",
  truncated_mapping_ids: []
}

The host attaches or encodes content. Encrypted PDFs raise NeetoPdfEngine::EncryptedDocumentError. Built-in fonts support ASCII and Latin-1; other characters are removed during server rendering.

API endpoints

With the engine mounted at /neeto_pdf:

GET    /neeto_pdf/api/v1/documents
POST   /neeto_pdf/api/v1/documents
GET    /neeto_pdf/api/v1/documents/:id
PATCH  /neeto_pdf/api/v1/documents/:id
PUT    /neeto_pdf/api/v1/documents/:id
PATCH  /neeto_pdf/api/v1/documents/:id/template_file
POST   /neeto_pdf/api/v1/documents/:id/clone
POST   /neeto_pdf/api/v1/documents/:id/render
DELETE /neeto_pdf/api/v1/documents/:id

Every request requires owner_id; clients cannot choose owner_type. Render also requires record_id and accepts temporary mappings without persisting them. Uploads accept an Active Storage signed blob ID or multipart template_file.

The default output filename is:

<parameterized-document-name>-{{id}}.pdf

filename_context[:id] replaces {{id}}.

Testing

Run the Rails test suite and build the frontend package before publishing a change:

bundle exec rails test
yarn build

For documentation-only changes, also run:

yarn prettier --check README.md
git diff --check -- README.md

Local development

gem "neeto-pdf-engine", path: "../neeto-pdf-nano"

For local frontend work, follow the frontend package development guide.

Publishing

Follow the building and releasing packages guide to publish the frontend package and Rails engine.

版本与发布记录

@bigbinary/neeto-pdf-frontend 共发布 32 个版本,首次发布于 2026/07/15;过去 12 个月发布了 32 个版本。

版本发布日期距今
1.0.31latest2026/10/2613 天前
1.0.302026/09/181 个月前
1.0.30-beta1beta2026/09/181 个月前
1.0.292026/09/171 个月前
1.0.282026/09/161 个月前
1.0.272026/08/063 个月前

关于 @bigbinary/neeto-pdf-frontend 的常见问题

@bigbinary/neeto-pdf-frontend 是什么?

A repo acts as the source of truth for the new nano's structure, configs, data etc.

如何安装 @bigbinary/neeto-pdf-frontend?

在项目目录运行 npm install @bigbinary/neeto-pdf-frontend,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:qs(6.16.1)、url(0.11.4)、yup(0.32.11)、antd(5.22.1)、util(^0.12.5)、axios(1.20.0)、ramda(0.29.0)、react(18.3.1)、buffer(^6.0.3)、formik(2.4.6)、i18next(22.5.1)、pdf-lib(1.17.1)、zustand(4.4.2)、dompurify(3.4.17)、js-logger(1.6.1)、react-dom(18.3.1)、classnames(2.5.1)、pdfjs-dist(6.2.108)、stream-http(3.2.0)、tailwindcss(4.1.17)、lucide-react(^1.8.0)、react-helmet(^6.1.0)、os-browserify(0.3.0)、react-i18next(12.3.1)、vm-browserify(1.1.2)、@babel/runtime(7.29.7)、react-toastify(8.0.2)、tty-browserify(0.0.1)、path-browserify(^1.0.1)、https-browserify(1.0.0)、mixpanel-browser(2.45.0)、react-router-dom(5.3.3)、crypto-browserify(3.12.1)、source-map-loader(4.0.1)、stream-browserify(^3.0.0)、@bigbinary/neetoui(8.8.3)、@honeybadger-io/js(6.10.1)、@bigbinary/neeto-cist(1.0.17)、@honeybadger-io/react(6.1.25)、@tanstack/react-query(5.59.20)、@bigbinary/neeto-atoms(1.1.0)、@bigbinary/neeto-icons(1.20.89)、@bigbinary/neeto-molecules(5.9.7)、@tanstack/react-query-devtools(5.59.20)、@bigbinary/neeto-commons-frontend(4.14.4)、@bigbinary/neeto-filters-frontend(4.5.1)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bigbinary/neeto-pdf-frontend 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bigbinary/neeto-pdf-frontend 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@bigbinary/neeto-pdf-frontend 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.13。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bigbinary/neeto-pdf-frontend 可以商用吗?

@bigbinary/neeto-pdf-frontend 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@bigbinary/neeto-pdf-frontend 的下载量有多少?

根据 npm 官方下载统计,@bigbinary/neeto-pdf-frontend 在 2026/10/08 – 2026/11/06 这 30 天内被下载 2,432 次,日均约 81 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bigbinary/neeto-pdf-frontend 还在维护吗?

最新版本 1.0.31 发布于 2026/10/26(13 天前);至今共发布 32 个版本,其中过去 12 个月发布了 32 个;首次发布于 2026/07/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。