├── .eslintignore
├── .gitignore
├── .travis.yml
├── jest.config.js
├── .babelrc
├── CHANGELOG.md
├── tsconfig.json
├── .editorconfig
├── .eslintrc
├── LICENSE
├── test
└── index.test.ts
├── src
└── index.ts
├── README.md
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | node_modules
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | coverage
3 | dist
4 | node_modules
5 | *.log
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - v8
4 | script:
5 | - yarn lint
6 | - yarn test --coverage
7 | cache:
8 | - yarn
9 | after_success:
10 | - bash <(curl -s https://codecov.io/bash)
11 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ["js", "jsx", "ts", "tsx"],
3 | testMatch: ["**/?(*.)+(spec|test).(j|t)s?(x)"],
4 | transform: {
5 | "\\.(j|t)sx?$": "babel-jest"
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "browsers": "defaults"
8 | }
9 | }
10 | ],
11 | "@babel/preset-typescript"
12 | ],
13 | "plugins": [
14 | "@babel/plugin-proposal-class-properties"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [0.1.1](https://github.com/diegohaz/styled-selector/compare/v0.1.0...v0.1.1) (2018-10-22)
3 |
4 |
5 | ### Bug Fixes
6 |
7 | * Fix build ([7b126ad](https://github.com/diegohaz/styled-selector/commit/7b126ad))
8 |
9 |
10 |
11 |
12 | # 0.1.0 (2018-10-22)
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "dist/ts",
5 | "target": "esnext",
6 | "module": "esnext",
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "strictFunctionTypes": false,
10 | "declaration": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "noImplicitReturns": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "stripInternal": true
16 | }
17 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "airbnb-base",
5 | "plugin:prettier/recommended"
6 | ],
7 | "env": {
8 | "jest": true
9 | },
10 | "settings": {
11 | "import/resolver": {
12 | "node": {
13 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
14 | }
15 | }
16 | },
17 | "overrides": [
18 | {
19 | "files": ["**/*.ts", "**/*.tsx"],
20 | "parser": "typescript-eslint-parser",
21 | "plugins": [
22 | "typescript"
23 | ],
24 | "rules": {
25 | "no-undef": "off",
26 | "no-unused-vars": "off",
27 | "no-restricted-globals": "off"
28 | }
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Haz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/index.test.ts:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import emotionStyled, { css } from "react-emotion";
3 | import use from "reuse";
4 | import s from "../src";
5 |
6 | test("styled-components", () => {
7 | const Comp = styled.div``;
8 | expect(s(Comp)).toMatchInlineSnapshot(`".sc-bdVaJa"`);
9 | });
10 |
11 | test("emotion styled", () => {
12 | const Comp = emotionStyled("div", { target: "foo" })``;
13 | expect(s(Comp)).toMatchInlineSnapshot(`".foo"`);
14 | });
15 |
16 | test("emotion css", () => {
17 | const className = css``;
18 | expect(s(className)).toMatchInlineSnapshot(`".css-0"`);
19 | });
20 |
21 | test("reuse", () => {
22 | const Comp = use(
23 | styled.div``,
24 | emotionStyled("div")``,
25 | emotionStyled("div", { target: "foo" })``,
26 | "span"
27 | );
28 | expect(s(Comp)).toMatchInlineSnapshot(`".sc-bwzfXH.foo"`);
29 | });
30 |
31 | test("selector", () => {
32 | const Comp = () => null;
33 | Comp.selector = "#foo";
34 | expect(s(Comp)).toBe("#foo");
35 | });
36 |
37 | test("no selector", () => {
38 | const Comp = () => null;
39 | expect(s(Comp)).toMatchInlineSnapshot(`"NO_COMPONENT_SELECTOR"`);
40 | });
41 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ReactType, ComponentType } from "react";
2 |
3 | const NO_SELECTOR = "NO_COMPONENT_SELECTOR";
4 |
5 | function isStyledComponent(comp: any): comp is { styledComponentId: string } {
6 | return Boolean(comp && typeof comp.styledComponentId === "string");
7 | }
8 |
9 | function isEmotion(comp: any) {
10 | // eslint-disable-next-line no-underscore-dangle
11 | return Boolean(comp && comp.__emotion_real);
12 | }
13 |
14 | function isReuse(comp: any): comp is { uses: Array } {
15 | return Boolean(comp && Array.isArray(comp.uses));
16 | }
17 |
18 | function hasSelector(comp: any): comp is { selector: string } {
19 | return Boolean(comp && typeof comp.selector === "string");
20 | }
21 |
22 | function getSelector(comp: string | ComponentType): string {
23 | if (typeof comp === "string") {
24 | return `.${comp}`;
25 | }
26 | if (isStyledComponent(comp)) {
27 | return `.${comp.styledComponentId}`;
28 | }
29 | if (isEmotion(comp)) {
30 | return comp.toString();
31 | }
32 | if (isReuse(comp)) {
33 | return comp.uses
34 | .filter(x => typeof x !== "string")
35 | .map(getSelector)
36 | .filter(x => x && x !== NO_SELECTOR)
37 | .join("");
38 | }
39 | if (hasSelector(comp)) {
40 | return comp.selector;
41 | }
42 | return NO_SELECTOR;
43 | }
44 |
45 | export default getSelector;
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # styled-selector
2 |
3 | [](https://github.com/diegohaz/nod)
4 | [](https://npmjs.org/package/styled-selector)
5 | [](https://travis-ci.org/diegohaz/styled-selector) [](https://codecov.io/gh/diegohaz/styled-selector/branch/master)
6 |
7 | Get static CSS(-in-JS) selectors (like `.sc-htpNat`) from React components
8 |
9 | > **Note**:
10 | >
11 | > This library relies on implementation details of libraries mentioned in [Usage](#usage) so as to get component selectors. Styled Components, for example, has a `.styledComponentId` property, whereas Emotion uses the `.toString()` method.
12 | >
13 | > This means that they can break it in patch versions. If this happens, we'll release a fix here as soon as possible. For more information, see our [code](https://github.com/diegohaz/styled-selector/blob/master/src/index.ts).
14 |
15 | ## Install
16 |
17 | $ npm i styled-selector
18 |
19 | ## Usage
20 |
21 | ### [Styled Components](https://github.com/styled-components/styled-components)
22 | ```js
23 | import styled from "styled-components";
24 | import s from "styled-selector";
25 |
26 | const Comp = styled.div``;
27 |
28 | s(Comp); // .sc-htpNat
29 | ```
30 |
31 | ### [Emotion](https://github.com/emotion-js/emotion)
32 | ```js
33 | import styled, { css } from "react-emotion";
34 | import s from "styled-selector";
35 |
36 | const Comp = styled("div")``;
37 |
38 | s(Comp); // .css-htpNat
39 |
40 | const className = css``;
41 |
42 | s(className); // .css-htpNat
43 | ```
44 |
45 | ### [Reuse](https://github.com/diegohaz/reuse)
46 | ```js
47 | import styled from "styled-components";
48 | import use from "reuse";
49 | import s from "styled-selector";
50 |
51 | const Comp = use(styled.div``);
52 |
53 | s(Comp); // .sc-htpNat
54 | ```
55 |
56 | ### Custom
57 | ```jsx
58 | import React from "react";
59 | import s from "styled-selector";
60 |
61 | const Comp = () => ;
62 | Comp.selector = "#foo";
63 |
64 | s(Comp); // #foo
65 | ```
66 |
67 | ## License
68 |
69 | MIT © [Haz](https://github.com/diegohaz)
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "styled-selector",
3 | "version": "0.1.1",
4 | "description": "Get static CSS(-in-JS) selectors from React components",
5 | "license": "MIT",
6 | "repository": "diegohaz/styled-selector",
7 | "main": "dist/index.js",
8 | "types": "dist/ts/src",
9 | "author": {
10 | "name": "Haz",
11 | "email": "hazdiego@gmail.com",
12 | "url": "https://github.com/diegohaz"
13 | },
14 | "files": [
15 | "dist",
16 | "src"
17 | ],
18 | "scripts": {
19 | "test": "jest",
20 | "coverage": "npm test -- --coverage",
21 | "postcoverage": "opn coverage/lcov-report/index.html",
22 | "type-check": "tsc --noEmit",
23 | "lint": "eslint . --ext js,ts,tsx",
24 | "clean": "rimraf dist",
25 | "prebuild": "npm run clean",
26 | "build": "tsc --emitDeclarationOnly && babel src -d dist -x .js,.ts,.tsx",
27 | "preversion": "npm run lint && npm test && npm run build",
28 | "version": "standard-changelog && git add CHANGELOG.md",
29 | "postpublish": "git push origin master --follow-tags"
30 | },
31 | "husky": {
32 | "hooks": {
33 | "pre-commit": "lint-staged"
34 | }
35 | },
36 | "lint-staged": {
37 | "*.{js,ts,tsx}": [
38 | "eslint --fix --ext js,ts,tsx",
39 | "git add"
40 | ]
41 | },
42 | "keywords": [
43 | "styled-selector"
44 | ],
45 | "dependencies": {},
46 | "devDependencies": {
47 | "@babel/cli": "^7.1.2",
48 | "@babel/core": "^7.1.2",
49 | "@babel/plugin-proposal-class-properties": "^7.1.0",
50 | "@babel/preset-env": "^7.1.0",
51 | "@babel/preset-typescript": "^7.1.0",
52 | "@types/jest": "^23.3.5",
53 | "@types/prop-types": "^15.5.6",
54 | "@types/react": "^16.4.18",
55 | "@types/react-dom": "^16.0.9",
56 | "@types/styled-components": "^4.0.1",
57 | "babel-core": "^7.0.0-bridge.0",
58 | "babel-eslint": "^10.0.1",
59 | "babel-jest": "^23.6.0",
60 | "emotion": "^9.2.12",
61 | "eslint": "^5.7.0",
62 | "eslint-config-airbnb-base": "^13.1.0",
63 | "eslint-config-prettier": "^3.1.0",
64 | "eslint-plugin-import": "^2.14.0",
65 | "eslint-plugin-prettier": "^3.0.0",
66 | "eslint-plugin-typescript": "^0.12.0",
67 | "husky": "^1.1.2",
68 | "jest-cli": "^23.6.0",
69 | "lint-staged": "^7.3.0",
70 | "opn-cli": "^3.1.0",
71 | "prettier": "^1.14.3",
72 | "prop-types": "^15.6.2",
73 | "react": "^16.5.2",
74 | "react-dom": "^16.5.2",
75 | "react-emotion": "^9.2.12",
76 | "reuse": "^1.2.0",
77 | "rimraf": "^2.6.2",
78 | "standard-changelog": "^2.0.1",
79 | "styled-components": "^4.0.2",
80 | "typescript": "^3.1.3",
81 | "typescript-eslint-parser": "^20.0.0"
82 | },
83 | "peerDependencies": {
84 | "react": "*"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------