Skip to content

Refactor page-compiler format API to modular transforms pipeline#72

Merged
YusukeHirao merged 22 commits intomainfrom
feat/format-and-manipulation
Feb 3, 2026
Merged

Refactor page-compiler format API to modular transforms pipeline#72
YusukeHirao merged 22 commits intomainfrom
feat/format-and-manipulation

Conversation

@YusukeHirao
Copy link
Copy Markdown
Member

Summary

Complete refactoring of page-compiler's format API into a modular transforms pipeline with improved extensibility and v1.3.0 compatibility fixes.

Breaking Changes

  1. Unified transforms option replaces individual format options (prettier, minifier, imageSizes, etc.)
  2. API renames: formatHtmlpageTransform, FormatHtmlOptions → integrated into PageCompilerOptions
  3. Unified Transform interface across page-compiler and devServer
  4. Package exports restructured: ./format./page-transform + individual transform exports

New Features

  • Function support for transforms - extend defaults without explicit imports
  • Modular transform factories - manipulateDOM, characterEntities, doctype, prettier, minifier, lineBreak
  • Enhanced TransformContext - added filePath, outputDir, compile function

Bug Fixes & Compatibility

  • Fix imageSizes not working with custom hooks (now receives parent elements, not img elements)
  • Fix fragment handling (fragments stay as fragments, not wrapped in full HTML)
  • Remove characterEntities from defaults (opt-in only, matching v1.3.0)
  • imageSizes defaults to true (matching v1.3.0)
  • Remove direct jsdom dependency (use kamado's domSerialize)

Files Changed

New Files

  • src/page-transform.ts - defaultPageTransforms export
  • src/page-transform.spec.ts - Transform pipeline tests
  • src/transform/manipulate-dom.ts - DOM manipulation transform (with imageSizes)
  • src/transform/character-entities.ts - Character entity conversion
  • src/transform/doctype.ts - DOCTYPE declaration
  • src/transform/prettier.ts - HTML formatting
  • src/transform/minifier.ts - HTML minification
  • src/transform/line-break.ts - Line break normalization
  • Comprehensive test files for each transform

Removed Files

  • src/format.ts - replaced by modular transforms
  • src/format.spec.ts - replaced by transform-specific tests

Modified Files

  • src/page-compiler.ts - Use transforms pipeline instead of formatHtml
  • src/types.ts - Replace format options with transforms option
  • package.json - Update exports, remove jsdom dependencies
  • README.md - Complete documentation rewrite
  • src/transform/inject-to-head.ts - Updated to Transform interface
  • src/transform/ssi-shim.ts - Updated to Transform interface
  • kamado/src/config/types.ts - Unified Transform interface

Migration Guide

Before (v1.x)

import { pageCompiler } from '@kamado-io/page-compiler';

pageCompiler({
  layouts: { dir: './layouts' },
  imageSizes: true,
  prettier: { printWidth: 120 },
  minifier: { collapseWhitespace: true },
  characterEntities: true,
});

After (v2.x)

import { pageCompiler, characterEntities } from '@kamado-io/page-compiler';

// Option 1: Use defaults (imageSizes true, no characterEntities)
pageCompiler({
  layouts: { dir: './layouts' },
});

// Option 2: Customize with function (no imports needed)
pageCompiler({
  layouts: { dir: './layouts' },
  transforms: (defaults) => [
    ...defaults.map(t => 
      t.name === 'prettier' 
        ? prettier({ options: { printWidth: 120 } })
        : t
    ),
    characterEntities(), // Opt-in
  ],
});

// Option 3: Full custom selection
import { manipulateDOM, prettier, minifier } from '@kamado-io/page-compiler';

pageCompiler({
  layouts: { dir: './layouts' },
  transforms: [
    manipulateDOM({ 
      imageSizes: true,
      hook: async (elements, window, ctx) => {
        // Custom DOM manipulation
      }
    }),
    prettier({ options: { printWidth: 120 } }),
    minifier({ options: { collapseWhitespace: true } }),
  ],
});

API Changes

defaultPageTransforms

// v2.x default pipeline (5 transforms)
[
  manipulateDOM({ imageSizes: true }),
  doctype(),
  prettier(),
  minifier(),
  lineBreak(),
]

Transform Interface

interface Transform {
  name: string;
  transform: (
    content: string | ArrayBuffer,
    context: TransformContext
  ) => string | ArrayBuffer | Promise<string | ArrayBuffer>;
  filter?: TransformFilter; // Only for devServer.transforms
}

Package Exports

// Main
import { pageCompiler, defaultPageTransforms } from '@kamado-io/page-compiler';

// Transforms
import { manipulateDOM } from '@kamado-io/page-compiler/transform/manipulate-dom';
import { characterEntities } from '@kamado-io/page-compiler/transform/character-entities';
import { doctype } from '@kamado-io/page-compiler/transform/doctype';
import { prettier } from '@kamado-io/page-compiler/transform/prettier';
import { minifier } from '@kamado-io/page-compiler/transform/minifier';
import { lineBreak } from '@kamado-io/page-compiler/transform/line-break';

// Page transform module
import { defaultPageTransforms } from '@kamado-io/page-compiler/page-transform';

Test Coverage

  • ✅ All transform modules have comprehensive tests
  • ✅ Transform pipeline tests (page-transform.spec.ts)
  • ✅ Integration tests (page-compiler.spec.ts)
  • ✅ Fragment preservation tests
  • ✅ Function-based transforms extension tests

🤖 Generated with Claude Code

YusukeHirao and others added 22 commits January 31, 2026 23:54
…essors

BREAKING CHANGE: Add boolean support to minifier option type

Split the 130+ line formatHtml function into 9 separate processor functions:
- buildTransformContext: Builds TransformContext for hooks
- beforeSerialize: Executes beforeSerialize hook
- domSerialize: DOM serialization with image size injection
- characterEntities: Converts characters to HTML entities
- doctype: Inserts DOCTYPE declaration
- prettier: Formats HTML with Prettier
- minifier: Minifies HTML with html-minifier-terser
- lineBreak: Normalizes line breaks
- replace: Final content replacement

Each processor follows a curried pattern: initialized with context/options,
returns a function that processes content. Processors are executed in a
pipeline array for clear, sequential transformation flow.

Type change:
- PageCompilerOptions.minifier: HMTOptions → HMTOptions | boolean
  (now supports false to disable minification)

Benefits:
- Single responsibility: Each function has one clear purpose
- Testability: Individual processors can be tested independently
- Maintainability: Easy to add, modify, or remove processing steps
- Readability: Main function is now a clear pipeline

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Replace formatHtml with pageTransform

- Rename formatHtml function to pageTransform
- Rename FormatHtmlContext to PageTransformContext
- Rename FormatHtmlOptions to PageTransformOptions
- Reorganize options by transformation phase (beforeFormat, domManipulation, afterFormat)
- Update all documentation and examples

The transformation pipeline is now explicitly divided into three phases:
1. Phase 1: beforeFormat - String transformations before DOM parsing
2. Phase 2: domManipulation - DOM-based transformations
3. Phase 3: afterFormat - String transformations after DOM serialization

Migration:
- Import { pageTransform } instead of { formatHtml }
- Use PageTransformContext instead of FormatHtmlContext
- Use PageTransformOptions instead of FormatHtmlOptions
- Function signature and options remain the same

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Rename format.ts to page-transform.ts

- Rename src/format.ts to src/page-transform.ts
- Rename src/format.spec.ts to src/page-transform.spec.ts
- Update package.json exports: ./format -> ./page-transform
- Update all import statements to use ./page-transform.js
- Update README.md import examples

This aligns file names with the function name pageTransform.

Migration:
- Import from '@kamado-io/page-compiler/page-transform' instead of '@kamado-io/page-compiler/format'

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Rename format processor files to match pageTransform API

File renames:
- before-serialize.ts -> preprocess-content.ts
- dom-serialize.ts -> manipulate-dom.ts
- replace.ts -> postprocess-content.ts

This is the first step of the breaking change.
The next commit will update internal implementations and API names.
BREAKING CHANGE: Rename pageTransform hook APIs for clarity

API renames:
- beforeSerialize -> preprocessContent (content preprocessing before DOM parsing)
- afterSerialize -> manipulateDOM (DOM manipulation after parsing)
- replace -> postprocessContent (content postprocessing after serialization)

Updated type definitions and internal implementations:
- PageCompilerOptions, PageTransformOptions interfaces
- PreprocessContentContext, ManipulateDOMContext, PostprocessContentContext types
- preprocessContent(), manipulateDOM(), postprocessContent() processor functions
- Updated JSDoc comments to reflect new phase names
…mpiler

BREAKING CHANGE: Update pageTransform call to use new option names

Updated option names when calling pageTransform:
- beforeSerialize -> preprocessContent
- afterSerialize -> manipulateDOM
- replace -> postprocessContent

This aligns the pageCompiler implementation with the renamed pageTransform API.
Update test suites and test cases to use new hook names:
- "beforeSerialize hook" -> "preprocessContent hook"
- "afterSerialize hook" -> "manipulateDOM hook"
- Update option names in all test cases
Update all documentation to use new hook names:
- beforeSerialize -> preprocessContent
- afterSerialize -> manipulateDOM
- replace -> postprocessContent

Updated sections:
- Options section
- TransformContext section
- Transform Utilities examples
- Standalone API section (phase descriptions and options)
…vServer

BREAKING CHANGE: Rename ResponseTransform to Transform and extend TransformContext

Type changes:
- ResponseTransform -> Transform
- Transform.name is now required (was optional)
- TransformContext extended with filePath, outputDir, and compile properties

This unifies the transform interfaces between page-compiler and devServer.transforms,
enabling better code reuse and consistency across the compilation pipeline.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ied transforms option

BREAKING CHANGE: Replace granular formatting options with flexible Transform Pipeline

Removed options:
- imageSizes (boolean | ImageSizesOptions)
- minifier (boolean | HMTOptions)
- prettier (boolean | PrettierOptions)
- lineBreak ('\n' | '\r\n')
- characterEntities (boolean)

Added option:
- transforms: Transform[] | ((defaultTransforms: readonly Transform[]) => Transform[])

Users must now configure transforms explicitly or use defaultPageTransforms.
Function variant allows extending defaults without importing defaultPageTransforms.

Migration guide:
```typescript
// Before
pageCompiler({ imageSizes: true, minifier: { collapseWhitespace: true } })

// After (using defaults)
pageCompiler({ transforms: defaultPageTransforms })

// After (customizing)
import { manipulateDOM, minifier } from '@kamado-io/page-compiler';
pageCompiler({
  transforms: [
    manipulateDOM({ imageSizes: true }),
    minifier({ options: { collapseWhitespace: true } })
  ]
})

// After (extending defaults without import)
pageCompiler({
  transforms: (defaults) => [customTransform, ...defaults]
})
```

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Move and refactor format/ transforms to transform/ directory

File movements and removals:
- Delete format/build-transform-context.ts (no longer needed)
- Delete format/preprocess-content.ts (functionality removed)
- Delete format/postprocess-content.ts (functionality removed)
- Move format/character-entities.ts -> transform/character-entities.ts
- Move format/doctype.ts -> transform/doctype.ts
- Move format/line-break.ts -> transform/line-break.ts
- Move format/manipulate-dom.ts -> transform/manipulate-dom.ts
- Move format/minifier.ts -> transform/minifier.ts
- Move format/prettier.ts -> transform/prettier.ts

All transform factory functions now:
- Accept options parameter only (no context parameter)
- Return Transform object directly
- Have simpler, unified API surface
- Remove unused preprocess/postprocess transform builders

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Move transform tests from page-transform.spec.ts to individual spec files:
- character-entities.spec.ts (2 tests)
- doctype.spec.ts (2 tests)
- line-break.spec.ts (3 tests)
- manipulate-dom.spec.ts (5 tests)
- minifier.spec.ts (2 tests)
- prettier.spec.ts (3 tests)

page-transform.spec.ts now only contains defaultPageTransforms array tests (7 tests).
This improves test organization and maintainability.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add support for function-based transforms configuration:

transforms option now accepts:
- Transform[] (existing behavior)
- (defaultTransforms: readonly Transform[]) => Transform[] (new)

The function variant receives defaultPageTransforms and returns modified array,
enabling users to prepend/append/filter transforms without explicit import.

Changes:
- page-compiler.ts: Add runtime type check and function invocation
- page-transform.ts: Simplify by removing transform builders, only export defaults
- page-compiler.spec.ts: Add test for function-based transforms extension
- Re-export transform factories from page-compiler.ts for convenience

Example usage:
```typescript
// No import needed
pageCompiler({
  transforms: (defaults) => [customTransform, ...defaults]
})

// vs. old way
import { defaultPageTransforms } from '@kamado-io/page-compiler/page-transform';
pageCompiler({
  transforms: [customTransform, ...defaultPageTransforms]
})
```

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ansform type

Update dev transform utilities to use unified Transform type:

- Replace ResponseTransform with Transform type
- Update default names: inject-to-head -> injectToHead, ssi-shim -> ssiShim
- Update parameter names: context -> ctx for consistency
- Update JSDoc to reference Transform instead of ResponseTransform
- Remove name option from SSIShimOptions (fixed to "ssiShim")

These utilities now use the same Transform interface as page compiler transforms,
improving consistency across the codebase.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Export individual transform factories to support direct imports:
- manipulate-dom
- character-entities
- doctype
- prettier
- minifier
- line-break

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update all documentation to reflect Transform API changes:

page-compiler README:
- Fix all function names (manipulateDOM, characterEntities, etc.)
- Remove non-existent functions (preprocessContentTransform, postprocessContentTransform)
- Document function support for transforms option
- Update all code examples with correct API
- Change variable name from imgs to images
- Add comprehensive migration guide for v2 to v3

kamado README:
- Update Transform API references
- Fix function names in examples
- Add correct imports for transform factories

kamado ARCHITECTURE:
- Update function references to match actual implementation
- Fix Transform interface documentation
- Correct hook references (manipulateDOM() instead of manipulateDOMTransform)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Simplify hook signature by removing redundant parameters:
- Before: (elements, window, isServe, context, compile)
- After: (elements, window, context)

isServe and compile are already available in context:
- context.isServe
- context.compile

This makes the API more consistent and less redundant.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…eSizes

Replace direct JSDOM usage with kamado's domSerialize utility to ensure
consistent fragment handling. Pass parent elements array to imageSizes
instead of img elements directly.

The bug was that img elements were being passed to imageSizes, which then
tried to query for images within those img elements using querySelectorAll,
resulting in empty arrays. imageSizes expects parent elements to query from.

Changes:
- Use domSerialize from kamado/utils/dom instead of creating JSDOM directly
- Pass elements (from domSerialize) to imageSizes instead of querying img elements
- Change hook window type from JSDOM['window'] to standard Window type
- Fragments remain as fragments, full documents remain as full documents

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove jsdom and @types/jsdom from package-compiler's direct dependencies.
The package now uses kamado's domSerialize utility which internally handles
jsdom, eliminating duplicate dependencies.

Benefits:
- Simpler dependency tree (jsdom only in kamado)
- Centralized jsdom version management
- Reduced bundle size through deduplication

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix test expectations that were incorrectly changed to expect DOCTYPE and
full HTML structure from fragment inputs. Fragments should remain as
fragments after processing.

Corrected expectations:
- Fragment input '<p>Hello, world!</p>' outputs '<p>Hello, world!</p>\n'
- Plain text 'p Hello, world!' outputs '' (empty, as it's not valid HTML)
- Pug-compiled fragments output as fragments, not full documents

These test expectations match the v1.3.0 behavior where domSerialize
preserves the input format (fragments stay fragments, full docs stay full).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update documentation to reflect that imageSizes defaults to true,
matching v1.3.0 behavior:
- Update manipulate-dom.ts comment to state default: true
- Update README.md to clearly document default: `true`
- Clarify host option default behavior (dev server vs production)
- Add note about domSerialize preserving fragments

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove characterEntities from defaultPageTransforms to match v1.3.0 behavior
where character entity conversion was opt-in only.

In v1.3.0, characterEntities option was optional and disabled by default.
Users had to explicitly enable it. The transform is still available for
explicit use via import.

Changes:
- Remove characterEntities from defaultPageTransforms (5 transforms now)
- Update README to document that characterEntities is not included by default
- Add usage example for explicitly adding characterEntities
- Update tests to reflect 5 transforms instead of 6

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@YusukeHirao YusukeHirao merged commit 9df91a1 into main Feb 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant