Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d0ce7f1
change initial commit message in changelogs (#3)
TomStrepsil Dec 24, 2024
27e436a
[#18] Fix JSDoc module names (#19)
TomStrepsil Jan 30, 2025
1f0cb29
[26] Fix public/scoped package publishing (#27)
TomStrepsil Mar 6, 2025
d85e398
fix registry url etc (#34)
TomStrepsil Mar 6, 2025
cd2fc82
Merge branch 'main' of github.com:TomStrepsil/web-toggle-point
TomStrepsil Apr 13, 2025
d20a886
Merge branch 'ASOS:main' into main
TomStrepsil Jul 14, 2025
710d357
Merge branch 'ASOS:main' into main
TomStrepsil Jul 14, 2025
f488cab
Merge branch 'ASOS:main' into main
TomStrepsil Jul 14, 2025
6d90bc3
Merge branch 'ASOS:main' into main
TomStrepsil Jul 14, 2025
e942cde
Extract portions of load strategies update and apply separately
TomStrepsil Jul 29, 2025
e41ef94
use get()
TomStrepsil Jul 29, 2025
a30583b
fix version link in CHANGELOGs to use correct date
TomStrepsil Aug 4, 2025
42a0784
more CHANGELOG date fix
TomStrepsil Aug 4, 2025
904e2e5
handle circular references
TomStrepsil Aug 19, 2025
d199c1c
try being explicit about type import?
TomStrepsil Aug 19, 2025
bad0091
import playwright types explicitly
TomStrepsil Aug 19, 2025
334d8f9
more type explicitness
TomStrepsil Aug 19, 2025
4eba157
more explicit type imports
TomStrepsil Aug 19, 2025
7c71a5a
sigh... more types
TomStrepsil Aug 19, 2025
955080f
version & changelog for test/automation
TomStrepsil Aug 21, 2025
b4b0f42
parallel folder example
TomStrepsil Aug 21, 2025
5a50097
changelog
TomStrepsil Aug 22, 2025
0039376
fix path in readme
TomStrepsil Aug 22, 2025
410a3e3
readme updates
TomStrepsil Aug 22, 2025
05bcc4f
update for change in initial state
TomStrepsil Aug 22, 2025
00e7a27
remove require.cache hackery
TomStrepsil Aug 22, 2025
bd432ac
update README
TomStrepsil Aug 22, 2025
503a06e
add "feature 5" to add a new redux slice entirely
TomStrepsil Aug 22, 2025
721b711
move getFeatures into serialization plumbing
TomStrepsil Aug 23, 2025
e371203
remove chatGPTs helpful comments
TomStrepsil Aug 23, 2025
c4d64bc
match sizes of the TopBox
TomStrepsil Aug 26, 2025
63f33b7
merge main
TomStrepsil Oct 21, 2025
28ae6b6
Merge branch 'main' into issue/46-parallel-filesystem-convention-example
TomStrepsil Oct 21, 2025
4c2060c
revert workspaces lint change
TomStrepsil Oct 21, 2025
8aa52e4
revert toVisible change to reduce noise
TomStrepsil Oct 21, 2025
627e7c4
EOL
TomStrepsil Oct 21, 2025
f6891e6
changelogs for eslint change
TomStrepsil Oct 21, 2025
ccef8eb
revert toBeVisible change to reduce noise
TomStrepsil Oct 21, 2025
84df310
more toBeVisible noise reduction
TomStrepsil Oct 21, 2025
5926db1
more toBeVisible stuff
TomStrepsil Oct 21, 2025
2a831e9
empty commit to kick off CI
TomStrepsil Oct 21, 2025
d1bc3a1
british spelling. consistent punctuation.
TomStrepsil Oct 21, 2025
75a5829
update docs for variantPathMap (missed in last update)
TomStrepsil Oct 22, 2025
baf68c4
add playwright spec
TomStrepsil Oct 23, 2025
5314649
empty commit to attempt to get around report merger failure
TomStrepsil Oct 23, 2025
42fcdd1
update README
TomStrepsil Oct 28, 2025
8e8afbf
add missing feature5 to readme
TomStrepsil Nov 10, 2025
55a082f
no content, to kick off flaky pipeline
TomStrepsil Nov 10, 2025
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
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This log covers the [monorepo](https://en.wikipedia.org/wiki/Monorepo).

- updated to `0.11.0` of [`eslint-plugin-workspaces`](https://github.com/joshuajaco/eslint-plugin-workspaces) after [addition of ESLint9 support](https://github.com/joshuajaco/eslint-plugin-workspaces/commit/af855c3a3d8069366d4446747e91828ddf7560c6)
- update `eslint.config.mjs` to utilise flat config
- updated `eslint` to 9.38.0

## [0.12.0] - 2025-09-30

Expand Down
10 changes: 10 additions & 0 deletions examples/express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,13 @@ Some example applications based on an [express](https://expressjs.com/) router
2. [config](./src/routes/config/README.md)

This example shows the use of the [`react-pointcuts`](../../packages/react-pointcuts/docs/README.md), [`features`](../../packages/features/docs/README.md), [`ssr`](../../packages/ssr/docs/README.md) and [`webpack`](../../packages/webpack/docs/README.md) packages.

3. [parallel-folder-convention](./src/routes/parallel-folder-convention/README.md)

This example shows the use of the [`react-pointcuts`](../../packages/react-pointcuts/docs/README.md), [`features`](../../packages/features/docs/README.md), [`ssr`](../../packages/ssr/docs/README.md) and [`webpack`](../../packages/webpack/docs/README.md) packages.

It has a bespoke filesystem convention, with parallel directory hierarchies containing arbitrary replacements and patches of various types of module, including:
- constants
- css
- react components
- [redux slices](https://redux.js.org/tutorials/essentials/part-2-app-structure#redux-slices)
10 changes: 10 additions & 0 deletions examples/express/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2025-10-21

### Changed

- added "parallel folder convention" example

### Fixed

- went ltd cmdr data on the contraction "I'm", converting to "I am" since the "large" example inexplicably no longer text-matched (space before apostrophe)

## [0.3.1] - 2025-10-21

### Fixed
Expand Down
30 changes: 29 additions & 1 deletion examples/express/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
import examplesConfig from "../eslint.config.mjs";
import asosConfigReact from "../../peripheral/eslint-config-asosconfig/react.js";
import asosConfigServer from "../../peripheral/eslint-config-asosconfig/server.js";
import parser from "@typescript-eslint/parser";
import globals from "globals";

export default [
...[...asosConfigReact, ...asosConfigServer].map((config) => ({
files: ["**/*.js"],
files: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"],
...config,
settings: {
"import/resolver": {
typescript: {
alwaysTryTypes: true,
project: "./src/routes/parallel-folder-convention/tsconfig.json"
}
},
react: {
version: "detect"
}
},
rules: {
...config.rules,
"react/prop-types": "off",
"prettier/prettier": [
"error",
{
trailingComma: "none",
endOfLine: "auto"
}
]
},
languageOptions: {
...config.languageOptions,
parser,
globals: {
CLIENT: "readonly",
...globals.browser,
...globals.node
}
}
})),
...examplesConfig,
Expand Down
18 changes: 12 additions & 6 deletions examples/express/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web-toggle-point-express-example",
"version": "0.3.1",
"version": "0.4.0",
"type": "module",
"engines": {
"node": ">=20.6.0"
Expand All @@ -16,32 +16,38 @@
"prelint": "npm run build-dependencies",
"lint": "npm run lint:code && npm run lint:docs",
"lint:fix": "npm run lint:code -- --fix && npm run lint:docs -- --fix",
"lint:code": "eslint src --flag unstable_config_lookup_from_file",
"lint:docs": "eslint *.md --flag unstable_config_lookup_from_file"
"lint:code": "eslint src --flag v10_config_lookup_from_file",
"lint:docs": "eslint *.md --flag v10_config_lookup_from_file"
},
"dependencies": {
"@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts",
"@asos/web-toggle-point-features": "file:../../packages/features",
"@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts",
"@asos/web-toggle-point-ssr": "file:../../packages/ssr",
"@asos/web-toggle-point-webpack": "file:../../packages/webpack",
"@reduxjs/toolkit": "^2.8.2",
"cross-env": "^7.0.3",
"express": "^4.17.1",
"http-status-codes": "^2.3.0",
"react": ">=17",
"react-dom": ">=17"
"react-dom": ">=17",
"react-redux": "^9.2.0",
"valtio": "^2.1.5"
},
"devDependencies": {
"babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"enhanced-tsconfig-paths-webpack-plugin": "^0.2.3",
"mini-css-extract-plugin": "^2.9.2",
"path-exists-cli": "^2.0.0",
"prop-types": "^15.7.2",
"source-map-loader": "^5.0.0",
"style-loader": "^4.0.0",
"ts-loader": "^9.5.2",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.2",
"webpack-node-externals": "^3.0.0"
},
"peerDependencies": {
"@playwright/test": "^1.49.0"
"@playwright/test": "^1.56.0"
}
}
3 changes: 3 additions & 0 deletions examples/express/src/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import express from "express";
import animalsRouter from "./routes/animals/router.js";
import configRouter from "./routes/config/router.js";
import parallelFolderConventionRouter from "./routes/parallel-folder-convention/router.tsx";

const app = express();
const PORT = process.env.PORT;

app.use("/animals", animalsRouter);
app.use("/config", configRouter);
app.use("/parallel-folder-convention", parallelFolderConventionRouter);
app.get("/", (_, response) => {
response.send(`<!DOCTYPE html>
<html lang="en">
Expand Down Expand Up @@ -39,6 +41,7 @@ app.get("/", (_, response) => {
<ul>
<li><a href="/animals">Version header with nodeRequestScoped store</a></li>
<li><a href="/config">.env config with ssrBackedReactContext store for initial value in browser</a></li>
<li><a href="/parallel-folder-convention">parallel folder convention, with varied constants, css, react & redux</a></li>
</ul>
</body>
</html>`);
Expand Down
2 changes: 1 addition & 1 deletion examples/express/src/routes/config/Component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const Component = () => (
<div
style={{ fontSize: "18pt", width: "50vw", minWidth: "6em", height: "50vh" }}
>
I'm Medium
I am Medium
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Component = ({ backgroundColor }) => (
backgroundColor
}}
>
I'm Large
I am Large
</div>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Component = ({ backgroundColor }) => (
backgroundColor
}}
>
I'm Small
I am Small
</div>
);

Expand Down
4 changes: 2 additions & 2 deletions examples/express/src/routes/config/playwright.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test.describe("config endpoint", () => {
});

test("it shows a varied experience", async ({ page }) => {
await expect(page.getByText(`I'm ${size}`)).toBeInViewport();
await expect(page.getByText(`I am ${size}`)).toBeVisible();
});

scenarios
Expand All @@ -25,7 +25,7 @@ test.describe("config endpoint", () => {
page
}) => {
await page.getByRole("button", { name: otherSize }).click();
await expect(page.getByText(`I'm ${otherSize}`)).toBeInViewport();
await expect(page.getByText(`I am ${otherSize}`)).toBeVisible();
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion examples/express/src/routes/config/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ router.get("/*", (req, res) => {
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossOrigin={"true"}
crossOrigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css2?family=Didact+Gothic&display=swap"
Expand Down
25 changes: 25 additions & 0 deletions examples/express/src/routes/parallel-folder-convention/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import TopBox from "./components/TopBox";
import BottomBox from "./components/BottomBox";
import StateProvider from "./StateProvider";
import { getFeatures, setValue } from "./toggle-plumbing/featuresStore";
import "./styles.css";

export default function App() {
return (
<StateProvider>
<select
onChange={({ target: { value: selection } }) => setValue({ selection })}
defaultValue={getFeatures().selection}
>
<option>baseline 🐶</option>
<option value="feature1">feature 1 🐱</option>
<option value="feature2">feature 2 🐹</option>
<option value="feature3">feature 3 🐰</option>
<option value="feature4">feature 4 🦀</option>
<option value="feature5">feature 5 🪐</option>
</select>
<TopBox />
<BottomBox />
</StateProvider>
);
}
143 changes: 143 additions & 0 deletions examples/express/src/routes/parallel-folder-convention/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Express "parallel folder convention" example

This example shows the use of the [`react-pointcuts`](../../../../../packages/react-pointcuts/docs/README.md), [`features`](../../../../../packages/features/docs/README.md), [`ssr`](../../../../../packages/ssr/docs/README.md) and [`webpack`](../../../../../packages/webpack/docs/README.md) packages, as part of an [express](https://expressjs.com/) application.

An inbound header named "feature" can take the following values:

- baseline (or omitted)
- feature1
- feature2
- feature3
- feature4
- feature5

...which is used server side (via a ["node request scoped features store"](../../../../../packages/features/docs/README.md#noderequestscopedfeaturesstorefactory)) to generate appropriate server-rendered content.

The chosen feature state is serialized to the browser using the `ssr` package, and loaded into a ["global features store"](../../../../../packages/features/docs/README.md#globalfeaturesstorefactory), using [`valtio`](https://github.com/pmndrs/valtio) browser-side, for reactivity.

To demonstrate the reactivity, a drop-down allows changing of the selected feature state.

## Filesystem structure

The base / control folder structure is thus:

```bash
├── components
│ ├── Animal
│ │ └── index.tsx
│ ├── BottomBox
│ │ ├── index.tsx
│ │ └── useAnimals.ts
│ └── TopBox
│ ├── TopBoxChild
│ │ ├── TopBoxButton
│ │ │ ├── index.module.css
│ │ │ ├── index.tsx
│ │ │ └── useAddAnimal.ts
│ │ └── index.tsx
│ └── index.tsx
├── constants
│ └── index.ts
└── state
├── modules
│ ├── animals
│ │ └── slice.ts
│ └── index.ts
└── store.ts
```

## Variations

The features comprise the following:

### _baseline_

The base experience. Clicking the dog dispatches a redux action adding a dog to the bottom box.

### _feature1_

Varied react components (`TopBox` & `BottomBox`), at various depths in the folder structure. A varied constant for the animal emoji (`constants/index.ts`) and varied css (background colour of the button) (`TopBoxButton/index.module.css`).

```bash
├── components
│ ├── BottomBox
│ │ └── index.tsx
│ └── TopBox
│ ├── TopBoxChild
│ │ └── TopBoxButton
│ │ └── index.module.css
│ └── index.tsx
└── constants
└── index.ts
```

### _feature2_

Varied constant (`constants/index.ts`), react component (`TopBoxChild`) and redux slice (`slice.ts`) connecting an additional action creator (`useFreeAnimal.ts`) to "free" added animals (clears the state collection). Has an alternate "initial state" containing two hamsters, activated during server rendering by a `feature` header containing `feature2`.

```bash
├── components
│ └── TopBox
│ └── TopBoxChild
│ ├── index.tsx
│ └── useFreeAnimals.ts
└── constants
└── index.ts
```

### _feature3_

Varied constant (`constants/index.ts`) & redux slice (`slice.ts`) with modified reducer action that multiplies rabbits, when added.

```bash
├── constants
│ └── index.ts
└── state
└── modules
└── animals
└── slice.ts
```

### _feature4_

Varied constant (`constants/index.ts`) & redux slice (`slice.ts`) with replaced redux selector that carcinises previously added animals.

```bash
├── constants
│ └── index.ts
└── state
└── modules
└── animals
└── slice.ts
```

### _feature5_

Varied component (`TopBox`) & redux store (`modules/index.ts`) that introduces a new "space" slice, with it's own state containing spacey stuff.

```bash
├── components
│ └── TopBox
│ ├── index.tsx
│ ├── styles.module.css
│ └── useSpaceStuff.ts
└── state
└── modules
├── space
│ └── slice.ts
└── index.ts
```

## Explanation

The webpack plugin is configured with a toggle handler that maps variants to controls based on a parallel root folder. This allows for any file to be replaced at any depth. The example shows both complete replacements, and augmentations (importing of the base, then modifying).

To vary react components, the toggle point from the `react-pointcuts` package is used.

To vary CSS files, constants, and [`redux` "slices"](https://redux.js.org/tutorials/essentials/part-2-app-structure#redux-slices), a toggle point is used that utilises an [object proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), to intercept property access. Despite objects not being innately reactive, as long as they are accessed from something that does update with change of state (i.e. react components), their properties will also update based on the new feature.

To vary `redux` reducer map, a toggle point that wraps the factory method (`getReducerMap`) is used.

To ensure that the redux store is reactive to feature state, a `StateProvider` react component is used to host the [react-redux provider](https://react-redux.js.org/api/provider), that subscribes to the valtio state, and calls [replaceReducer](https://redux.js.org/usage/code-splitting#using-replacereducer) from the [`@reduxjs/toolkit`](https://github.com/reduxjs/redux-toolkit) whenever the feature state changes. This ensures a new redux store (but retaining current state) is made available to the react components. Care should be taken to ensure the existing state is compatible with any updated selectors etc.

N.B. It is assumed that feature state will not change during a request cycle on the server, so `valtio` is only plumbed in to the client-side feature store, via conditional compilation using [Webpack's `DefinePlugin`](https://webpack.js.org/plugins/define-plugin/).
Loading
Loading