diff --git a/examples/express/docs/CHANGELOG.md b/examples/express/docs/CHANGELOG.md index 89d7780..af7bab2 100644 --- a/examples/express/docs/CHANGELOG.md +++ b/examples/express/docs/CHANGELOG.md @@ -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 diff --git a/examples/express/package.json b/examples/express/package.json index 75ecd27..07a30e8 100644 --- a/examples/express/package.json +++ b/examples/express/package.json @@ -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" diff --git a/examples/express/webpack.config.js b/examples/express/webpack.config.js index 3507cb4..73d72f4 100644 --- a/examples/express/webpack.config.js +++ b/examples/express/webpack.config.js @@ -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" }; @@ -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, diff --git a/examples/next/docs/CHANGELOG.md b/examples/next/docs/CHANGELOG.md index 735a7ff..91c3ed8 100644 --- a/examples/next/docs/CHANGELOG.md +++ b/examples/next/docs/CHANGELOG.md @@ -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 diff --git a/examples/next/next.config.mjs b/examples/next/next.config.mjs index 397e9b9..428a031 100644 --- a/examples/next/next.config.mjs +++ b/examples/next/next.config.mjs @@ -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 () => diff --git a/examples/next/package.json b/examples/next/package.json index 60a4744..e1d85f0 100644 --- a/examples/next/package.json +++ b/examples/next/package.json @@ -1,6 +1,6 @@ { "name": "web-toggle-point-next-example", - "version": "0.2.3", + "version": "0.2.4", "private": true, "type": "module", "scripts": { diff --git a/examples/next/src/app/fixtures/experiments/4-varied-variant/README.mdx b/examples/next/src/app/fixtures/experiments/4-varied-variant/README.mdx index 1cfddca..1f71f95 100644 --- a/examples/next/src/app/fixtures/experiments/4-varied-variant/README.mdx +++ b/examples/next/src/app/fixtures/experiments/4-varied-variant/README.mdx @@ -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" }` diff --git a/examples/serve/README.md b/examples/serve/README.md index 5b29fbd..2d17c37 100644 --- a/examples/serve/README.md +++ b/examples/serve/README.md @@ -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: diff --git a/examples/serve/docs/CHANGELOG.md b/examples/serve/docs/CHANGELOG.md index 9e05397..b3f8ea1 100644 --- a/examples/serve/docs/CHANGELOG.md +++ b/examples/serve/docs/CHANGELOG.md @@ -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 diff --git a/examples/serve/package.json b/examples/serve/package.json index ce27389..0ea90d2 100644 --- a/examples/serve/package.json +++ b/examples/serve/package.json @@ -1,6 +1,6 @@ { "name": "web-toggle-point-serve-example", - "version": "0.2.5", + "version": "0.2.6", "type": "module", "private": true, "scripts": { diff --git a/examples/serve/src/fixtures/audience/__pointCutConfig.js b/examples/serve/src/fixtures/audience/__pointCutConfig.js index 49df1c5..b0d5d34 100644 --- a/examples/serve/src/fixtures/audience/__pointCutConfig.js +++ b/examples/serve/src/fixtures/audience/__pointCutConfig.js @@ -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")) diff --git a/examples/serve/src/fixtures/config/__pointCutConfig.js b/examples/serve/src/fixtures/config/__pointCutConfig.js index 092ce28..ca7293a 100644 --- a/examples/serve/src/fixtures/config/__pointCutConfig.js +++ b/examples/serve/src/fixtures/config/__pointCutConfig.js @@ -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 }; diff --git a/examples/serve/src/fixtures/event/__pointCutConfig.js b/examples/serve/src/fixtures/event/__pointCutConfig.js index 857abfc..b5c3ca6 100644 --- a/examples/serve/src/fixtures/event/__pointCutConfig.js +++ b/examples/serve/src/fixtures/event/__pointCutConfig.js @@ -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") }; diff --git a/examples/serve/src/fixtures/translation/__pointCutConfig.js b/examples/serve/src/fixtures/translation/__pointCutConfig.js index 3b0e8ee..99cee63 100644 --- a/examples/serve/src/fixtures/translation/__pointCutConfig.js +++ b/examples/serve/src/fixtures/translation/__pointCutConfig.js @@ -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 }; diff --git a/package-lock.json b/package-lock.json index 70da226..df4b789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ }, "examples/express": { "name": "web-toggle-point-express-example", - "version": "0.2.4", + "version": "0.2.5", "dependencies": { "@asos/web-toggle-point-features": "file:../../packages/features", "@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts", @@ -89,7 +89,7 @@ }, "examples/next": { "name": "web-toggle-point-next-example", - "version": "0.2.3", + "version": "0.2.4", "dependencies": { "@asos/web-toggle-point-features": "file:../../packages/features", "@asos/web-toggle-point-react-pointcuts": "file:../../packages/react-pointcuts", @@ -116,7 +116,7 @@ }, "examples/serve": { "name": "web-toggle-point-serve-example", - "version": "0.2.5", + "version": "0.2.6", "dependencies": { "@asos/web-toggle-point-features": "file:../../packages/features", "@asos/web-toggle-point-webpack": "file:../../packages/webpack", @@ -19936,7 +19936,7 @@ }, "packages/webpack": { "name": "@asos/web-toggle-point-webpack", - "version": "0.7.5", + "version": "0.8.0", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", diff --git a/packages/webpack/docs/CHANGELOG.md b/packages/webpack/docs/CHANGELOG.md index 7a133ee..8673a3c 100644 --- a/packages/webpack/docs/CHANGELOG.md +++ b/packages/webpack/docs/CHANGELOG.md @@ -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 @@ -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 diff --git a/packages/webpack/docs/README.md b/packages/webpack/docs/README.md index c5bbd98..a262523 100644 --- a/packages/webpack/docs/README.md +++ b/packages/webpack/docs/README.md @@ -37,7 +37,7 @@ import { NormalModule } from 'webpack'; interface PointCut { name: string; togglePointModule: string; - variantGlob?: string; + variantGlobs?: string[]; joinPointResolver?: (variantPath: string) => string; } @@ -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). @@ -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. @@ -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"] }] }); ``` diff --git a/packages/webpack/package.json b/packages/webpack/package.json index a2fce31..093b158 100644 --- a/packages/webpack/package.json +++ b/packages/webpack/package.json @@ -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", diff --git a/packages/webpack/src/plugins/togglePointInjection/index.js b/packages/webpack/src/plugins/togglePointInjection/index.js index 2fa3310..12d32da 100644 --- a/packages/webpack/src/plugins/togglePointInjection/index.js +++ b/packages/webpack/src/plugins/togglePointInjection/index.js @@ -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 @@ -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}"] * } * ] * }); diff --git a/packages/webpack/src/plugins/togglePointInjection/integration.test.js b/packages/webpack/src/plugins/togglePointInjection/integration.test.js index bfa008c..d28aa06 100644 --- a/packages/webpack/src/plugins/togglePointInjection/integration.test.js +++ b/packages/webpack/src/plugins/togglePointInjection/integration.test.js @@ -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" } ]; @@ -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 }); diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.js index e50b71e..fecdcd5 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.js @@ -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; diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.test.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.test.js index cf5d997..a199b19 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.test.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/fillDefaultOptionalValues.test.js @@ -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(() => { @@ -23,7 +23,7 @@ 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) }); }); @@ -31,8 +31,8 @@ describe("fillDefaultOptionalValues", () => { 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); @@ -40,7 +40,7 @@ describe("fillDefaultOptionalValues", () => { it("should fill the defaults", () => { expect(result).toEqual({ - variantGlob: pointCut.variantGlob, + variantGlobs: pointCut.variantGlobs, joinPointResolver: expect.any(Function) }); }); @@ -48,23 +48,23 @@ describe("fillDefaultOptionalValues", () => { 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); diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantFiles.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantFiles.js deleted file mode 100644 index c458c6a..0000000 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantFiles.js +++ /dev/null @@ -1,17 +0,0 @@ -import { posix } from "path"; -import fastGlob from "fast-glob"; -const { relative, join } = posix; - -const getVariantFiles = async ({ variantGlob, appRoot, fileSystem }) => { - const results = await fastGlob.glob(join(appRoot, variantGlob), { - objectMode: true, - fs: fileSystem - }); - - return results.map(({ path, name }) => ({ - path: `/${relative(appRoot, path)}`, - name - })); -}; - -export default getVariantFiles; diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantPaths.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantPaths.js new file mode 100644 index 0000000..4bf6894 --- /dev/null +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantPaths.js @@ -0,0 +1,19 @@ +import { posix } from "path"; +import fastGlob from "fast-glob"; +const { relative, join } = posix; + +const getVariantPaths = async ({ variantGlobs, appRoot, fileSystem }) => { + const variantPaths = new Set(); + for await (const glob of variantGlobs) { + const results = await fastGlob.glob(join(appRoot, glob), { + objectMode: true, + fs: fileSystem + }); + for (const { path } of results) { + variantPaths.add(`/${relative(appRoot, path)}`); + } + } + return variantPaths; +}; + +export default getVariantPaths; diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantFiles.test.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantPaths.test.js similarity index 60% rename from packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantFiles.test.js rename to packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantPaths.test.js index 8a63b86..d6bbf9a 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantFiles.test.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/getVariantPaths.test.js @@ -1,8 +1,8 @@ import { fs as fileSystem, vol } from "memfs"; import { join, sep } from "path"; -import getVariantFiles from "./getVariantFiles"; +import getVariantPaths from "./getVariantPaths"; -describe("getVariantFiles", () => { +describe("getVariantPaths", () => { const appRoot = "/test-folder"; let result; beforeEach(async () => { @@ -42,23 +42,20 @@ describe("getVariantFiles", () => { sep ); - result = await getVariantFiles({ - variantGlob: "./**/test-matching-folder/**/test-matching-*.js", + result = await getVariantPaths({ + variantGlobs: [ + "./**/test-matching-folder/**/test-matching-*.js", + "./**/test-matching-folder/test-matching-*.js" + ], appRoot, fileSystem }); }); - it("should return the matching files as an object with the file name and the path relative to the application root", () => { - expect(result).toEqual([ - expect.objectContaining({ - name: "test-matching-file-1.js", - path: "/test-matching-folder/test-matching-file-1.js" - }), - expect.objectContaining({ - name: "test-matching-file-2.js", - path: "/test-matching-folder/test-matching-file-2.js" - }) + it("should return the distinct matching file paths relative to the application root", () => { + expect(Array.from(result)).toEqual([ + "/test-matching-folder/test-matching-file-1.js", + "/test-matching-folder/test-matching-file-2.js" ]); }); }); diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.js index 01162c9..503b057 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.js @@ -1,5 +1,5 @@ import processVariantFiles from "./processVariantFiles/index.js"; -import getVariantFiles from "./getVariantFiles.js"; +import getVariantPaths from "./getVariantPaths.js"; import fillDefaultOptionalValues from "./fillDefaultOptionalValues.js"; const processPointCuts = async ({ @@ -11,21 +11,20 @@ const processPointCuts = async ({ const configFiles = new Map(); const warnings = []; for await (const pointCut of pointCuts.values()) { - const { variantGlob, joinPointResolver } = + const { variantGlobs, joinPointResolver } = fillDefaultOptionalValues(pointCut); - const variantFiles = await getVariantFiles({ - variantGlob, + const variantPaths = await getVariantPaths({ + variantGlobs, appRoot, fileSystem }); await processVariantFiles({ - variantFiles, + variantPaths, joinPointFiles, pointCut, joinPointResolver, - variantGlob, warnings, configFiles, fileSystem, diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.test.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.test.js index 39ff6bf..62768a2 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.test.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/index.test.js @@ -1,15 +1,15 @@ import processVariantFiles from "./processVariantFiles/index.js"; -import getVariantFiles from "./getVariantFiles.js"; +import getVariantPaths from "./getVariantPaths.js"; import processPointCuts from "./index.js"; import fillDefaultOptionalValues from "./fillDefaultOptionalValues.js"; jest.mock("./processVariantFiles/index", () => jest.fn()); -jest.mock("./getVariantFiles", () => +jest.mock("./getVariantPaths", () => jest.fn(() => Symbol("test-variant-files")) ); jest.mock("./fillDefaultOptionalValues", () => jest.fn(() => ({ - variantGlob: Symbol("test-variant-glob"), + variantGlobs: Symbol("test-variant-globs"), joinPointResolver: Symbol("test-join-point-resolver") })) ); @@ -42,10 +42,10 @@ describe("processPointCuts", () => { it("should get variant files for each of the point cuts", () => { for (const index of pointCutsValues.keys()) { - const { variantGlob } = + const { variantGlobs } = fillDefaultOptionalValues.mock.results[index].value; - expect(getVariantFiles).toHaveBeenCalledWith({ - variantGlob, + expect(getVariantPaths).toHaveBeenCalledWith({ + variantGlobs, appRoot, fileSystem }); @@ -54,15 +54,14 @@ describe("processPointCuts", () => { it("should process the variant files, and keep a shared record of config files found between each point cut", () => { for (const [index, pointCut] of pointCutsValues.entries()) { - const variantFiles = getVariantFiles.mock.results[index].value; - const { variantGlob, joinPointResolver } = + const variantPaths = getVariantPaths.mock.results[index].value; + const { joinPointResolver } = fillDefaultOptionalValues.mock.results[index].value; expect(processVariantFiles).toHaveBeenCalledWith({ - variantFiles, + variantPaths, joinPointFiles, pointCut, joinPointResolver, - variantGlob, warnings, configFiles: expect.any(Map), fileSystem, diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.js index 63629cc..8d8e09a 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.js @@ -1,24 +1,24 @@ import { posix } from "path"; import isJoinPointInvalid from "./isJoinPointInvalid"; -const { dirname, relative } = posix; +const { parse, relative } = posix; const processVariantFiles = async ({ - variantFiles, + variantPaths, joinPointFiles, pointCut, joinPointResolver, warnings, ...rest }) => { - for (const { name, path } of variantFiles) { - const joinPointPath = joinPointResolver(path); - const joinDirectory = dirname(joinPointPath); + for (const variantPath of variantPaths) { + const joinPointPath = joinPointResolver(variantPath); + const { dir: directory, base: filename } = parse(joinPointPath); if (!joinPointFiles.has(joinPointPath)) { const isInvalid = await isJoinPointInvalid({ - name, + filename, joinPointPath, - joinDirectory, + directory, ...rest }); @@ -40,7 +40,7 @@ const processVariantFiles = async ({ } joinPointFile.variants.push( - relative(joinDirectory, path).replace(/^([^./])/, "./$1") + relative(directory, variantPath).replace(/^([^./])/, "./$1") ); } }; diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.test.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.test.js index bf8adc7..1ff96c5 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.test.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/index.test.js @@ -1,7 +1,7 @@ import processVariantFiles from "."; import { memfs } from "memfs"; import { posix } from "path"; -const { resolve, basename, join, sep } = posix; +const { resolve, join, sep } = posix; describe("processVariantFiles", () => { let joinPointFiles; @@ -10,7 +10,7 @@ describe("processVariantFiles", () => { let warnings; const variantFileGlob = "test-variant-*.*"; - const variantGlob = `/${variantFileGlob}`; + const variantGlobs = [`/${variantFileGlob}`]; const appRoot = "/test-app-root/"; const moduleFile = "test-module.js"; const joinPointFolder = "test-folder"; @@ -24,14 +24,14 @@ describe("processVariantFiles", () => { joinPointFiles = new Map(); }); - const act = async ({ variantFiles, configFiles }) => { + const act = async ({ variantPaths, configFiles }) => { await processVariantFiles({ - variantFiles, + variantPaths, configFiles, joinPointFiles, pointCut, joinPointResolver, - variantGlob, + variantGlobs, warnings, name: moduleFile, fileSystem, @@ -41,7 +41,7 @@ describe("processVariantFiles", () => { describe("when given no variant files", () => { beforeEach(async () => { - await act({ variantFiles: [], configFiles: new Map() }); + await act({ variantPaths: new Set(), configFiles: new Map() }); }); it("should add no warnings, and not modify joinPointFiles", async () => { @@ -50,30 +50,25 @@ describe("processVariantFiles", () => { }); }); - const variantFilePath = variantFileGlob.replaceAll("*", "1"); + const variantPath = variantFileGlob.replaceAll("*", "1"); describe.each` - variantFilePath | expectedVariant - ${variantFilePath} | ${"." + sep + variantFilePath} - ${"." + variantFilePath} | ${"." + variantFilePath} - ${"." + sep + variantFilePath} | ${"." + sep + variantFilePath} - ${".." + sep + variantFilePath} | ${".." + sep + variantFilePath} + variantPath | expectedVariant + ${variantPath} | ${"." + sep + variantPath} + ${"." + variantPath} | ${"." + variantPath} + ${"." + sep + variantPath} | ${"." + sep + variantPath} + ${".." + sep + variantPath} | ${".." + sep + variantPath} `( - "when given a variant path ($variantFilePath)", - ({ variantFilePath, expectedVariant }) => { - const variantFiles = [ - { - name: basename(variantFilePath), - path: resolve(joinPointFolder, variantFilePath) - } - ]; + "when given a variant path ($variantPath)", + ({ variantPath, expectedVariant }) => { + const variantPaths = new Set([resolve(joinPointFolder, variantPath)]); describe("when given a variant file that has no matching join point file", () => { beforeEach(async () => { joinPointResolver.mockReturnValue( join(joinPointFolder, "test-not-matching-control") ); - await act({ variantFiles, configFiles: new Map() }); + await act({ variantPaths, configFiles: new Map() }); }); it("should add no warnings, and not modify joinPointFiles", async () => { @@ -89,7 +84,7 @@ describe("processVariantFiles", () => { describe("and no config file precludes it being valid", () => { beforeEach(async () => { - await act({ variantFiles, configFiles: new Map() }); + await act({ variantPaths, configFiles: new Map() }); }); it("should add no warnings, and add a single joinPointFile representing the matched join point", async () => { @@ -111,7 +106,7 @@ describe("processVariantFiles", () => { describe("and a config file confirms it as valid", () => { beforeEach(async () => { await act({ - variantFiles, + variantPaths, configFiles: new Map([ [joinPointFolder, { joinPoints: [moduleFile] }] ]) @@ -137,7 +132,7 @@ describe("processVariantFiles", () => { describe("and a config file precludes it from being valid", () => { beforeEach(async () => { await act({ - variantFiles, + variantPaths, configFiles: new Map([[joinPointFolder, { joinPoints: [] }]]) }); }); @@ -155,7 +150,7 @@ describe("processVariantFiles", () => { pointCut: testOtherPointCut }); await act({ - variantFiles, + variantPaths, configFiles: new Map() }); }); diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.js index 90c2b75..c88885a 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.js @@ -17,32 +17,32 @@ const fileExists = async (fileSystem, path) => { const ensureConfigFile = async ({ configFiles, fileSystem, - joinDirectory, + directory, appRoot }) => { - if (!configFiles.has(joinDirectory)) { + if (!configFiles.has(directory)) { let configFile = null; - const path = `${join(appRoot, joinDirectory, TOGGLE_CONFIG)}`; + const path = `${join(appRoot, directory, TOGGLE_CONFIG)}`; if (await fileExists(fileSystem, path)) { configFile = JSON.parse(await readFile({ fileSystem, path })); validateConfigSchema({ configFile, appRoot, path }); } - configFiles.set(joinDirectory, configFile); + configFiles.set(directory, configFile); } }; const isJoinPointInvalid = async ({ configFiles, - name, + filename, fileSystem, appRoot, joinPointPath, - joinDirectory + directory }) => { - await ensureConfigFile({ configFiles, fileSystem, joinDirectory, appRoot }); + await ensureConfigFile({ configFiles, fileSystem, directory, appRoot }); - if (configFiles.has(joinDirectory)) { - if (configFiles.get(joinDirectory)?.joinPoints.includes(name) === false) { + if (configFiles.has(directory)) { + if (configFiles.get(directory)?.joinPoints.includes(filename) === false) { return true; } } diff --git a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.test.js b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.test.js index 1cc0804..2b431a2 100644 --- a/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.test.js +++ b/packages/webpack/src/plugins/togglePointInjection/processPointCuts/processVariantFiles/isJoinPointInvalid.test.js @@ -10,10 +10,10 @@ jest.mock("../../constants", () => ({ jest.mock("./validateConfigSchema", () => jest.fn()); const appRoot = "test-app-root"; -const joinDirectory = "test-join-directory"; +const directory = "test-join-directory"; const mockJoinPoint = "test-join-point"; -const joinPointPath = join(joinDirectory, mockJoinPoint); -const toggleConfigPath = join(appRoot, joinDirectory, TOGGLE_CONFIG); +const joinPointPath = join(directory, mockJoinPoint); +const toggleConfigPath = join(appRoot, directory, TOGGLE_CONFIG); const joinPointFullPath = join(appRoot, joinPointPath); describe("isJoinPointInvalid", () => { @@ -22,7 +22,7 @@ describe("isJoinPointInvalid", () => { beforeEach(() => { configFiles = new Map(); ({ fs: fileSystem, vol } = memfs()); - vol.fromJSON({ [join(appRoot, joinDirectory)]: {} }); + vol.fromJSON({ [join(appRoot, directory)]: {} }); jest.spyOn(fileSystem, "stat"); jest.spyOn(fileSystem, "readFile"); }); @@ -30,11 +30,11 @@ describe("isJoinPointInvalid", () => { const act = () => isJoinPointInvalid({ configFiles, - name: mockJoinPoint, + filename: mockJoinPoint, fileSystem, appRoot, joinPointPath, - joinDirectory + directory }); const makeSecondCallAssertions = (expectedResult) => { diff --git a/packages/webpack/src/plugins/togglePointInjection/schema.json b/packages/webpack/src/plugins/togglePointInjection/schema.json index 21094df..b75b215 100644 --- a/packages/webpack/src/plugins/togglePointInjection/schema.json +++ b/packages/webpack/src/plugins/togglePointInjection/schema.json @@ -6,9 +6,18 @@ "items": { "type": "object", "properties": { - "name": { "type": "string" }, - "togglePointModule": { "type": "string" }, - "variantGlob": { "type": "string" }, + "name": { + "type": "string" + }, + "togglePointModule": { + "type": "string" + }, + "variantGlobs": { + "type": "array", + "items": { + "type": "string" + } + }, "toggleHandler": { "type": "string" }, @@ -17,7 +26,10 @@ } }, "additionalProperties": false, - "required": ["name", "togglePointModule"] + "required": [ + "name", + "togglePointModule" + ] } }, "webpackNormalModule": { @@ -25,5 +37,7 @@ } }, "additionalProperties": false, - "required": ["pointCuts"] + "required": [ + "pointCuts" + ] }