@metapages/metapage
v1.10.0
Published
Connect web pages together
Maintainers
Readme
@metapages/metapage
Build composable, interconnected web applications using iframes and data pipes
@metapages/metapage is a JavaScript library that lets you create and embed interactive workflows in the browser by connecting independent iframe components together through input/output data pipes.
Quick Links
What is this?
A metapage is a web application made up of connected iframes called metaframes. Each metaframe can:
- Receive data from other metaframes (inputs)
- Send data to other metaframes (outputs)
- Run independently (JavaScript, Docker containers, markdown editors, or any web component)
Think of it like a visual programming environment where each component is a full web application that can communicate with others through simple JSON data pipes.
Installation
npm install @metapages/metapageOr use directly from CDN:
import {
renderMetapage,
Metapage,
Metaframe,
} from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";Quick Start
Rendering a Metapage
The simplest way to embed a workflow is using renderMetapage:
import { renderMetapage } from "@metapages/metapage";
// Fetch a metapage definition (or define your own JSON)
const response = await fetch(
"https://metapage.io/m/87ae11673508447e883b598bf7da9c5d/metapage.json",
);
const definition = await response.json();
// Render it
const { setInputs, dispose } = await renderMetapage({
definition,
rootDiv: document.getElementById("container"),
});The renderMetapage function the react-grid-layout layout in metapage.json:
{
"meta": {
"layouts": {
"react-grid-layout": {
...
}
}
}
}Creating a Metaframe (Inside an iframe)
If you're building a component to use in a metapage:
import { Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";
const metaframe = new Metaframe();
// Listen for input data from other metaframes
metaframe.onInput("data", (value) => {
console.log("Received:", value);
// Process the data and send output
metaframe.setOutput("result", value.toUpperCase());
});
// Or listen to all inputs at once
metaframe.onInputs((inputs) => {
console.log("All inputs:", inputs);
});Core Concepts
Metapage Definition
A metapage is defined using JSON that specifies which metaframes to load and how they connect:
{
"version": "2",
"metaframes": {
"input": {
"url": "https://editor.mtfm.io/#?hm=disabled"
},
"processor": {
"url": "https://js.mtfm.io/",
"inputs": [
{
"metaframe": "input",
"source": "text",
"target": "code"
}
]
},
"output": {
"url": "https://markdown.mtfm.io/",
"inputs": [
{
"metaframe": "processor",
"source": "output"
}
]
}
}
}This creates a pipeline: input → processor → output
Metaframe Definition
See code
This is provided either by:
https://<your metaframe>/metaframe.jsonhttps://<your metaframe>/#?definition=<json encoded hash param>
The definition describes inputs, outputs, security, and the types of hash parameters (so AI tools can correctly modify)
export interface MetaframeDefinitionV2 {
version: VersionsMetaframe;
inputs?: {
[key: string]: MetaframePipeDefinition;
}; // <MetaframePipeId, MetaframePipeDefinition>
outputs?: {
[key: string]: MetaframePipeDefinition;
}; // <MetaframePipeId, MetaframePipeDefinition>
metadata: MetaframeMetadataV2;
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Feature_Policy/Using_Feature_Policy#the_iframe_allow_attribute
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy#directives
allow?: string;
// Set or override allowed features for the iframe
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox
sandbox?: string;
// Hash parameters configuration.
// Accepts both legacy array format (string[]) and new object format (HashParamsObject).
// When fetched via helper methods, array format is normalized to object format.
hashParams?: HashParamsRaw;
}Data Pipes
Pipes connect metaframe outputs to other metaframe inputs:
{
"metaframe": "sourceMetaframeId", // Where data comes from
"source": "outputPipeName", // Name of the output pipe
"target": "inputPipeName" // Name of the input pipe (optional, defaults to source)
}Working with Data
The library automatically handles serialization of complex data types:
// In a metaframe - these are automatically serialized when sent between iframes
metaframe.setOutput("file", new File([blob], "data.txt"));
metaframe.setOutput("binary", new Uint8Array([1, 2, 3]));
metaframe.setOutput("buffer", arrayBuffer);
// And automatically deserialized when received
metaframe.onInput("file", (file) => {
console.log(file instanceof File); // true
});Usage Examples
Full HTML Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<style>
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
}
#metapage-container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="metapage-container"></div>
<script type="module">
import { renderMetapage } from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";
const definition = await fetch(
"https://metapage.io/m/87ae11673508447e883b598bf7da9c5d/metapage.json",
).then((r) => r.json());
const { setInputs, dispose, metapage } = await renderMetapage({
definition,
rootDiv: document.getElementById("metapage-container"),
onOutputs: (outputs) => {
console.log("Metaframe outputs:", outputs);
},
options: {
hideFrameBorders: true,
hideOptions: true,
},
});
// Send inputs to metaframes
setInputs({
metaframeId: {
inputPipeName: "some value",
},
});
// Clean up when done
// dispose();
</script>
</body>
</html>Building a Metaframe Component
import { Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";
const metaframe = new Metaframe();
// Handle inputs
metaframe.onInputs((inputs) => {
const { data, config } = inputs;
// Process inputs
const result = processData(data, config);
// Send outputs
metaframe.setOutputs({
result: result,
timestamp: Date.now(),
});
});
// Individual input listener
metaframe.onInput("reset", () => {
metaframe.setOutputs({});
});
// Get a specific input value
const currentValue = metaframe.getInput("data");
// Get all inputs
const allInputs = metaframe.getInputs();
// Clean up
metaframe.dispose();Programmatic Metapage Control
import { Metapage } from "@metapages/metapage";
const metapage = new Metapage({
definition: {
version: "2",
metaframes: {
viewer: {
url: "https://markdown.mtfm.io/",
},
},
},
});
// Listen to metaframe outputs
metapage.on(Metapage.OUTPUTS, (outputs) => {
console.log("Outputs from all metaframes:", outputs);
});
// Set inputs to metaframes
await metapage.setInputs({
viewer: {
text: "# Hello World",
},
});
// Get current outputs
const outputs = metapage.getState().metaframes.outputs;
// Clean up
metapage.dispose();Advanced Features
Hash Parameters in Metaframes
Metaframes can read and write to their URL hash parameters:
import {
getHashParamValueJsonFromWindow,
setHashParamValueJsonInWindow,
} from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";
// Read from URL hash
const config = getHashParamValueJsonFromWindow("config");
// Write to URL hash
setHashParamValueJsonInWindow("config", { theme: "dark" });Pattern Matching in Pipes
Use glob patterns to match multiple outputs:
{
"inputs": [
{
"metaframe": "source",
"source": "data/*", // Matches data/foo, data/bar, etc.
"target": "inputs/"
}
]
}Binary Data Handling
// Send binary data
const imageData = await fetch("/image.png").then((r) => r.arrayBuffer());
metaframe.setOutput("image", imageData);
// Receive and use
metaframe.onInput("image", async (data) => {
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
document.getElementById("img").src = url;
});API Overview
renderMetapage(options)
Render a metapage into a DOM element.
Parameters:
definition: Metapage definition objectrootDiv: DOM element to render intoonOutputs: Callback for metaframe outputs (optional)options: Rendering options (optional)hideBorder: Hide metapage borderhideFrameBorders: Hide individual metaframe bordershideOptions: Hide options panelhideMetaframeLabels: Hide metaframe labels
Returns: { setInputs, setOutputs, dispose, metapage }
Metapage Class
Methods:
setInputs(inputs): Set inputs for metaframesgetState(): Get current state (inputs/outputs)dispose(): Clean up and remove all listenerson(event, handler): Listen to events
Events:
Metapage.OUTPUTS: When metaframe outputs changeMetapage.INPUTS: When metapage inputs changeMetapage.DEFINITION: When definition changes
Metaframe Class
Methods:
setOutput(name, value): Set a single outputsetOutputs(outputs): Set multiple outputsgetInput(name): Get a single input valuegetInputs(): Get all input valuesonInput(name, callback): Listen to specific inputonInputs(callback): Listen to all inputsdispose(): Clean up
Properties:
id: Metaframe ID assigned by parent metapageisInputOutputBlobSerialization: Enable/disable automatic binary serialization
Creating Your Own Metaframes
Any web application can become a metaframe by:
- Loading the library
- Creating a
Metaframeinstance - Listening for inputs
- Sending outputs
Example minimal metaframe:
<!DOCTYPE html>
<html>
<head>
<title>My Metaframe</title>
</head>
<body>
<script type="module">
import { Metaframe } from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";
const metaframe = new Metaframe();
metaframe.onInputs((inputs) => {
// Your logic here
metaframe.setOutput("result", "processed: " + JSON.stringify(inputs));
});
</script>
</body>
</html>TypeScript Support
Full TypeScript definitions are included:
import {
Metapage,
Metaframe,
MetapageDefinitionV2,
MetaframeInputMap,
MetapageInstanceInputs,
} from "https://cdn.jsdelivr.net/npm/@metapages/[email protected]";
const definition: MetapageDefinitionV2 = {
version: "2",
metaframes: {
example: {
url: "https://example.com",
},
},
};
const metapage = new Metapage({ definition });Browser Support
- Chrome 78+
- Modern browsers with ES2020 support
- ES modules required
License
Apache-2.0
Contributing
Issues and pull requests welcome at https://github.com/metapages/metapage
