@dinesh-gamage/react-scoped-css 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +373 -0
  2. package/dist/adapters/next.d.mts +45 -0
  3. package/dist/adapters/next.d.ts +45 -0
  4. package/dist/adapters/next.js +14465 -0
  5. package/dist/adapters/next.js.map +1 -0
  6. package/dist/adapters/next.mjs +14461 -0
  7. package/dist/adapters/next.mjs.map +1 -0
  8. package/dist/adapters/vite.d.mts +15 -0
  9. package/dist/adapters/vite.d.ts +15 -0
  10. package/dist/adapters/vite.js +14465 -0
  11. package/dist/adapters/vite.js.map +1 -0
  12. package/dist/adapters/vite.mjs +14461 -0
  13. package/dist/adapters/vite.mjs.map +1 -0
  14. package/dist/adapters/webpack.d.mts +25 -0
  15. package/dist/adapters/webpack.d.ts +25 -0
  16. package/dist/adapters/webpack.js +14440 -0
  17. package/dist/adapters/webpack.js.map +1 -0
  18. package/dist/adapters/webpack.mjs +14436 -0
  19. package/dist/adapters/webpack.mjs.map +1 -0
  20. package/dist/babel/index.d.mts +14 -0
  21. package/dist/babel/index.d.ts +14 -0
  22. package/dist/babel/index.js +14392 -0
  23. package/dist/babel/index.js.map +1 -0
  24. package/dist/babel/index.mjs +14386 -0
  25. package/dist/babel/index.mjs.map +1 -0
  26. package/dist/cli/init.d.mts +1 -0
  27. package/dist/cli/init.d.ts +1 -0
  28. package/dist/cli/init.js +122 -0
  29. package/dist/cli/init.js.map +1 -0
  30. package/dist/cli/init.mjs +99 -0
  31. package/dist/cli/init.mjs.map +1 -0
  32. package/dist/index.d.mts +16 -0
  33. package/dist/index.d.ts +16 -0
  34. package/dist/index.js +43 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/index.mjs +17 -0
  37. package/dist/index.mjs.map +1 -0
  38. package/dist/options-DBNexJk6.d.mts +25 -0
  39. package/dist/options-DBNexJk6.d.ts +25 -0
  40. package/dist/postcss/index.d.mts +17 -0
  41. package/dist/postcss/index.d.ts +17 -0
  42. package/dist/postcss/index.js +113 -0
  43. package/dist/postcss/index.js.map +1 -0
  44. package/dist/postcss/index.mjs +83 -0
  45. package/dist/postcss/index.mjs.map +1 -0
  46. package/package.json +102 -0
@@ -0,0 +1,83 @@
1
+ // react-scoped-css v2 — https://github.com/dinesh-gamage/react-scoped-css
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/shared/hash.ts
10
+ import { createHash } from "crypto";
11
+ import fs from "fs";
12
+ import path from "path";
13
+ function findUpSync(filename, startDir) {
14
+ let dir = startDir;
15
+ while (true) {
16
+ const candidate = path.join(dir, filename);
17
+ if (fs.existsSync(candidate)) return candidate;
18
+ const parent = path.dirname(dir);
19
+ if (parent === dir) return void 0;
20
+ dir = parent;
21
+ }
22
+ }
23
+ function readPackageNameSync(pkgJsonPath) {
24
+ if (!pkgJsonPath) return "";
25
+ try {
26
+ const raw = fs.readFileSync(pkgJsonPath, "utf8");
27
+ const pkg = JSON.parse(raw);
28
+ return typeof pkg.name === "string" ? pkg.name : "";
29
+ } catch {
30
+ return "";
31
+ }
32
+ }
33
+ function generateHash(absoluteFilePath, salt, hashLength = 8) {
34
+ const pkgJsonPath = findUpSync("package.json", path.dirname(absoluteFilePath));
35
+ const projectRoot = pkgJsonPath ? path.dirname(pkgJsonPath) : process.cwd();
36
+ const relativeFilePath = path.relative(projectRoot, absoluteFilePath).replace(/\.[^./\\]+$/, "");
37
+ const effectiveSalt = salt ?? readPackageNameSync(pkgJsonPath);
38
+ return createHash("md5").update(relativeFilePath + effectiveSalt).digest("hex").slice(0, hashLength);
39
+ }
40
+
41
+ // src/shared/exclude.ts
42
+ function isExcluded(className, excludePrefixes) {
43
+ if (excludePrefixes.length === 0) return false;
44
+ return excludePrefixes.some((prefix) => className.startsWith(prefix));
45
+ }
46
+
47
+ // src/postcss/index.ts
48
+ var scssSyntax = __require("postcss-scss");
49
+ var CLASS_SELECTOR_RE = /\.(-?[_a-zA-Z][_a-zA-Z0-9-]*)/g;
50
+ var plugin = (opts = {}) => {
51
+ const { exclude = [], salt, hashLength = 8 } = opts;
52
+ return {
53
+ postcssPlugin: "react-scoped-css",
54
+ prepare(result) {
55
+ const filePath = result.opts.from;
56
+ if (!filePath || filePath.includes(".module.")) {
57
+ return {};
58
+ }
59
+ if (/\.s[ac]ss$/i.test(filePath) && !result.opts.syntax) {
60
+ result.opts.syntax = scssSyntax;
61
+ }
62
+ const hash = generateHash(filePath, salt, hashLength);
63
+ return {
64
+ Rule(rule) {
65
+ rule.selector = rule.selector.replace(
66
+ CLASS_SELECTOR_RE,
67
+ (match, className) => {
68
+ if (isExcluded(className, exclude)) return match;
69
+ return `.${className}-${hash}`;
70
+ }
71
+ );
72
+ }
73
+ };
74
+ }
75
+ };
76
+ };
77
+ plugin.postcss = true;
78
+ var postcss_default = plugin;
79
+ export {
80
+ postcss_default as default,
81
+ plugin as scopedCssPostcss
82
+ };
83
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/shared/hash.ts","../../src/shared/exclude.ts","../../src/postcss/index.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport fs from 'fs';\nimport path from 'path';\n\n/** Walk up from `dir` looking for `filename`. Returns the first match or undefined. */\nfunction findUpSync(filename: string, startDir: string): string | undefined {\n let dir = startDir;\n while (true) {\n const candidate = path.join(dir, filename);\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) return undefined; // reached filesystem root\n dir = parent;\n }\n}\n\nfunction readPackageNameSync(pkgJsonPath: string | undefined): string {\n if (!pkgJsonPath) return '';\n try {\n const raw = fs.readFileSync(pkgJsonPath, 'utf8');\n const pkg = JSON.parse(raw) as { name?: string };\n return typeof pkg.name === 'string' ? pkg.name : '';\n } catch {\n return '';\n }\n}\n\n/**\n * Generate a stable 8-char hex hash for a source file.\n *\n * Uses the file's path relative to the nearest package.json root so the hash\n * is identical on every developer machine and in CI, regardless of where the\n * repo is cloned.\n *\n * @param absoluteFilePath Absolute path to the source file being processed.\n * @param salt Override the default salt (package.json \"name\").\n * Pass an explicit value to guarantee global uniqueness\n * across monorepos or multi-app deployments.\n * @param hashLength Number of hex characters to use (default: 8).\n */\nexport function generateHash(\n absoluteFilePath: string,\n salt?: string,\n hashLength = 8,\n): string {\n const pkgJsonPath = findUpSync('package.json', path.dirname(absoluteFilePath));\n const projectRoot = pkgJsonPath ? path.dirname(pkgJsonPath) : process.cwd();\n // Strip extension so Card.tsx and Card.scss produce the same hash.\n // A component's scope identity is its path without extension.\n const relativeFilePath = path.relative(projectRoot, absoluteFilePath).replace(/\\.[^./\\\\]+$/, '');\n\n const effectiveSalt = salt ?? readPackageNameSync(pkgJsonPath);\n\n return createHash('md5')\n .update(relativeFilePath + effectiveSalt)\n .digest('hex')\n .slice(0, hashLength);\n}\n","/**\n * Returns true if className should be left unscoped.\n *\n * A class is excluded when its string value starts with any of the configured\n * prefixes. This is the intended behaviour for component-library overrides:\n * `exclude: ['uxp-', 'global-']` leaves `uxp-button` and `global-header`\n * untouched while scoping everything else.\n */\nexport function isExcluded(className: string, excludePrefixes: string[]): boolean {\n if (excludePrefixes.length === 0) return false;\n return excludePrefixes.some(prefix => className.startsWith(prefix));\n}\n","import type { PluginCreator, Syntax } from 'postcss';\nimport { generateHash } from '../shared/hash';\nimport { isExcluded } from '../shared/exclude';\nimport type { ScopedCssOptions } from '../shared/options';\n\n// postcss-scss is a bundled dependency — use it automatically for .scss/.sass files\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nconst scssSyntax: Syntax = require('postcss-scss');\n\n// Matches a CSS class selector token: .foo, .foo-bar, ._private, etc.\n// Captures the class name without the leading dot.\nconst CLASS_SELECTOR_RE = /\\.(-?[_a-zA-Z][_a-zA-Z0-9-]*)/g;\n\n/**\n * PostCSS plugin — appends `-{hash}` to every class selector in the file,\n * unless the class name starts with an excluded prefix.\n *\n * Files whose path contains `.module.` are skipped entirely — they are already\n * handled by CSS Modules and must not be double-processed.\n *\n * Usage (postcss.config.js):\n * const { scopedCssPostcss } = require('react-scoped-css/postcss');\n * module.exports = { plugins: [scopedCssPostcss({ exclude: ['uxp-'] })] };\n */\nconst plugin: PluginCreator<ScopedCssOptions> = (opts: ScopedCssOptions = {}) => {\n const { exclude = [], salt, hashLength = 8 } = opts;\n\n return {\n postcssPlugin: 'react-scoped-css',\n\n prepare(result) {\n const filePath = result.opts.from;\n\n // Skip CSS Modules files — they are already scoped.\n if (!filePath || filePath.includes('.module.')) {\n return {};\n }\n\n // Apply SCSS syntax automatically for .scss/.sass files.\n // This allows users to run postcss-loader without a separate syntax config.\n if (/\\.s[ac]ss$/i.test(filePath) && !result.opts.syntax) {\n result.opts.syntax = scssSyntax;\n }\n\n const hash = generateHash(filePath, salt, hashLength);\n\n return {\n Rule(rule) {\n rule.selector = rule.selector.replace(\n CLASS_SELECTOR_RE,\n (match, className: string) => {\n if (isExcluded(className, exclude)) return match;\n return `.${className}-${hash}`;\n },\n );\n },\n };\n },\n };\n};\n\nplugin.postcss = true;\n\nexport { plugin as scopedCssPostcss };\nexport default plugin;\n"],"mappings":";;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,SAAS,WAAW,UAAkB,UAAsC;AACxE,MAAI,MAAM;AACV,SAAO,MAAM;AACT,UAAM,YAAY,KAAK,KAAK,KAAK,QAAQ;AACzC,QAAI,GAAG,WAAW,SAAS,EAAG,QAAO;AACrC,UAAM,SAAS,KAAK,QAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACV;AACJ;AAEA,SAAS,oBAAoB,aAAyC;AAClE,MAAI,CAAC,YAAa,QAAO;AACzB,MAAI;AACA,UAAM,MAAM,GAAG,aAAa,aAAa,MAAM;AAC/C,UAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,WAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAAA,EACrD,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAeO,SAAS,aACZ,kBACA,MACA,aAAa,GACP;AACN,QAAM,cAAc,WAAW,gBAAgB,KAAK,QAAQ,gBAAgB,CAAC;AAC7E,QAAM,cAAc,cAAc,KAAK,QAAQ,WAAW,IAAI,QAAQ,IAAI;AAG1E,QAAM,mBAAmB,KAAK,SAAS,aAAa,gBAAgB,EAAE,QAAQ,eAAe,EAAE;AAE/F,QAAM,gBAAgB,QAAQ,oBAAoB,WAAW;AAE7D,SAAO,WAAW,KAAK,EAClB,OAAO,mBAAmB,aAAa,EACvC,OAAO,KAAK,EACZ,MAAM,GAAG,UAAU;AAC5B;;;ACjDO,SAAS,WAAW,WAAmB,iBAAoC;AAC9E,MAAI,gBAAgB,WAAW,EAAG,QAAO;AACzC,SAAO,gBAAgB,KAAK,YAAU,UAAU,WAAW,MAAM,CAAC;AACtE;;;ACJA,IAAM,aAAqB,UAAQ,cAAc;AAIjD,IAAM,oBAAoB;AAa1B,IAAM,SAA0C,CAAC,OAAyB,CAAC,MAAM;AAC7E,QAAM,EAAE,UAAU,CAAC,GAAG,MAAM,aAAa,EAAE,IAAI;AAE/C,SAAO;AAAA,IACH,eAAe;AAAA,IAEf,QAAQ,QAAQ;AACZ,YAAM,WAAW,OAAO,KAAK;AAG7B,UAAI,CAAC,YAAY,SAAS,SAAS,UAAU,GAAG;AAC5C,eAAO,CAAC;AAAA,MACZ;AAIA,UAAI,cAAc,KAAK,QAAQ,KAAK,CAAC,OAAO,KAAK,QAAQ;AACrD,eAAO,KAAK,SAAS;AAAA,MACzB;AAEA,YAAM,OAAO,aAAa,UAAU,MAAM,UAAU;AAEpD,aAAO;AAAA,QACH,KAAK,MAAM;AACP,eAAK,WAAW,KAAK,SAAS;AAAA,YAC1B;AAAA,YACA,CAAC,OAAO,cAAsB;AAC1B,kBAAI,WAAW,WAAW,OAAO,EAAG,QAAO;AAC3C,qBAAO,IAAI,SAAS,IAAI,IAAI;AAAA,YAChC;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;AAEA,OAAO,UAAU;AAGjB,IAAO,kBAAQ;","names":[]}
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "@dinesh-gamage/react-scoped-css",
3
+ "version": "2.0.0",
4
+ "description": "Zero-code-change CSS scoping for React — no className renames, no new syntax",
5
+ "license": "MIT",
6
+ "author": "dinesh-gamage",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/dinesh-gamage/react-scoped-css.git"
10
+ },
11
+ "keywords": [
12
+ "react",
13
+ "css",
14
+ "scoped css",
15
+ "babel plugin",
16
+ "postcss plugin",
17
+ "css modules",
18
+ "scoped styles",
19
+ "vite",
20
+ "webpack",
21
+ "nextjs"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.mjs",
27
+ "require": "./dist/index.js"
28
+ },
29
+ "./babel": {
30
+ "types": "./dist/babel/index.d.ts",
31
+ "import": "./dist/babel/index.mjs",
32
+ "require": "./dist/babel/index.js"
33
+ },
34
+ "./postcss": {
35
+ "types": "./dist/postcss/index.d.ts",
36
+ "import": "./dist/postcss/index.mjs",
37
+ "require": "./dist/postcss/index.js"
38
+ },
39
+ "./vite": {
40
+ "types": "./dist/adapters/vite.d.ts",
41
+ "import": "./dist/adapters/vite.mjs",
42
+ "require": "./dist/adapters/vite.js"
43
+ },
44
+ "./next": {
45
+ "types": "./dist/adapters/next.d.ts",
46
+ "import": "./dist/adapters/next.mjs",
47
+ "require": "./dist/adapters/next.js"
48
+ },
49
+ "./webpack": {
50
+ "types": "./dist/adapters/webpack.d.ts",
51
+ "import": "./dist/adapters/webpack.mjs",
52
+ "require": "./dist/adapters/webpack.js"
53
+ }
54
+ },
55
+ "main": "./dist/index.js",
56
+ "module": "./dist/index.js",
57
+ "types": "./dist/index.d.ts",
58
+ "bin": {
59
+ "react-scoped-css": "./dist/cli/init.js"
60
+ },
61
+ "files": [
62
+ "dist/**/*"
63
+ ],
64
+ "scripts": {
65
+ "build": "tsup",
66
+ "build:watch": "tsup --watch",
67
+ "test": "vitest run",
68
+ "test:watch": "vitest",
69
+ "test:coverage": "vitest run --coverage",
70
+ "lint": "tsc --noEmit",
71
+ "prepublishOnly": "npm run lint && npm run test && npm run build"
72
+ },
73
+ "dependencies": {
74
+ "postcss-scss": "^4.0.9"
75
+ },
76
+ "devDependencies": {
77
+ "@babel/core": "^7.24.0",
78
+ "@babel/generator": "^7.24.0",
79
+ "@babel/parser": "^7.24.0",
80
+ "@babel/traverse": "^7.24.0",
81
+ "@babel/types": "^7.24.0",
82
+ "@types/babel__core": "^7.20.5",
83
+ "@types/babel__traverse": "^7.20.5",
84
+ "@types/node": "^20.0.0",
85
+ "postcss": "^8.4.38",
86
+ "tsup": "^8.0.0",
87
+ "typescript": "^5.4.0",
88
+ "vitest": "^1.4.0"
89
+ },
90
+ "peerDependencies": {
91
+ "@babel/core": ">=7.0.0",
92
+ "postcss": ">=8.0.0"
93
+ },
94
+ "peerDependenciesMeta": {
95
+ "@babel/core": {
96
+ "optional": true
97
+ },
98
+ "postcss": {
99
+ "optional": true
100
+ }
101
+ }
102
+ }