Skip to content

Commit bf5b973

Browse files
authored
[52] support "variantKey" parameter in withToggledHookFactory, mirroring withTogglePointFactory (#53)
1 parent bc7f6fd commit bf5b973

66 files changed

Lines changed: 811 additions & 758 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ N.B. See changelogs for individual packages, where most change will occur:
1414

1515
This log covers the [monorepo](https://en.wikipedia.org/wiki/Monorepo).
1616

17+
## [0.11.0] - 2025-09-29
18+
19+
### Changed
20+
21+
- removed the `tags` badge from main `README.md`, this monorepo does not use release tags
22+
23+
### Added
24+
25+
- a `discussion` badge
26+
1727
## [0.10.7] - 2025-07-14
1828

1929
### Fixed

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
A library providing a means to toggle or branch web application code.
66
</p>
77
<p align="center">
8-
<a href="https://github.com/ASOS/web-toggle-point/tags/"><img src="https://img.shields.io/github/tag/ASOS/web-toggle-point" alt="Current version" /></a>
98
<img src="https://github.com/ASOS/web-toggle-point/workflows/Pull Request Checks/badge.svg" alt="Current test status" />
109
<a href="http://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs are welcome" /></a>
1110
<a href="https://github.com/ASOS/web-toggle-point/issues/"><img src="https://img.shields.io/github/issues/ASOS/web-toggle-point" alt="toggle point issues" /></a>
11+
<a href="https://github.com/ASOS/web-toggle-point/discussions/"><img src="https://img.shields.io/github/discussions/ASOS/web-toggle-point" alt="toggle point discussions" /></a>
1212
<img src="https://img.shields.io/github/stars/ASOS/web-toggle-point" alt="toggle point stars" />
1313
<img src="https://img.shields.io/github/forks/ASOS/web-toggle-point" alt="toggle point forks" />
1414
<img src="https://img.shields.io/github/license/ASOS/web-toggle-point" alt="toggle point license" />

examples/next/README.md

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,24 @@
11
# Next JS example
22

3-
This example shows the use of the [`react-pointcuts`](../../packages/react-pointcuts/docs/README.md), [`features`](../../packages/features/docs/README.md) and [`webpack`](../../packages/webpack/docs/README.md) packages, as part of an ["app router"](https://nextjs.org/docs/app) [Next.js](https://nextjs.org/) application.
3+
These examples show the use of the [`react-pointcuts`](../../packages/react-pointcuts/docs/README.md), [`features`](../../packages/features/docs/README.md) and [`webpack`](../../packages/webpack/docs/README.md) packages, as part of an ["app router"](https://nextjs.org/docs/app) [Next.js](https://nextjs.org/) application.
44

55
N.B. NextJs support is currently experimental, see [caveats](#caveats).
66

7-
## Setup
7+
## Examples
88

9-
It is using a contrived point cut plugin, replicating an Optimizely activation handler:
9+
1. [content management](./src/app/fixtures/content-management/README.mdx)
1010

11-
```js
12-
{
13-
onCodeSelected: ({ matchedFeatures }) => {
14-
if (matchedFeatures?.length) {
15-
const [[feature]] = matchedFeatures;
16-
console.log(
17-
`activated ${feature} with audience ${getFeatures().audience}`
18-
);
19-
}
20-
}
21-
}
22-
```
11+
This is a basic example demonstrating the ability to vary react hooks, using a contrived content management feature.
2312

24-
...which logs the activation event normally destined for the toggle router (Optimizely) to the console.
13+
2. [experiments](./src/app/fixtures/experiments/README.mdx)
2514

26-
A contrived server function called `getExperiments` exists to parse inbound headers containing experiments, used to drive the toggling.
27-
28-
## Usage
29-
30-
(from the `examples/next` folder of the monorepo)
31-
32-
1. install [mod header](https://modheader.com/), or some other tool for modifying request headers sent in a browser
33-
2. `npm install`
34-
4. `npm run dev`
35-
5. open `localhost:3000/fixtures/experiments` in a browser
36-
37-
See appropriate documentation within [the examples folder](./src/app/fixtures/experiments/README.md).
38-
39-
N.B. To confirm the `experiments` header you've set with `mod header`, you can add `?showExperiments=true` to the URL to render the value to the top of the page.
40-
If you're not seeing the experiments header show up, try refreshing the page. NextJs is perhaps pre-caching the pages.
15+
These examples show various toggle setups with react components being varied, opting out of variation, etc.
4116

4217
## Caveats
4318

44-
- Only client components are supported, since request-bound context is not supported by server components. They are not meant to be stateful.
45-
- API routes may be supportable, via use of [an async local storage wrapper](https://github.com/rexfordessilfie/nextwrappers/tree/main/packages/async-local-storage), once support for named exports is added ([Issue #4](https://github.com/ASOS/web-toggle-point/issues/4)) - since would need to match the HTTP verbs.
46-
- The webpack package cannot currently vary NextJs managed files such as [pages](https://nextjs.org/docs/app/building-your-application/routing/pages) themselves, but can vary modules they import ([Issue #9](https://github.com/ASOS/web-toggle-point/issues/9)).
19+
- Only client components can be toggled using a per-request features store, since request-bound context is not supported by server components. They are not meant to be stateful
20+
- API routes may be supportable, via use of [an async local storage wrapper](https://github.com/rexfordessilfie/nextwrappers/tree/main/packages/async-local-storage), once support for named exports is added ([Issue #4](https://github.com/ASOS/web-toggle-point/issues/4)) - since would need to match the HTTP verbs
21+
- The webpack package cannot currently vary some of NextJs' [filesystem convention files](https://nextjs.org/docs/pages/getting-started/project-structure#files-conventions) ([Issue #9](https://github.com/ASOS/web-toggle-point/issues/9))
4722
- The `webpack` plugin uses webpack hooks, so is incompatible with the new TurboPack bundler
48-
- The `webpack` plugin uses Node JS APIs to access the filesystem, so may be incompatible with [the edge runtime](https://nextjs.org/docs/app/api-reference/edge#unsupported-apis).
23+
- The `webpack` plugin uses Node JS APIs to access the filesystem, so may be incompatible with [the edge runtime](https://nextjs.org/docs/app/api-reference/edge#unsupported-apis)
24+
- The `nodeRequestScopedFeaturesStoreFactory` from the [`features`](../../packages/features/docs/README.md) package relies on singleton values held in top-level scope, which Next does not support. See [issue 50](https://github.com/ASOS/web-toggle-point/issues/50), which should remedy this.

examples/next/docs/CHANGELOG.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.0] - 2025-09-29
9+
10+
### Added
11+
12+
- added a "content management" example, demonstrating use of `withToggledHookFactory` from the `react-pointcuts` package
13+
14+
### Changed
15+
16+
- colocate documentation for "experiments" example to sit with its own `README.mdx`
17+
- update to static `webpackNormalModule` option of webpack package [version 0.8.1](../../../packages/webpack/docs/CHANGELOG.md#081---2025-07-27)
18+
- Updated to named exports version of "features" package ([0.4.0](../../../packages/features/docs/CHANGELOG.md#040---2025-07-15))
19+
20+
### Fixed
21+
22+
- consistent "Explanation" and "Activation" sections in example `README.mdx` files
23+
- removed errant `toggle-point.d.ts` in `tsconfig.json`
24+
- moved type packages to devDependencies
25+
826
## [0.2.5] - 2025-07-15
927

1028
### Changed
@@ -19,19 +37,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1937

2038
### Changed
2139

22-
- updated to use `variantGlobs` array, with updated webpack plugin [0.8.0][version 0.8.0](../../../packages/webpack/docs/CHANGELOG.md#080---2025-05-27)
40+
- updated to use `variantGlobs` array, with updated webpack plugin [version 0.8.0](../../../packages/webpack/docs/CHANGELOG.md#080---2025-05-27)
2341

2442
## [0.2.3] - 2025-02-07
2543

2644
### Fixed
2745

28-
- fixed a regression in the ability to use `?showExperiments` query in the experiments fixtures, regressed in version `0.2.0`.
46+
- fixed a regression in the ability to use `?showExperiments` query in the experiments fixtures, regressed in [version 0.2.0](#020---2024-12-06).
2947

3048
## [0.2.2] - 2024-12-24
3149

3250
### Fixed
3351

3452
- links to folders, not `README.mdx`, in the experiments examples
53+
- create `FeaturesProvider` via factory in outermost scope, rather than on each render of an example
3554

3655
## [0.2.1] - 2024-12-18
3756

examples/next/next-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference path="./.next/types/routes.d.ts" />
34

45
// NOTE: This file should not be edited
56
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

examples/next/next.config.mjs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
import createMDX from "@next/mdx";
22
import remarkGfm from "remark-gfm";
3+
import { TogglePointInjection } from "@asos/web-toggle-point-webpack/plugins";
4+
import experimentPointCutConfig from "./src/app/fixtures/experiments/__pointCutConfig.js";
5+
import contentManagementPointCutConfig from "./src/app/fixtures/content-management/__pointCutConfig.js";
6+
import webpackNormalModule from "next/dist/compiled/webpack/NormalModule.js";
37

48
/** @type {import('next').NextConfig} */
59
const nextConfig = {
610
pageExtensions: ["js", "md", "mdx", "ts", "tsx"]
711
};
812

9-
import { TogglePointInjection } from "@asos/web-toggle-point-webpack/plugins";
1013
const togglePointInjection = new TogglePointInjection({
11-
pointCuts: [
12-
{
13-
name: "experiments",
14-
togglePointModule: "/src/app/fixtures/experiments/withTogglePoint",
15-
variantGlobs: [
16-
"./src/app/fixtures/experiments/**/__variants__/*/*/!(*.spec).tsx"
17-
]
18-
}
19-
],
20-
webpackNormalModule: async () =>
21-
(await import("next/dist/compiled/webpack/NormalModule.js")).default
14+
pointCuts: [...experimentPointCutConfig, ...contentManagementPointCutConfig],
15+
webpackNormalModule
2216
});
2317

2418
nextConfig.webpack = (config) => {

examples/next/package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "web-toggle-point-next-example",
3-
"version": "0.2.5",
3+
"version": "0.3.0",
44
"private": true,
55
"type": "module",
66
"scripts": {
@@ -17,20 +17,21 @@
1717
"lint:docs": "eslint **/*.mdx"
1818
},
1919
"dependencies": {
20-
"@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts",
2120
"@asos/web-toggle-point-features": "file:../../packages/features",
21+
"@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts",
2222
"@asos/web-toggle-point-webpack": "file:../../packages/webpack",
2323
"@mdx-js/loader": "^3.1.0",
2424
"@mdx-js/react": "^3.1.0",
2525
"@next/mdx": "^15.0.3",
26-
"@types/mdx": "^2.0.13",
27-
"next": "^15.0.3",
28-
"react": "^18.3.1",
29-
"react-dom": "^18.3.1",
30-
"remark-gfm": "^4.0.0"
26+
"next": "^15.3.0",
27+
"remark-gfm": "^4.0.0",
28+
"turndown": "^7.2.0"
3129
},
3230
"devDependencies": {
3331
"@next/eslint-plugin-next": "^15.0.3",
32+
"@types/mdx": "^2.0.13",
33+
"@types/turndown": "^5.0.5",
34+
"@types/webpack-env": "^1.18.8",
3435
"eslint-config-next": "^15.0.3",
3536
"eslint-import-resolver-alias": "^1.1.2",
3637
"eslint-plugin-mdx": "^3.1.5",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Content Management
2+
==================
3+
4+
Explanation
5+
-----------
6+
7+
This example demonstrates the `withToggledHookFactory` from the `react-pointcuts` package, and the `reactContextFeaturesStoreFactory` from the `features` package.
8+
9+
A React hook is varied, from a ["no operation"](https://en.wikipedia.org/wiki/NOP_\(code\)) control version, to one that enables [design mode](https://developer.mozilla.org/en-US/docs/Web/API/Document/designMode) for the document.
10+
11+
Activation
12+
----------
13+
14+
If a cookie exists with the name `i-am-a-content-editor` and any value, on refreshing the page, the document becomes editable. Its background will be green to help indicate this state.
15+
16+
After making changes, pressing [Command](https://en.wikipedia.org/wiki/Command_key) and S (⌘+S or ⊞+S, per O/S) will save changes back to disk, via a [server function](https://react.dev/reference/rsc/server-functions). The background will flash blue whilst save is in progress.
17+
18+
N.B. The action uses [turndown](https://github.com/mixmark-io/turndown) to convert the HTML back to [Markdown](https://en.wikipedia.org/wiki/Markdown), so is limited by what that can interpret.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default [
2+
{
3+
name: "content management",
4+
togglePointModule: "/src/app/fixtures/content-management/withToggledHook",
5+
variantGlobs: [
6+
"./src/app/fixtures/content-management/__variants__/devMode/active/useContentEditable.ts"
7+
]
8+
}
9+
];
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useEffect, useRef, useTransition } from "react";
2+
import { saveMarkdown } from "../../../actions";
3+
import type TurndownService from "turndown";
4+
5+
const useContentEditable = () => {
6+
const [isPending, startTransition] = useTransition();
7+
const turndownServiceRef = useRef<TurndownService>(null);
8+
9+
useEffect(() => {
10+
if (isPending) {
11+
document.body.setAttribute("data-is-saving", "true");
12+
} else {
13+
document.body.removeAttribute("data-is-saving");
14+
}
15+
}, [isPending]);
16+
17+
const okKeyDown = (e: KeyboardEvent) => {
18+
if (e.metaKey && e.key === "s") {
19+
e.preventDefault();
20+
startTransition(async () => {
21+
await saveMarkdown(
22+
turndownServiceRef.current?.turndown(
23+
document.body.innerHTML.replaceAll(/<script>(.*?)<\/script>/g, "")!
24+
)!
25+
);
26+
});
27+
}
28+
};
29+
30+
useEffect(() => {
31+
void (async () => {
32+
if (!turndownServiceRef.current) {
33+
const { default: Service } = (await import("turndown")) as {
34+
default: typeof TurndownService;
35+
};
36+
turndownServiceRef.current = new Service();
37+
}
38+
})();
39+
document.designMode = "on";
40+
document.body.setAttribute("data-design-mode", "true");
41+
document.addEventListener("keydown", okKeyDown, false);
42+
43+
return () => {
44+
document.designMode = "off";
45+
document.body.removeAttribute("data-design-mode");
46+
document.removeEventListener("keydown", okKeyDown, false);
47+
};
48+
}, []);
49+
};
50+
51+
export default useContentEditable;

0 commit comments

Comments
 (0)