Skip to content
Merged
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
7 changes: 7 additions & 0 deletions examples/express/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ 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.2.5] - 2025-05-27

### Changed

- 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)
- used some differing syntax from [`micromatch`](https://github.com/micromatch/micromatch) to define `variantGlobs`, for coverage and where may be preferred

## [0.2.4] - 2024-02-07

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion 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.2.4",
"version": "0.2.5",
"type": "module",
"engines": {
"node": ">=20.6.0"
Expand Down
6 changes: 4 additions & 2 deletions examples/express/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fileURLToPath } from "url";

const configPointCutConfig = {
name: "configuration variants",
variantGlob: "./src/routes/config/__variants__/*/*/*.jsx",
variantGlobs: ["./src/routes/config/__variants__/*/*/*.jsx"],
togglePointModule: "/src/routes/config/togglePoint.js"
};

Expand Down Expand Up @@ -48,7 +48,9 @@ const config = [
configPointCutConfig,
{
name: "animal apis by version",
variantGlob: "./src/routes/animals/api/**/v[1-9]*([0-9])/*.js",
variantGlobs: [
"./src/routes/animals/api/**/v{1..9}*([[:digit:]])/*.js"
],
joinPointResolver: (variantPath) =>
posix.resolve(
variantPath,
Expand Down
6 changes: 6 additions & 0 deletions examples/next/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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.2.4] - 2025-05-27

### Changed

- 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)

## [0.2.3] - 2025-02-07

### Fixed
Expand Down
3 changes: 2 additions & 1 deletion examples/next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ const togglePointInjection = new TogglePointInjection({
{
name: "experiments",
togglePointModule: "/src/app/fixtures/experiments/withTogglePoint",
variantGlob:
variantGlobs: [
"./src/app/fixtures/experiments/**/__variants__/*/*/!(*.spec).tsx"
]
}
],
webpackNormalModule: async () =>
Expand Down
2 changes: 1 addition & 1 deletion examples/next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web-toggle-point-next-example",
"version": "0.2.3",
"version": "0.2.4",
"private": true,
"type": "module",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This example shows compound variation, in that a component varied by "feature 1"
can be varied again by a "feature 2". In this instance, the same point cut
(for experimentation) is acting on both, since it's `variantGlob` is defined
(for experimentation) is acting on both, since it has a `variantGlob` defined
with a recursive [`globstar`](https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html#:~:text=globstar,only%20directories%20and%20subdirectories%20match.).

If an `experiments` header is set, with a `"test-feature": { "bucket": "test-variant" }`
Expand Down
2 changes: 1 addition & 1 deletion examples/serve/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This example shows the use of [`webpack`](../../packages/webpack/docs/README.md)

It uses a `globalFeaturesStoreFactory` from the `features` package, to hold a invariant global toggle state.

It demonstrates a setup that utilises the `toggleHandler`, `variantGlob`, and `controlResolver` options of the Webpack plugin, with some basic convention-based filesystem approaches to toggling:
It demonstrates a setup that utilises the `toggleHandler`, `variantGlobs`, and `controlResolver` options of the Webpack plugin, with some basic convention-based filesystem approaches to toggling:

1. selecting a translations JSON file based on [`navigator.language`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/language).
- This uses language files stored at:
Expand Down
6 changes: 6 additions & 0 deletions examples/serve/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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.2.6] - 2025-07-14

### Changed

- 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)

## [0.2.5] - 2025-07-14

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion examples/serve/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "web-toggle-point-serve-example",
"version": "0.2.5",
"version": "0.2.6",
"type": "module",
"private": true,
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion examples/serve/src/fixtures/audience/__pointCutConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import getToggleHandlerPath from "../../getToggleHandlerPath.js";
export default {
name: "audience",
togglePointModule: "/src/fixtures/audience/__togglePoint.js",
variantGlob: "./src/fixtures/audience/**/cohort-[1-9]*([0-9])/*.js",
variantGlobs: ["./src/fixtures/audience/**/cohort-[1-9]*([0-9])/*.js"],
toggleHandler: getToggleHandlerPath("singlePathSegment.js"),
joinPointResolver: (path) =>
posix.resolve(path, "../..", basename(path).replace("bespoke", "control"))
Expand Down
2 changes: 1 addition & 1 deletion examples/serve/src/fixtures/config/__pointCutConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import joinPointResolver from "../../joinPointResolver.js";
export default {
name: "configuration",
togglePointModule: "/src/fixtures/config/__togglePoint.js",
variantGlob: "./src/fixtures/config/**/sites/*/*.js",
variantGlobs: ["./src/fixtures/config/**/sites/*/*.js"],
toggleHandler: getToggleHandlerPath("listExtractionFromPathSegment.js"),
joinPointResolver
};
2 changes: 1 addition & 1 deletion examples/serve/src/fixtures/event/__pointCutConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import getToggleHandlerPath from "../../getToggleHandlerPath.js";
export default {
name: "event",
togglePointModule: "/src/fixtures/event/__togglePoint.js",
variantGlob: "./src/fixtures/event/**/*.*.css",
variantGlobs: ["./src/fixtures/event/**/*.*.css"],
toggleHandler: getToggleHandlerPath("singleFilenameDottedSegment.js"),
joinPointResolver: (path) => path.replace(/\.([^.]+)\.css$/, ".css")
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import joinPointResolver from "../../joinPointResolver.js";
export default {
name: "translation",
togglePointModule: "/src/fixtures/translation/__togglePoint.js",
variantGlob: "./src/fixtures/translation/languages/*/*.json",
variantGlobs: ["./src/fixtures/translation/languages/*/*.json"],
joinPointResolver
};
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion packages/webpack/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ 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.8.0] - 2025-05-27

### Changed

- moved to accept an array for variant globs in the plugin, allowing cherry-picking of disparately-located files to be selected where a common pattern does not apply

## [0.7.5] - 2025-03-06

### Changed
Expand All @@ -21,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Added `package.json` [repository](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#repository), [bugs](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#bugs), and [`directories/doc`](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#directories) fields, for clarity once on NPM
- added `package.json` [repository](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#repository), [bugs](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#bugs), and [`directories/doc`](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#directories) fields, for clarity once on NPM

## [0.7.3] - 2024-12-26

Expand Down
18 changes: 10 additions & 8 deletions packages/webpack/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { NormalModule } from 'webpack';
interface PointCut {
name: string;
togglePointModule: string;
variantGlob?: string;
variantGlobs?: string[];
joinPointResolver?: (variantPath: string) => string;
}

Expand Down Expand Up @@ -70,11 +70,13 @@ It's paramount that this module is compatible with the modules it is varying. e

Also, the interface of the toggle point, and the variations that it may supplant, needs to be interchangeable with the base/default module. [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle) must apply; functionality may differ, but it must still be compatible with all the consumers of the base module.

#### _`variantGlob`_
#### _`variantGlobs`_

A [glob](https://en.wikipedia.org/wiki/Glob_(programming)) which points at varied code modules. The default/base modules are extrapolated from this source location.[^2]
An array of [globs](https://en.wikipedia.org/wiki/Glob_(programming)) which point at varied code modules. The default/base modules are extrapolated from these source locations.[^2]

This can be as specific or generic as needed, but ideally the most specific possible for the use-cases in effect.
These can be as specific or generic as needed, but ideally the most specific possible for the use-cases in effect.

The common case is a single glob, but an array is provided to mitigate the need for complex ["brace expansion"](https://github.com/micromatch/braces) or [extended globs](https://github.com/micromatch/micromatch#extglobs), which could be complex when targeting variations which are located in disparate parts of a codebase.

It should match modules that are compatible with the `togglePointModule` - e.g. if all React code is held within a `/components` folder, it makes sense to include this in the glob path to avoid inadvertently toggling non-react code (should a variant be set up for non-React code without considering the configuration).

Expand All @@ -97,13 +99,13 @@ If not supplied, a default `glob` of `/**/__variants__/*/*/!(*.test).{js,jsx,ts,

#### _`toggleHandler`_

This module unpicks the [WebPack context module](https://webpack.js.org/guides/dependency-management/#context-module-api) produced by enacting the configured `variantGlob` and converts it into a form suitable for the configured `togglePoint`.
This module unpicks the [WebPack context module](https://webpack.js.org/guides/dependency-management/#context-module-api) produced by enacting the configured `variantGlobs` and converts it into a form suitable for the configured `togglePoint`.

If not supplied, a default handler (`@asos/web-toggle-point-webpack/pathSegmentToggleHandler`) is used, compatible with the default `variantGlob`, that converts the matched paths into a tree data structure held in a `Map`, with each path segment as a node in the tree, and the variant modules as the leaf nodes.
If not supplied, a default handler (`@asos/web-toggle-point-webpack/pathSegmentToggleHandler`) is used, compatible with the default `variantGlobs`, that converts the matched paths into a tree data structure held in a `Map`, with each path segment as a node in the tree, and the variant modules as the leaf nodes.

#### _`joinPointResolver`_

This marries with the `variantGlob`, in that it "undoes" the variation in file path made to the base/default module.
This marries with the `variantGlobs`, in that it "undoes" the variation in file path made to the base/default module.

For every variant path found, the plugin executes this method to locate the related base/default path.

Expand Down Expand Up @@ -163,7 +165,7 @@ const plugin = new TogglePointInjection({
pointCuts: [{
name: "my point cut",
togglePointModule: "/src/modules/withTogglePoint",
variantGlob: "./src/modules/**/__variants__/*/*/*.js"
variantGlobs: ["./src/modules/**/__variants__/*/*/*.js"]
}]
});
```
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@asos/web-toggle-point-webpack",
"description": "toggle point webpack plugin",
"version": "0.7.5",
"version": "0.8.0",
"license": "MIT",
"type": "module",
"main": "./lib/main.cjs",
Expand Down
6 changes: 3 additions & 3 deletions packages/webpack/src/plugins/togglePointInjection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TogglePointInjection {
* @param {object[]} options.pointCuts toggle point point cut configuration, with target toggle point code as advice. The first matching point cut will be used
* @param {string} options.pointCuts[].name name to describe the nature of the point cut, for clarity in logs and dev tools etc.
* @param {string} options.pointCuts[].togglePointModule path, from root of the compilation, of where the toggle point sits. Or a resolvable node_module.
* @param {string} [options.pointCuts[].variantGlob='.\/**\/__variants__/*\/*\/!(*.test).{js,jsx,ts,tsx}'] {@link https://en.wikipedia.org/wiki/Glob_(programming)|Glob} to identified variant modules. The plugin uses {@link https://github.com/mrmlnc/fast-glob|fast-glob} under the hood, so supports any glob that it does.
* @param {string[]} [options.pointCuts[].variantGlobs=[.\/**\/__variants__/*\/*\/!(*.test).{js,jsx,ts,tsx}]] {@link https://en.wikipedia.org/wiki/Glob_(programming)|Globs} to identified variant modules. The plugin uses {@link https://github.com/mrmlnc/fast-glob|fast-glob} under the hood, so supports any glob that it does.
* @param {function} [options.pointCuts[].joinPointResolver=(variantPath) => path.posix.resolve(variantPath, "../../../..", path.basename(variantPath))] A function that takes the path to a variant module and returns a join point / base module. N.B. This is executed at build-time, so cannot use run-time context. It should use posix path segments, so on Windows be sure to use path.posix.resolve.
* @param {string} [options.pointCuts[].toggleHandler] Path to a toggle handler that unpicks a {@link https://webpack.js.org/api/module-methods/#requirecontext|require.context} containing potential variants, passing that plus a joint point module to a toggle point function. If not provided, the plugin will use a default handler that processes folder names into a tree held in a Map. Leaf nodes of the tree are the variant modules.
* @param {function} [options.webpackNormalModule] A function that returns the Webpack NormalModule class. This is required for Next.js, as it does not expose the NormalModule class directly
Expand All @@ -29,11 +29,11 @@ class TogglePointInjection {
* pointCuts: [
* {
* togglePointModule: "/withToggledHook",
* variantGlob: "./**\/__variants__/*\/*\/use!(*.test).{ts,tsx}"
* variantGlobs: ["./**\/__variants__/*\/*\/use!(*.test).{ts,tsx}"]
* },
* {
* togglePointModule: "/withTogglePoint",
* variantGlob: "./**\/__variants__/*\/*\/!(use*|*.test).{ts,tsx}"
* variantGlobs: ["./**\/__variants__/*\/*\/!(use*|*.test).{ts,tsx}"]
* }
* ]
* });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ describe("togglePointInjection", () => {
{
name: "not react hooks",
togglePointModule: togglePointModule1,
variantGlob: `${modulesFolder}**/${variantsFolder}/*/*/!(*use)*.js`,
variantGlobs: [`${modulesFolder}**/${variantsFolder}/*/*/!(*use)*.js`],
moduleName: "testModule.js"
},
{
name: "react hooks",
togglePointModule: togglePointModule2,
variantGlob: `${modulesFolder}**/${variantsFolder}/*/*/use*.js`,
variantGlobs: [`${modulesFolder}**/${variantsFolder}/*/*/use*.js`],
moduleName: "useTestModule.js"
}
];
Expand Down Expand Up @@ -158,7 +158,9 @@ describe("togglePointInjection", () => {
const [{ moduleName }] = testCases;

beforeEach(async () => {
testCases[1].variantGlob = `${modulesFolder}**/${variantsFolder}/*/*/*.js`;
testCases[1].variantGlobs = [
`${modulesFolder}**/${variantsFolder}/*/*/*.js`
];
plugin = new TogglePointInjection({
pointCuts: testCases.map(({ moduleName, ...rest }) => rest) // eslint-disable-line no-unused-vars
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { posix, basename } from "path";

const fillPointCutDefaults = (pointCut) => {
const {
variantGlob = "./**/__variants__/*/*/!(*.test).{js,jsx,ts,tsx}",
variantGlobs = ["./**/__variants__/*/*/!(*.test).{js,jsx,ts,tsx}"],
joinPointResolver = (variantPath) =>
posix.resolve(variantPath, ...Array(4).fill(".."), basename(variantPath))
} = pointCut;

return { variantGlob, joinPointResolver };
return { variantGlobs, joinPointResolver };
};

export default fillPointCutDefaults;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe("fillDefaultOptionalValues", () => {
});
};

describe("when the point cut has no variantGlob or joinPointResolver", () => {
describe("when the point cut has no variantGlobs or joinPointResolver", () => {
const pointCut = {};

beforeEach(() => {
Expand All @@ -23,48 +23,48 @@ describe("fillDefaultOptionalValues", () => {

it("should fill the defaults", () => {
expect(result).toEqual({
variantGlob: "./**/__variants__/*/*/!(*.test).{js,jsx,ts,tsx}",
variantGlobs: ["./**/__variants__/*/*/!(*.test).{js,jsx,ts,tsx}"],
joinPointResolver: expect.any(Function)
});
});

makeDefaultJoinPointResolverAssertions();
});

describe("when the point cut has a variantGlob but no joinPointResolver", () => {
const pointCut = { variantGlob: Symbol("test-variant-glob") };
describe("when the point cut has a variantGlobs but no joinPointResolver", () => {
const pointCut = { variantGlobs: Symbol("test-variant-globs") };

beforeEach(() => {
result = fillPointCutDefaults(pointCut);
});

it("should fill the defaults", () => {
expect(result).toEqual({
variantGlob: pointCut.variantGlob,
variantGlobs: pointCut.variantGlobs,
joinPointResolver: expect.any(Function)
});
});

makeDefaultJoinPointResolverAssertions();
});

describe("when the point cut has a joinPointResolver but no variantGlob", () => {
it("should return the supplied joinPointResolver and fill a default variantGlob", () => {
describe("when the point cut has a joinPointResolver but no variantGlobs", () => {
it("should return the supplied joinPointResolver and fill default variantGlobs", () => {
const pointCut = {
joinPointResolver: Symbol("test-join-point-resolver")
};
const result = fillPointCutDefaults(pointCut);
expect(result).toEqual({
variantGlob: "./**/__variants__/*/*/!(*.test).{js,jsx,ts,tsx}",
variantGlobs: ["./**/__variants__/*/*/!(*.test).{js,jsx,ts,tsx}"],
joinPointResolver: pointCut.joinPointResolver
});
});
});

describe("when the point cut has a variantGlob and a joinPointResolver", () => {
describe("when the point cut has variantGlobs and a joinPointResolver", () => {
it("should return the point cut supplied values", () => {
const pointCut = {
variantGlob: Symbol("test-variant-glob"),
variantGlobs: Symbol("test-variant-glob"),
joinPointResolver: Symbol("test-join-point-resolver")
};
const result = fillPointCutDefaults(pointCut);
Expand Down
Loading
Loading