diff --git a/CHANGELOG.md b/CHANGELOG.md index 6142b0e..637c329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- namespace prefix to help multiple instances of library not clash (fix #381 in #395) + ## [1.5.0] - transform anonymous default exports (fix #371 in #367) diff --git a/README.md b/README.md index ec79060..431a5cd 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,7 @@ interface Options { ssr: boolean; displayName: boolean; minify: boolean; + componentIdPrefix: string; } ``` @@ -275,6 +276,12 @@ The minification is not exactly the same and may produce slightly different resu Default value is `false` which means the minification is not being performed. +### `componentIdPrefix` + +To avoid colisions when running more than one insance of typescript-plugin-styled-components at a time, you can add a componentIdPrefix by providing an arbitrary string to this option. + +Default value is `''` which means that no namespacing will happen. + ### `identifiers` This option allows to customize identifiers used by `styled-components` API functions. diff --git a/src/__tests__/baselines/componentIdPrefix/issue33.ts.baseline b/src/__tests__/baselines/componentIdPrefix/issue33.ts.baseline new file mode 100644 index 0000000..8051f5b --- /dev/null +++ b/src/__tests__/baselines/componentIdPrefix/issue33.ts.baseline @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`issue33.ts 1`] = ` + +File: issue33.ts +Source code: + + declare const styled: any; + declare const $: any; + declare const jQuery: any; + declare const _: any; + + declare const Button: any; + + const Button1 = Button.extend\` color: red \`; + + const Button2 = $.extend\` color: red \`; + const Button3 = jQuery.extend\` color: red \`; + const Button4 = _.extend\` color: red \`; + + +TypeScript before transform: + + declare const styled: any; + declare const $: any; + declare const jQuery: any; + declare const _: any; + declare const Button: any; + const Button1 = Button.extend \` color: red \`; + const Button2 = $.extend \` color: red \`; + const Button3 = jQuery.extend \` color: red \`; + const Button4 = _.extend \` color: red \`; + + +TypeScript after transform: + + declare const styled: any; + declare const $: any; + declare const jQuery: any; + declare const _: any; + declare const Button: any; + const Button1 = Button.extend \` color: red \`; + const Button2 = $.extend \` color: red \`; + const Button3 = jQuery.extend \` color: red \`; + const Button4 = _.extend \` color: red \`; + + + +`; diff --git a/src/__tests__/baselines/componentIdPrefix/multiple-components.tsx.baseline b/src/__tests__/baselines/componentIdPrefix/multiple-components.tsx.baseline new file mode 100644 index 0000000..a792748 --- /dev/null +++ b/src/__tests__/baselines/componentIdPrefix/multiple-components.tsx.baseline @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`multiple-components.tsx 1`] = ` + +File: multiple-components.tsx +Source code: + + import styled from 'styled-components'; + + export function createButtons() { + const A = styled.button\` color: red; \`; + const B = styled(A)\` color: blue; \`; + + return { A, B }; + } + + export function createDivs() { + const A = styled.div\` color: green; \`; + const B = styled(A)\` color: yellow; \`; + + return { A, B }; + } + + +TypeScript before transform: + + import styled from "styled-components"; + export function createButtons() { + const A = styled.button \` color: red; \`; + const B = styled(A) \` color: blue; \`; + return { A, B }; + } + export function createDivs() { + const A = styled.div \` color: green; \`; + const B = styled(A) \` color: yellow; \`; + return { A, B }; + } + + +TypeScript after transform: + + import styled from 'styled-components'; + export function createButtons() { + const A = styled.button.withConfig({ displayName: "test-A", componentId: "test-hana72" }) \` color: red; \`; + const B = styled(A).withConfig({ displayName: "test-B", componentId: "test-ke4nds" }) \` color: blue; \`; + return { A, B }; + } + export function createDivs() { + const A = styled.div.withConfig({ displayName: "test-A", componentId: "test-1q7vmnt" }) \` color: green; \`; + const B = styled(A).withConfig({ displayName: "test-B", componentId: "test-7q8oop" }) \` color: yellow; \`; + return { A, B }; + } + + + +`; diff --git a/src/__tests__/baselines/componentIdPrefix/sample1.ts.baseline b/src/__tests__/baselines/componentIdPrefix/sample1.ts.baseline new file mode 100644 index 0000000..5a7066a --- /dev/null +++ b/src/__tests__/baselines/componentIdPrefix/sample1.ts.baseline @@ -0,0 +1,96 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`sample1.ts 1`] = ` + +File: sample1.ts +Source code: + + import styled from 'styled-components'; + + const Button = styled.button\` + color: red; + \`; + + declare const nonStyled: any; + + const NonButton = nonStyled.button\` + yo + \`; + + const OtherButton = styled(Button)\` + color: blue; + \`; + + const SuperButton = Button.extend\` + color: super; + \`; + + export default styled.link\` + color: black; + \`; + + export const SmallButton = Button.extend\` + font-size: .7em; + \`; + + const MiniButton = styled(SmallButton).attrs({ size: 'mini' })\` + font-size: .1em; + \`; + + +TypeScript before transform: + + import styled from "styled-components"; + const Button = styled.button \` + color: red; + \`; + declare const nonStyled: any; + const NonButton = nonStyled.button \` + yo + \`; + const OtherButton = styled(Button) \` + color: blue; + \`; + const SuperButton = Button.extend \` + color: super; + \`; + export default styled.link \` + color: black; + \`; + export const SmallButton = Button.extend \` + font-size: .7em; + \`; + const MiniButton = styled(SmallButton).attrs({ size: "mini" }) \` + font-size: .1em; + \`; + + +TypeScript after transform: + + import styled from 'styled-components'; + const Button = styled.button.withConfig({ displayName: "test-Button", componentId: "test-1okqsxw" }) \` + color: red; + \`; + declare const nonStyled: any; + const NonButton = nonStyled.button \` + yo + \`; + const OtherButton = styled(Button).withConfig({ displayName: "test-OtherButton", componentId: "test-ce0fkl" }) \` + color: blue; + \`; + const SuperButton = Button.extend \` + color: super; + \`; + export default styled.link.withConfig({ componentId: "test-vba0dl" }) \` + color: black; + \`; + export const SmallButton = Button.extend \` + font-size: .7em; + \`; + const MiniButton = styled(SmallButton).attrs({ size: "mini" }).withConfig({ displayName: "test-MiniButton", componentId: "test-ndnumj" }) \` + font-size: .1em; + \`; + + + +`; diff --git a/src/__tests__/baselines/componentIdPrefix/sample2.ts.baseline b/src/__tests__/baselines/componentIdPrefix/sample2.ts.baseline new file mode 100644 index 0000000..7554e7e --- /dev/null +++ b/src/__tests__/baselines/componentIdPrefix/sample2.ts.baseline @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`sample2.ts 1`] = ` + +File: sample2.ts +Source code: + + import styled from 'styled-components'; + + const styled2 = styled; + const NonButton = styled.button; + const NonStyled = styled\` color: red; \`; + const Link = styled(NonStyled)\` color: red; \`; + + +TypeScript before transform: + + import styled from "styled-components"; + const styled2 = styled; + const NonButton = styled.button; + const NonStyled = styled \` color: red; \`; + const Link = styled(NonStyled) \` color: red; \`; + + +TypeScript after transform: + + import styled from 'styled-components'; + const styled2 = styled; + const NonButton = styled.button; + const NonStyled = styled \` color: red; \`; + const Link = styled(NonStyled).withConfig({ displayName: "test-Link", componentId: "test-1xlc242" }) \` color: red; \`; + + + +`; diff --git a/src/__tests__/baselines/componentIdPrefix/sample3.tsx.baseline b/src/__tests__/baselines/componentIdPrefix/sample3.tsx.baseline new file mode 100644 index 0000000..cf12265 --- /dev/null +++ b/src/__tests__/baselines/componentIdPrefix/sample3.tsx.baseline @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`sample3.tsx 1`] = ` + +File: sample3.tsx +Source code: + + import * as React from 'react'; + import styled from '../themed-styled'; + import { SmallButton } from './sample1'; + + interface LabelProps { + size: number; + } + + const CustomLabel = styled.label\` + font-size: \${(props: LabelProps) => props.size + 'px'} + \`; + + const LabeledLink = () => ; + + export default CustomLabel; + + +TypeScript before transform: + + import * as React from "react"; + import styled from "../themed-styled"; + import { SmallButton } from "./sample1"; + interface LabelProps { + size: number; + } + const CustomLabel = styled.label \` + font-size: \${(props: LabelProps) => props.size + "px"} + \`; + const LabeledLink = () => ; + export default CustomLabel; + + +TypeScript after transform: + + import * as React from 'react'; + import styled from '../themed-styled'; + import { SmallButton } from './sample1'; + interface LabelProps { + size: number; + } + const CustomLabel = styled.label.withConfig({ displayName: "test-CustomLabel", componentId: "test-1ydgk9x" }) \` + font-size: \${(props: LabelProps) => props.size + 'px'} + \`; + const LabeledLink = () => ; + export default CustomLabel; + + + +`; diff --git a/src/__tests__/baselines/componentIdPrefix/style-objects.ts.baseline b/src/__tests__/baselines/componentIdPrefix/style-objects.ts.baseline new file mode 100644 index 0000000..32cae79 --- /dev/null +++ b/src/__tests__/baselines/componentIdPrefix/style-objects.ts.baseline @@ -0,0 +1,96 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`style-objects.ts 1`] = ` + +File: style-objects.ts +Source code: + + declare const styled: any; + + const Button = styled.button({ + color: 'red' + }); + + declare const nonStyled: any; + + const NonButton = nonStyled.button({ + color: 'red' + }); + + const OtherButton = styled(Button)({ + color: 'blue' + }); + + const SuperButton = Button.extend({ + color: 'super' + }); + + export default styled.link({ + color: 'black' + }); + + export const SmallButton = Button.extend({ + fontSize: '.7em' + }); + + const MiniButton = styled(SmallButton).attrs({ size: 'mini' })({ + fontSize: '.1em' + }); + + +TypeScript before transform: + + declare const styled: any; + const Button = styled.button({ + color: "red" + }); + declare const nonStyled: any; + const NonButton = nonStyled.button({ + color: "red" + }); + const OtherButton = styled(Button)({ + color: "blue" + }); + const SuperButton = Button.extend({ + color: "super" + }); + export default styled.link({ + color: "black" + }); + export const SmallButton = Button.extend({ + fontSize: ".7em" + }); + const MiniButton = styled(SmallButton).attrs({ size: "mini" })({ + fontSize: ".1em" + }); + + +TypeScript after transform: + + declare const styled: any; + const Button = styled.button.withConfig({ displayName: "test-Button", componentId: "test-7b7p9e" })({ + color: 'red' + }); + declare const nonStyled: any; + const NonButton = nonStyled.button({ + color: 'red' + }); + const OtherButton = styled(Button).withConfig({ displayName: "test-OtherButton", componentId: "test-14ah7t" })({ + color: 'blue' + }); + const SuperButton = Button.extend({ + color: 'super' + }); + export default styled.link.withConfig({ componentId: "test-8xjslt" })({ + color: 'black' + }); + export const SmallButton = Button.extend({ + fontSize: '.7em' + }); + const MiniButton = styled(SmallButton).attrs({ size: "mini" }).withConfig({ displayName: "test-MiniButton", componentId: "test-ad4g7l" })({ + fontSize: '.1em' + }); + + + +`; diff --git a/src/__tests__/componentIdPrefix-baselines.test.ts b/src/__tests__/componentIdPrefix-baselines.test.ts new file mode 100644 index 0000000..f22ea4b --- /dev/null +++ b/src/__tests__/componentIdPrefix-baselines.test.ts @@ -0,0 +1,9 @@ +import createTransformer from '..'; +import { expectBaselineTransforms } from './expectTransform'; + +const transformer = createTransformer({ + componentIdPrefix: 'test' +}); + +expectBaselineTransforms(transformer, __dirname + '/fixtures/base', 'baselines/componentIdPrefix'); +expectBaselineTransforms(transformer, __dirname + '/fixtures/componentIdPrefix', 'baselines/componentIdPrefix'); diff --git a/src/__tests__/fixtures/componentIdPrefix/multiple-components.tsx b/src/__tests__/fixtures/componentIdPrefix/multiple-components.tsx new file mode 100644 index 0000000..5789b9b --- /dev/null +++ b/src/__tests__/fixtures/componentIdPrefix/multiple-components.tsx @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +export function createButtons() { + const A = styled.button` color: red; `; + const B = styled(A)` color: blue; `; + + return { A, B }; +} + +export function createDivs() { + const A = styled.div` color: green; `; + const B = styled(A)` color: yellow; `; + + return { A, B }; +} diff --git a/src/createTransformer.ts b/src/createTransformer.ts index ad02b56..8955b5b 100644 --- a/src/createTransformer.ts +++ b/src/createTransformer.ts @@ -122,7 +122,8 @@ export function createTransformer({ identifiers = {}, ssr = true, displayName = true, - minify = false + minify = false, + componentIdPrefix = '', } : Partial = {}) { /** @@ -132,9 +133,9 @@ export function createTransformer({ * (const|var|let) ComponentName = styled... * export default styled... */ - function getDisplayNameFromNode(node: ts.Node): string | undefined { + function getDisplayNameFromNode(node: ts.Node, componentIdPrefix?: string): string | undefined { if (isVariableDeclaration(node) && isIdentifier(node.name)) { - return getDisplayName(node.getSourceFile().fileName, node.name.text); + return (!!componentIdPrefix ? `${componentIdPrefix}-` : '') + getDisplayName(node.getSourceFile().fileName, node.name.text); } if (isExportAssignment(node)) { @@ -144,11 +145,11 @@ export function createTransformer({ return undefined; } - function getIdFromNode(node: ts.Node, sourceRoot: string | undefined, position: number): string | undefined { + function getIdFromNode(node: ts.Node, sourceRoot: string | undefined, position: number, componentIdPrefix?: string): string | undefined { if ((isVariableDeclaration(node) && isIdentifier(node.name)) || isExportAssignment(node)) { const fileName = node.getSourceFile().fileName; const filePath = sourceRoot ? path.relative(sourceRoot, fileName).replace(path.sep, path.posix.sep) : fileName; - return 'sc-' + hash(`${getDisplayNameFromNode(node)}${filePath}${position}`); + return `${!!componentIdPrefix ? componentIdPrefix : 'sc'}-` + hash(`${getDisplayNameFromNode(node)}${filePath}${position}`); } return undefined; } @@ -187,17 +188,17 @@ export function createTransformer({ const styledConfig = []; if (displayName) { - const displayNameValue = getDisplayNameFromNode(node.parent.parent); + const displayNameValue = getDisplayNameFromNode(node.parent.parent, componentIdPrefix); if (displayNameValue) { styledConfig.push(ts.createPropertyAssignment('displayName', ts.createLiteral(displayNameValue))); - } + } } if (ssr) { - const componentId = getIdFromNode(node.parent.parent, sourceRoot, ++lastComponentPosition); + const componentId = getIdFromNode(node.parent.parent, sourceRoot, ++lastComponentPosition, componentIdPrefix); if (componentId) { - styledConfig.push(ts.createPropertyAssignment('componentId', ts.createLiteral(componentId))); - } + styledConfig.push(ts.createPropertyAssignment('componentId', ts.createLiteral(componentId))); + } } if (styledConfig.length > 0) { diff --git a/src/models/Options.ts b/src/models/Options.ts index 116103b..3b0d462 100644 --- a/src/models/Options.ts +++ b/src/models/Options.ts @@ -5,17 +5,17 @@ export interface Options { * Default strategy is to use `bindingName` if it's defined and use inference algorithm from `filename` otherwise. */ getDisplayName(filename: string, bindingName: string | undefined): string | undefined; - + /** * This option allows to customize identifiers used by `styled-components` API functions. */ identifiers: CustomStyledIdentifiers; - + /** * By adding a unique identifier to every styled component, this plugin avoids checksum mismatches * due to different class generation on the client and on the server. * This option allows to disable component id generation by setting it to `false` - * + * * @defaultValue `true` */ ssr: boolean; @@ -24,9 +24,9 @@ export interface Options { * This option enhances the attached CSS class name on each component with richer output * to help identify your components in the DOM without React DevTools. * It also adds allows you to see the component's `displayName` in React DevTools. - * + * * To disable `displayName` generation set this option to `false` - * + * * @defaultValue `true` */ displayName: boolean; @@ -39,6 +39,14 @@ export interface Options { * @experimental The minification feature is experimental. */ minify: boolean; + + /** + * By adding a componentIdPrefix, running multiple instances of typescript-plugin-styled-components + * will not result in clashes caused by the class generation hash. + * + * @defaultValue `''` + */ + componentIdPrefix: string; } export interface CustomStyledIdentifiers { @@ -58,28 +66,28 @@ export interface CustomStyledIdentifiers { /** * Identifiers of `keyframes` function. - * + * * @defaultValue `['keyframes']` */ keyframes?: string[]; /** * Identifiers of `css` function. - * + * * @defaultValue `['css']` */ css?: string[]; /** * Identifiers of `createGlobalStyle` function. - * + * * @defaultValue `['createGlobalStyle']` */ createGlobalStyle?: string[]; /** * Identifiers of `extend` function. - * + * * @defaultValue `[]` */ extend?: string[];