Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fierce-wolves-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@merkur/preact": major
---

Remove the `./webpack` subpath export and its associated peer dependencies.

- **What** The `@merkur/preact/webpack` subpath export has been deleted. The peer dependencies `@merkur/tool-webpack`, `babel-loader`, and `@babel/preset-react` are no longer declared or required by this package.
- **Why** Webpack-based tooling was superseded by the Vite/esbuild pipeline. Maintaining a parallel Webpack integration added complexity and prevented simplifying the package's dependency surface. Removing it reduces install size and eliminates the need to keep Babel peer deps in sync.
- **How** Replace any import from `@merkur/preact/webpack` with the Vite/Storybook-based equivalent provided by `@merkur/tool-storybook`. Remove `@merkur/tool-webpack`, `babel-loader`, and `@babel/preset-react` from your project's dependencies if they were pulled in solely for Merkur's Webpack integration.
9 changes: 9 additions & 0 deletions .changeset/happy-birds-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@merkur/tool-storybook": minor
---

Add vanilla JS widget support and preview configuration helpers to Storybook tooling.

- **What** New `createPreviewConfig` helper provides a simplified Storybook `preview.js` setup with automatic widget name/version validation. New `createVanillaRenderer` enables rendering vanilla JavaScript widgets inside Storybook stories; a per-widget WeakMap ensures isolated state across concurrent stories. `createWidgetLoader` now correctly unmounts the previous widget before mounting a new one when switching stories, preserves `state`/`props` keys not present in story args, and supports both `setProps` and `setState` widget APIs. All exported functions are documented with comprehensive JSDoc. Fixed ES11 module format issues that prevented the package from being consumed in certain build pipelines.
- **Why** Previously only Preact-based widgets had first-class Storybook support. Vanilla JS widgets had no renderer, forcing teams to wire up lifecycle management by hand. The missing `createPreviewConfig` abstraction caused boilerplate duplication across projects, and the lifecycle bugs in `createWidgetLoader` led to stale widget instances between story navigations.
- **How** No breaking changes. Opt in to the new helpers by importing them from `@merkur/tool-storybook`. Use `createPreviewConfig({ name, version })` in `.storybook/preview.js` and `createVanillaRenderer()` as the story renderer for vanilla widgets.
9 changes: 9 additions & 0 deletions .changeset/tiny-sparks-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@merkur/create-widget": patch
---

Fix vanilla ErrorView template to use correct HTML attribute and prevent XSS.

- **What** The generated `ErrorView` template in the vanilla widget scaffold used the JSX attribute `className` instead of the HTML attribute `class`, causing the CSS class to be silently dropped. Additionally, interpolated values (`status`, `message`, `stack`) were injected raw into the HTML string, opening a cross-site scripting (XSS) vulnerability.
- **Why** Vanilla templates are plain HTML strings — not JSX — so `className` is not interpreted. Raw interpolation of untrusted error data (e.g. a server-side message) could allow script injection in any app that renders the error view.
- **How** Nothing.
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
function escHtml(s) {
return String(s)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}

export default function ErrorView(widget) {
return `
<div className='merkur__error'>
<h1>Status: ${widget.error.status}</h1>
<h2>Message: ${widget.error.message}</h2>
<pre>${widget.error.stack}</pre>
<div class='merkur__error'>
<h1>Status: ${escHtml(widget.error.status)}</h1>
<h2>Message: ${escHtml(widget.error.message)}</h2>
<pre>${escHtml(widget.error.stack)}</pre>
</div>
`;
}
1 change: 0 additions & 1 deletion packages/preact/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
!cli/**/*
!lib/**/*
!entries/**/*
!webpack/**/*
!package.json
90 changes: 57 additions & 33 deletions packages/preact/README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,75 @@
<p align="center">
<a href="https://merkur.js.org/docs/getting-started" title="Getting started">
<img src="https://raw.githubusercontent.com/mjancarik/merkur/master/images/merkur-logo.png" width="100px" height="100px" alt="Merkur illustration"/>
</a>
</p>

# Merkur
# @merkur/preact

[![Build Status](https://github.com/mjancarik/merkur/workflows/CI/badge.svg)](https://github.com/mjancarik/merkur/actions/workflows/ci.yml)
[![NPM package version](https://img.shields.io/npm/v/@merkur/core/latest.svg)](https://www.npmjs.com/package/@merkur/core)
![npm bundle size (scoped version)](https://img.shields.io/bundlephobia/minzip/@merkur/core/latest)
[![NPM package version](https://img.shields.io/npm/v/@merkur/preact/latest.svg)](https://www.npmjs.com/package/@merkur/preact)
![npm bundle size (scoped version)](https://img.shields.io/bundlephobia/minzip/@merkur/preact/latest)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)

The [Merkur](https://merkur.js.org/) is tiny extensible javascript library for front-end microservices(micro frontends). It allows by default server side rendering for loading performance boost. You can connect it with other frameworks or languages because merkur defines easy API. You can use one of six predefined template's library [Preact](https://preactjs.com/), [µhtml](https://github.com/WebReflection/uhtml#readme), [Svelte](https://svelte.dev/) and [vanilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) but you can easily extend for others.

## Features
- Flexible templating engine
- Usable with all tech stacks
- SSR-ready by default
- Easy extensible with plugins
- Tiny - 1 KB minified + gzipped
Preact integration helpers for [Merkur](https://merkur.js.org/) widgets. Provides client and server entry points, rendering helpers, and CLI scaffolding support for Preact-based Merkur widgets.

## Getting started
## Installation

```bash
npx @merkur/create-widget <name>
npm install @merkur/preact
```

cd name
Peer dependencies required:

npm run dev // Point your browser at http://localhost:4444/
```bash
npm install @merkur/core @merkur/plugin-component
```
![alt text](https://raw.githubusercontent.com/mjancarik/merkur/master/images/hello-widget.png "Merkur example, hello widget")
## Documentation

To check out [live demo](https://merkur.js.org/demo) and [docs](https://merkur.js.org/docs), visit [https://merkur.js.org](https://merkur.js.org).
## Exports

## Contribution
| Export | Description |
|--------|-------------|
| `@merkur/preact/client` | Client-side Preact rendering helpers |
| `@merkur/preact/server` | Server-side rendering helpers (uses `preact-render-to-string`) |
| `@merkur/preact/entries/client.js` | Preact client widget entry point |
| `@merkur/preact/entries/server.js` | Preact server widget entry point |
| `@merkur/preact/cli` | CLI helpers for widget scaffolding |

Contribute to this project via [Pull-Requests](https://github.com/mjancarik/merkur/pulls).
## Documentation

We are using [Changesets](https://github.com/changesets/changesets) for versioning and releasing. To add a changeset describing your changes, run `npm run changeset` from the root of the monorepo.
Full documentation and setup guide at [merkur.js.org](https://merkur.js.org/docs/getting-started).

## Breaking Changes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be in a changeset instead of README?

I guess the version does not match anymore either, but if we put it in the changeset, it will be always correct.


### v0.47.0

The `@merkur/preact/webpack` export (`applyBabelLoader`, `applyPreactConfig`) has been **removed**, along with the `@babel/preset-react`, `@merkur/tool-webpack`, and `babel-loader` peer dependencies.

If your webpack config used these helpers, configure the Preact Babel preset manually:

```javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(js|ts|tsx|jsx|mjs)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-react',
{ runtime: 'automatic', importSource: 'preact' },
],
],
},
},
},
],
},
};
```

> **Note:** The release process and [changeset format](https://github.com/mjancarik/merkur#changeset-format) are documented in the [root README](https://github.com/mjancarik/merkur#contribution), which is the source of truth for all contribution and release guidelines.
Alternatively, migrate to the Vite-based Storybook setup described in the [Storybook integration guide](https://merkur.js.org/docs/storybook-integration-into-merkur), which no longer requires webpack or Babel configuration.

---
## Contribution

Thank you to all the people who already contributed to Merkur!
Contribute via [Pull-Requests](https://github.com/mjancarik/merkur/pulls).

<a href="https://github.com/mjancarik/merkur/graphs/contributors">
<img src="https://contrib.rocks/image?repo=mjancarik/merkur" />
</a>
We use [Changesets](https://github.com/changesets/changesets) for versioning. Run `npm run changeset` from the monorepo root to add a changeset for your changes.
6 changes: 1 addition & 5 deletions packages/preact/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"exports": {
"./entries/client.js": "./entries/client.js",
"./entries/server.js": "./entries/server.js",
"./webpack": "./webpack/index.js",
"./cli": "./cli/index.mjs",
"./client": {
"types": "./lib/client/client.d.ts",
Expand Down Expand Up @@ -44,14 +43,11 @@
"access": "public"
},
"dependencies": {
"@babel/preset-react": "^7.28.5",
"preact": "^10.27.2",
"preact-render-to-string": "^6.6.3"
},
"peerDependencies": {
"@merkur/core": ">=0.34",
"@merkur/plugin-component": ">=0.34",
"@merkur/tool-webpack": ">=0.28",
"babel-loader": ">=9"
"@merkur/plugin-component": ">=0.34"
}
}
71 changes: 0 additions & 71 deletions packages/preact/webpack/index.js

This file was deleted.

58 changes: 57 additions & 1 deletion packages/tool-storybook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,63 @@

The module enables integration of [Storybook](https://storybook.js.org/) into [Merkur](https://merkur.js.org/).

**[Documentation for @merkur/tool-storybook](https://merkur.js.org/docs/storybook-integration-into-merkur).**
**[Full documentation](https://merkur.js.org/docs/storybook-integration-into-merkur).**

## API

### `createPreviewConfig(options)`

Registers a Merkur widget with Merkur's factory and returns a partial Storybook `preview.mjs` configuration (`{ loaders }`). Spread the result into your preview export.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `widgetProperties` | `Object` | ✅ | Widget definition object — must include `name` and `version`. |
| `render` | `Function` | — | Called each time the widget's update lifecycle fires, receives the widget instance. Defaults to a no-op. |
| `createWidget` | `Function` | — | Widget factory function. Defaults to `createMerkurWidget` from `@merkur/core`. |

```javascript
import { createPreviewConfig } from '@merkur/tool-storybook';
import widgetProperties from '../src/widget.js';

export default {
...createPreviewConfig({ widgetProperties, render: myRenderCallback }),
};
```

### `createVanillaRenderer(options)`

Creates a `render` / `update` pair for vanilla JavaScript widgets that produce HTML strings. Pass `render` as the Storybook story render function and `render: renderer.update` to `createPreviewConfig` so state changes trigger re-renders.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `ViewComponent` | `Function \| Object<string, Function>` | ✅ | A single view function `(widget) => htmlString`, or a named map with a `"default"` key. |
| `bindEvents` | `Function` | — | Called after every render: `(container, widget) => void`. Falls back to `widget.View.bindEvents` if present. |

```javascript
import { createPreviewConfig, createVanillaRenderer } from '@merkur/tool-storybook';
import widgetProperties from '../src/widget.js';

const renderer = createVanillaRenderer({
ViewComponent: { default: (widget) => `<div>${widget.state.counter}</div>` },
bindEvents(container, widget) {
container.querySelector('button')?.addEventListener('click', widget.onClick);
},
});

export default {
...createPreviewConfig({ widgetProperties, render: renderer.update }),
render: renderer.render,
};
```

### `createWidgetLoader(options)`

Low-level factory that returns a single Storybook loader function. `createPreviewConfig` uses this internally. Use it directly when you need more control over widget registration.

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `widgetProperties` | `Object` | ✅ | Widget definition object — must include `name` and `version`. |
| `render` | `Function` | — | Render callback invoked on widget updates. Defaults to a no-op. |

## About Merkur

Expand Down
2 changes: 1 addition & 1 deletion packages/tool-storybook/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const defaultConfig = require('../../jest.config.js');

module.exports = { ...defaultConfig };
module.exports = { ...defaultConfig, testEnvironment: 'jsdom' };
Loading