This document explains the print architecture and how the two different print players work together.
Short answer: Not always! Print components are self-contained and work with regular players.
Print components are just delivery components with transformations:
- They call
preparePrintModel()internally - They render using the same delivery component
- You can render them with any player by passing appropriate props
Use print players when:
- ✅ You want explicit print-focused APIs (
roleinstead of complex env) - ✅ You need multi-element items (passage + questions)
- ✅ You have markup strings from a CMS
- ✅ You want to match production patterns (pieoneer, content systems)
Don't need print players if:
- ✅ You can use the interactive player with print-appropriate props
- ✅ You're rendering single elements
- ✅ You have direct access to components (not markup strings)
Package: @pie-element/element-player (this repository)
Component: <pie-element-player view="print">
Purpose: Testing and developing individual element print views
<pie-element-player
view="print"
element-name="multiple-choice"
role="student"
model={model}
></pie-element-player>Use Cases:
- Element development and testing
- Element documentation
- Single element demos
- Quick print preview during development
Location: packages/element-player/src/players/PieElementPlayer.svelte
Package: @pie-players/pie-print-player (pie-players repository)
Component: <pie-print>
Purpose: Production rendering of complete assessment items
<pie-print config={itemConfig}></pie-print>Where itemConfig includes:
item.markup- HTML with multiple elementsitem.elements- Package version mapitem.models- Array of element modelsoptions.role- student/instructor
Use Cases:
- Production applications (pieoneer, content delivery)
- Multi-element assessments (passage + questions)
- Markup-driven rendering
- Floater elements (rubrics, standalone components)
Location: packages/print-player in the pie-players repository.
Note: this repo also contains a
packages/print-player(@pie-element/print-player). It is superseded — its resolver loadsdist/print/index.js(the bundler artifact, which is not browser-loadable) and it does not inject a React import map. Use@pie-players/pie-print-playerfor production print.
Current Coverage: Print support is available for 10 out of 28 elements. Print components are being added incrementally as elements are developed.
Each element package can include a print export:
packages/elements-react/multiple-choice/
├── src/
│ ├── delivery/ # Interactive component
│ ├── author/ # Configuration UI
│ ├── controller/ # Business logic
│ └── print/ # Print component
│ └── index.tsx # Print custom element
└── package.json
Print components are self-contained and handle their own transformations:
// packages/elements-react/multiple-choice/src/print/index.tsx
const preparePrintModel = (model, opts) => {
const instr = opts.role === 'instructor';
return {
...model,
disabled: true, // Disable all interactions
lockChoiceOrder: true, // Lock randomization
alwaysShowCorrect: instr, // Show answers for instructors
animationsDisabled: true, // No animations
// ... other print-specific transformations
};
};
export default class MultipleChoicePrint extends HTMLElement {
set model(m) {
const printModel = preparePrintModel(m, this._options);
// Render with delivery component
this._root.render(<Main model={printModel} session={{}} />);
}
set options(o) {
this._options = o;
}
}Key Points:
- Each print component exports a custom element class
preparePrintModel()handles print-specific transformations- Renders using the existing delivery component
- Respects
options.rolefor answer visibility - No external orchestration needed for single elements
Print is bundled by Vite, per element package, from the single
src/print/index.tsx entry. Two artifacts are produced:
| Artifact | Built by | Purpose |
|---|---|---|
dist/print/index.js |
the package's vite.config.ts |
Node / bundler ESM. Keeps bare imports (@pie-element/*, @pie-lib/*, …). Loads in a browser only when the host resolves them — via a full import map or a bundler. Not self-contained. |
dist/browser/print/index.js |
tools/vite/element-browser.config.ts |
Browser ESM. This is what the item-level player loads. Self-contained except for React (only react / react-dom stay external). |
The browser build externalizes only React, per
tools/vite/browser-esm-policy.json:
{
"allowedBareImports": [
"react", "react/jsx-runtime", "react/jsx-dev-runtime",
"react-dom", "react-dom/client"
],
"sharedDependencyVersions": { "react": "18.2.0", "react-dom": "18.2.0" }
}Everything else the print module needs — @pie-lib/* (render-ui,
correct-answer-toggle, icons, translator), math rendering (including MathJax),
lodash, MUI, Emotion, and any private child elements (e.g. EBSR's internal
multiple-choice) — is inlined into the bundle's own chunks.
React stays external because it must be a single shared instance on the page.
The item-level player supplies it at load time through an injected import
map that resolves react / react-dom to one pinned singleton
(React 18.2.0).
classic pie-elements |
pie-elements-ng |
|
|---|---|---|
| Bundler | pslb |
Vite |
| Print artifact | module/print.js |
dist/browser/print/index.js |
| Shared at runtime | React and a broad set of libs (MUI, render-ui, correct-answer-toggle, math-rendering) via shared DLL modules (@pie-lib/shared-module, @pie-lib/math-rendering-module) |
Only React (react / react-dom / JSX runtimes) |
| Everything else | provided by the shared modules at runtime | inlined into the element's own print bundle |
| React provided by | shared DLL modules | an import map injected by the player |
✅ Single element rendering ✅ Have direct component access ✅ Simple use cases ✅ Can pass print-appropriate props directly
Example:
<pie-element-player
view="delivery"
element-name="multiple-choice"
model={{ ...model, disabled: true }}
session={{}}
></pie-element-player>✅ Explicit print intent in code ✅ Cleaner API (role vs complex env) ✅ Element development/testing ✅ Print-focused demos
Example:
<pie-element-player
view="print"
element-name="multiple-choice"
role="student"
model={model}
></pie-element-player>✅ Multi-element items (passage + questions + rubrics) ✅ Markup strings from CMS/database ✅ Production applications (pieoneer pattern) ✅ Floater elements (not in markup) ✅ Dynamic CDN loading
Example:
<pie-print config={itemConfig}></pie-print>| Feature | Element-Level | Item-Level |
|---|---|---|
| Package | @pie-element/element-player |
@pie-players/pie-print-player |
| Tag | <pie-element-player view="print"> |
<pie-print> |
| Input | Element name + model | Full item config |
| Markup | Not used | HTML string with elements |
| Elements | Single | Multiple |
| Loading | Import maps | Dynamic CDN imports |
| Registration | Fixed tag names | Hash-based unique names |
| Use Case | Development | Production |
The element-demo app uses the element-level player:
<!-- apps/element-demo/src/lib/element-player/components/PrintView.svelte -->
<pie-element-player
view="print"
element-name={elementName}
role={role}
model={model}
></pie-element-player>This provides:
- Consistent API with interactive demos
- Easy role switching (student/instructor)
- Automatic math rendering
- Clean, focused testing environment
The new item-level player (@pie-players/pie-print-player) is a drop-in replacement:
- <script src="https://cdn.jsdelivr.net/npm/@pie-framework/pie-print@2.7.0/lib/pie-print.js"></script>
+ <script src="https://cdn.jsdelivr.net/npm/@pie-players/pie-print-player/dist/print-player.js"></script>The <pie-print> API is the same; the URL resolution differs. pie-elements-ng
packages are loaded from the browser print artifact (dist/browser/print),
not the bundler-facing dist/print. The default resolver already does this, so
no resolve override is needed for standard packages. To load from a custom
host:
player.resolve = (tagName, pkg) => {
const [_, name, version] = pkg.match(/@pie-element\/(.*?)@(.*)/);
return Promise.resolve({
tagName,
pkg,
// Browser ESM artifact (self-contained except React)
url: `https://cdn.jsdelivr.net/npm/@pie-element/${name}@${version}/dist/browser/print/index.js`,
module: true,
loader: 'browser-esm',
});
};Note:
dist/print/index.jsalso exists and can be loaded in a browser, but it is not self-contained — its bare@pie-element/*/@pie-lib/*imports must be resolved by a full import map or a bundler, and loading the@pie-libgraph at runtime exposes it to transitive version mismatches. The player usesdist/browser/print/index.js, which only needs a React import map.
- Custom elements use the same
model/optionspattern andpreparePrintModel(model, opts). - Use
opts.role('student'|'instructor') for answer visibility and similar behavior.
-
Start the demo server:
bun cli dev:demo multiple-choice
-
Navigate to print route:
http://localhost:5173/multiple-choice/print -
Toggle between student/instructor roles
- Student: Shows questions only
- Instructor: Shows answers and rationales
-
The demo uses
<pie-element-player view="print">automatically
Print components are built as part of the main element build:
# Build specific element
bun run turbo build --filter @pie-element/multiple-choice
# Build all elements
bun run turbo build --filter "@pie-element/*"The build outputs dist/print/index.js (bundler ESM) and dist/print/index.d.ts,
plus dist/browser/print/index.js (the browser artifact the item-level player
loads). See How the Print Bundle Is Built.
- Element-Level Print Player README
- Item-Level Print Player README:
packages/print-player/README.mdin thepie-playersrepository - Item-Level Print Player Usage Examples:
packages/print-player/USAGE_EXAMPLE.mdin thepie-playersrepository - PieElementPlayer Source
Print elements are self-contained - they handle their own transformations and rendering.
Print players are orchestrators:
- Element-level: For development - loads single elements with import maps
- Item-level: For production - coordinates multiple elements from markup
Both use the same underlying print components, just at different scales and for different purposes.