diff --git a/.eslintrc b/.eslintrc index a552b8009..998f15edd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,20 +1,12 @@ { "parser": "babel-eslint", - "extends": [ - "airbnb", - "prettier", - "prettier/flowtype" - ], + "extends": ["airbnb", "prettier", "prettier/flowtype"], "globals": { "__SERVER__": true }, "plugins": ["flowtype", "flowtype-errors", "prettier"], "rules": { - "semi": 0, - "quotes": 0, - "comma-dangle": 0, "curly": [2, "multi-line"], - "arrow-parens": 0, "class-methods-use-this": 0, "symbol-description": 0, "no-unused-vars": [2, { "varsIgnorePattern": "^_+$" }], @@ -23,10 +15,15 @@ "no-else-return": 0, "react/sort-comp": 0, "react/jsx-filename-extension": 0, + "react/require-default-props": 0, "flowtype-errors/show-errors": 2, "no-prototype-builtins": 0, "no-duplicate-imports": 0, - "flowtype/require-valid-file-annotation": [2, "always", {"annotationStyle": "line"}], + "flowtype/require-valid-file-annotation": [ + 2, + "always", + { "annotationStyle": "line" } + ], "flowtype/boolean-style": [2, "boolean"], "flowtype/no-dupe-keys": 2, "flowtype/union-intersection-spacing": [2, "always"], @@ -37,7 +34,8 @@ "ForOfStatement", "LabeledStatement", "WithStatement" - ] + ], + "no-return-assign": 0 }, "env": { "jest": true, diff --git a/.flowconfig b/.flowconfig index 6699ccfb1..b3951bd22 100644 --- a/.flowconfig +++ b/.flowconfig @@ -22,4 +22,3 @@ [options] suppress_comment=.*\\$FlowFixMe suppress_comment=.*\\$FlowInvalidInputTest -unsafe.enable_getters_and_setters=true diff --git a/.gitignore b/.gitignore index 9717997c0..dc0613080 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __diff_output__ lib sandbox/node_modules *.log +test-results.json diff --git a/.travis.yml b/.travis.yml index 254a970e4..1982af072 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ addons: packages: - libnss3 - google-chrome-beta +branches: + only: master env: global: - GOOGLE_CHROME_BINARY="/usr/bin/google-chrome-beta" @@ -16,13 +18,15 @@ node_js: script: - node --version - yarn --version - - yarn run build + - yarn build + - yarn test # Tests have to run before Danger runs because # danger-plugin-jest references the test output - - yarn run test - - yarn run danger - - yarn run flow - - yarn run lint && yarn run typescript && yarn run tslint + # - yarn danger + - yarn flow + - yarn lint + - yarn typescript + - yarn tslint notifications: email: on_failure: change diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a108daba..868d79049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,29 @@ _The format is based on [Keep a Changelog](http://keepachangelog.com/) and this ## Unreleased -* Fix off-by-one error in insertRuleHelpers.js, by [@migueloller](https://github.com/migueloller) (see [#1749](https://github.com/styled-components/styled-components/pull/1749)) +- [placeholder] -* Add first-class support for objects: +## [v3.3.2] - 2018-06-04 + +* Allow non-plain objects as `ThemeProvider` themes, by @phyllisstein (see #1780) + +* Upgrade flow-bin to latest, by @halvves (see #1748) + +* Update various CI bits, by @probablyup (see #1769) + +* Reimplement SSR stream handling as a transform stream rather than a second-order readable stream, by @probablyup (see #1768) + +* Allow React Component as attr, by @valerybugakov (see #1751) + +* Added pointer events to valid attributes check, by @plankguy (see #1790) + +_v3.3.1 was skipped due to a bad deploy._ + +## [v3.3.0] - 2018-05-25 + +* Fix off-by-one error in insertRuleHelpers.js, by @migueloller (see #1749) + +* Add first-class support for objects, by @mxstbr (see #1732) ```JS const Component = styled.div({ @@ -16,15 +36,15 @@ _The format is based on [Keep a Changelog](http://keepachangelog.com/) and this }) ``` -- Fix typo in console warning about multiple instances, by [@lucianbuzzo] (see [#1730](https://github.com/styled-components/styled-components/pull/1730)) +- Fix typo in console warning about multiple instances, by @lucianbuzzo (see #1730) -- Make the multiple instance warning criteria a little more strict to avoid badgering people running unit tests ([see #1693](https://github.com/styled-components/styled-components/pull/1693)) +- Make the multiple instance warning criteria a little more strict to avoid badgering people running unit tests, by @probablyup (see #1693) -- Fix `React.createRef()` values for `innerRef` being ignored in React Native, by @simonbuchan (see [#1718](https://github.com/styled-components/styled-components/pull/1718)) +- Fix `React.createRef()` values for `innerRef` being ignored in React Native, by @simonbuchan (see #1718) - Hoist non-react static properties on wrapped classes, by @probablyup (see #1750) -- Support attributes prefixed by `x-` (see [#1753](https://github.com/styled-components/styled-components/pull/1753)) +- Support attributes prefixed by `x-`, by @mlecoq (see #1753) ## [v3.2.6] - 2018-04-17 @@ -474,7 +494,8 @@ _The format is based on [Keep a Changelog](http://keepachangelog.com/) and this * Fixed compatibility with other react-broadcast-based systems (like `react-router` v4) -[unreleased]: https://github.com/styled-components/styled-components/compare/v3.2.6...master +[unreleased]: https://github.com/styled-components/styled-components/compare/v3.3.0...master +[v3.3.0]: https://github.com/styled-components/styled-components/compare/v3.2.6...v3.3.0 [v3.2.6]: https://github.com/styled-components/styled-components/compare/v3.2.5...v3.2.6 [v3.2.5]: https://github.com/styled-components/styled-components/compare/v3.2.3...v3.2.5 [v3.2.3]: https://github.com/styled-components/styled-components/compare/v3.2.2...v3.2.3 diff --git a/appveyor.yml b/appveyor.yml index 8dce4d446..6eda6300b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,7 @@ version: "{build}" +branches: + only: + - master skip_tags: true build: off clone_depth: 1 @@ -7,12 +10,11 @@ matrix: fast_finish: true environment: matrix: - - nodejs_version: 7 - - nodejs_version: 6 + - nodejs_version: 8 init: - git config --global core.autocrlf input install: - - ps: Install-Product node $env:nodejs_version x64 + - ps: 'Install-Product node $env:nodejs_version x64' - set CI=true - yarn cache: @@ -20,6 +22,6 @@ cache: test_script: - node --version - yarn --version - - yarn run build - - yarn run flow - - yarn run lint && npm run test + - yarn build + - yarn flow + - yarn test diff --git a/dangerfile.js b/dangerfile.js index af431cce8..bd7c2ec7a 100644 --- a/dangerfile.js +++ b/dangerfile.js @@ -7,17 +7,27 @@ import jest from 'danger-plugin-jest' jest() -const jsModifiedFiles = danger.git.modified_files.filter(path => path.startsWith('src') && path.endsWith('js')) -const vendorModifiedFiles = danger.git.modified_files.filter(path => path.startsWith('src/vendor') && path.endsWith('js')) +const jsModifiedFiles = danger.git.modified_files.filter( + path => path.startsWith('src') && path.endsWith('js') +) +const vendorModifiedFiles = danger.git.modified_files.filter( + path => path.startsWith('src/vendor') && path.endsWith('js') +) -const hasAppChanges = jsModifiedFiles.filter(filepath => !filepath.endsWith('test.js')).length > 0 -const jsTestChanges = jsModifiedFiles.filter(filepath => filepath.endsWith('test.js')) +const hasAppChanges = + jsModifiedFiles.filter(filepath => !filepath.endsWith('test.js')).length > 0 +const jsTestChanges = jsModifiedFiles.filter(filepath => + filepath.endsWith('test.js') +) const hasTestChanges = jsTestChanges.length > 0 // Congrats, version bump up! danger.git.diffForFile('package.json').then(packageDiff => { if ( - packageDiff && packageDiff.version && packageDiff.version.before && packageDiff.version.after + packageDiff && + packageDiff.version && + packageDiff.version.before && + packageDiff.version.after ) { if (semver.lt(packageDiff.version.before, packageDiff.version.after)) { message(':tada: Version BUMP UP!') @@ -25,33 +35,46 @@ danger.git.diffForFile('package.json').then(packageDiff => { } }) -// Warn when there is a big PR -const bigPRThreshold = 500 -if (danger.github.pr.additions + danger.github.pr.deletions > bigPRThreshold) { - warn(':exclamation: Big PR') -} - // Fail if there are app changes without a CHANGELOG if (!danger.git.modified_files.includes('CHANGELOG.md') && hasAppChanges) { - const changelogLink = 'https://github.com/styled-components/styled-components/blob/master/CHANGELOG.md' - fail(`Please include a CHANGELOG entry. You can find it at CHANGELOG.md`) + const changelogLink = + 'https://github.com/styled-components/styled-components/blob/master/CHANGELOG.md' + fail( + `Please include a CHANGELOG entry. You can find it at CHANGELOG.md` + ) } // Warn if there are library changes, but not tests (excluding vendor) -const libraryOnlyFiles = jsModifiedFiles.filter(file => !vendorModifiedFiles.includes(file)) +const libraryOnlyFiles = jsModifiedFiles.filter( + file => !vendorModifiedFiles.includes(file) +) if (libraryOnlyFiles.length > 0 && !hasTestChanges) { - warn("There are library changes, but not tests. That's OK as long as you're refactoring existing code") + warn( + "There are library changes, but not tests. That's OK as long as you're refactoring existing code" + ) } // Warn if StyledComponent.js was edited but not StyledNativeComponent.js or viceversa -const hasStyledChanges = danger.git.modified_files.find(path => path.endsWith('StyledComponent.js')) !== null -const hasNativeStyledChanges = danger.git.modified_files.find(path => path.endsWith('StyledNativeComponent.js')) !== null +const hasStyledChanges = + danger.git.modified_files.find(path => + path.endsWith('StyledComponent.js') + ) !== null +const hasNativeStyledChanges = + danger.git.modified_files.find(path => + path.endsWith('StyledNativeComponent.js') + ) !== null if (hasStyledChanges && !hasNativeStyledChanges) { - warn("A change was made in StyledComponent.js that wasn't made in StyledNativeComponent.js or viceversa.") + warn( + "A change was made in StyledComponent.js that wasn't made in StyledNativeComponent.js or viceversa." + ) } // Changes to these files may need SemVer bumps -const semverBumpFiles = ['ThemeProvider.js', 'StyledComponent.js', 'StyledNativeComponent.js'] +const semverBumpFiles = [ + 'ThemeProvider.js', + 'StyledComponent.js', + 'StyledNativeComponent.js', +] semverBumpFiles.forEach(file => { if (jsModifiedFiles.includes(file)) { warn('Changes to #{file} might be SemVer major changes.') @@ -61,5 +84,7 @@ semverBumpFiles.forEach(file => { // Be careful of leaving testing shortcuts in the codebase jsTestChanges.forEach(file => { const content = fs.readFileSync(file).toString() - if (content.includes('it.only') || content.includes('describe.only')) { fail(`an \`only\` was left in tests (${file})`) } + if (content.includes('it.only') || content.includes('describe.only')) { + fail(`an \`only\` was left in tests (${file})`) + } }) diff --git a/flow-typed/lodash_v4.x.x.js b/flow-typed/lodash_v4.x.x.js deleted file mode 100644 index 90715876b..000000000 --- a/flow-typed/lodash_v4.x.x.js +++ /dev/null @@ -1,499 +0,0 @@ -// Manually modified version of flow-typed/lodash - -declare module 'lodash' { - declare type TemplateSettings = { - escape?: RegExp, - evaluate?: RegExp, - imports?: Object, - interpolate?: RegExp, - variable?: string, - }; - - declare type TruncateOptions = { - length?: number, - omission?: string, - separator?: RegExp|string, - }; - - declare type DebounceOptions = { - leading?: bool, - maxWait?: number, - trailing?: bool, - }; - - declare type ThrottleOptions = { - leading?: bool, - trailing?: bool, - }; - - declare type NestedArray = Array>; - - declare type OPredicate = - | ((value: A, key: string, object: O) => any) - | Object - | string; - declare type OIterateeWithResult = Object|string|((value: V, key: string, object: O) => R); - declare type OIteratee = OIterateeWithResult; - - declare type Predicate = - | ((value: T, index: number, array: Array) => any) - | Object - | string; - declare type _Iteratee = (item: T, index: number, array: ?Array) => mixed; - declare type Iteratee = _Iteratee|Object|string; - declare type Iteratee2 = ((item: T, index: number, array: ?Array) => U)|Object|string; - declare type FlatMapIteratee = ((item: T, index: number, array: ?Array) => Array)|Object|string; - declare type Comparator = (item: T, item2: T) => bool; - - declare type MapIterator1 = (item: T) => U; - declare type MapIterator2 = (item: T, index: number) => U; - declare type MapIterator3 = (item: T, index: number, array: Array) => U; - declare type MapIterator = MapIterator1|MapIterator2|MapIterator3; - - declare class Lodash { - // Array - chunk(array: ?Array, size?: number): Array>; - compact(array: Array): Array; - concat(base: Array, ...elements: Array): Array; - difference(array: ?Array, values?: Array): Array; - differenceBy(array: ?Array, values: Array, iteratee: Iteratee): T[]; - differenceWith(array: T[], values: T[], comparator?: Comparator): T[]; - drop(array: ?Array, n?: number): Array; - dropRight(array: ?Array, n?: number): Array; - dropRightWhile(array: ?Array, predicate?: Predicate): Array; - dropWhile(array: ?Array, predicate?: Predicate): Array; - fill(array: ?Array, value: U, start?: number, end?: number): Array; - findIndex(array: ?Array, predicate?: Predicate): number; - findLastIndex(array: ?Array, predicate?: Predicate): number; - // alias of _.head - first(array: ?Array): T; - flatten(array: Array|X>): Array; - flattenDeep(array: any[]): Array; - flattenDepth(array: any[], depth?: number): any[]; - fromPairs(pairs: Array): Object; - head(array: ?Array): T; - indexOf(array: ?Array, value: T, fromIndex?: number): number; - initial(array: ?Array): Array; - intersection(...arrays: Array>): Array; - //Workaround until (...parameter: T, parameter2: U) works - intersectionBy(a1: Array, iteratee?: Iteratee): Array; - intersectionBy(a1: Array, a2: Array, iteratee?: Iteratee): Array; - intersectionBy(a1: Array, a2: Array, a3: Array, iteratee?: Iteratee): Array; - intersectionBy(a1: Array, a2: Array, a3: Array, a4: Array, iteratee?: Iteratee): Array; - //Workaround until (...parameter: T, parameter2: U) works - intersectionWith(a1: Array, comparator: Comparator): Array; - intersectionWith(a1: Array, a2: Array, comparator: Comparator): Array; - intersectionWith(a1: Array, a2: Array, a3: Array, comparator: Comparator): Array; - intersectionWith(a1: Array, a2: Array, a3: Array, a4: Array, comparator: Comparator): Array; - join(array: ?Array, separator?: string): string; - last(array: ?Array): T; - lastIndexOf(array: ?Array, value: T, fromIndex?: number): number; - nth(array: T[], n?: number): T; - pull(array: ?Array, ...values?: Array): Array; - pullAll(array: ?Array, values: Array): Array; - pullAllBy(array: ?Array, values: Array, iteratee?: Iteratee): Array; - pullAllWith(array?: T[], values: T[], comparator?: Function): T[]; - pullAt(array: ?Array, ...indexed?: Array): Array; - pullAt(array: ?Array, indexed?: Array): Array; - remove(array: ?Array, predicate?: Predicate): Array; - reverse(array: ?Array): Array; - slice(array: ?Array, start?: number, end?: number): Array; - sortedIndex(array: ?Array, value: T): number; - sortedIndexBy(array: ?Array, value: T, iteratee?: Iteratee): number; - sortedIndexOf(array: ?Array, value: T): number; - sortedLastIndex(array: ?Array, value: T): number; - sortedLastIndexBy(array: ?Array, value: T, iteratee?: Iteratee): number; - sortedLastIndexOf(array: ?Array, value: T): number; - sortedUniq(array: ?Array): Array; - sortedUniqBy(array: ?Array, iteratee?: (value: T) => mixed): Array; - tail(array: ?Array): Array; - take(array: ?Array, n?: number): Array; - takeRight(array: ?Array, n?: number): Array; - takeRightWhile(array: ?Array, predicate?: Predicate): Array; - takeWhile(array: ?Array, predicate?: Predicate): Array; - union(array?: Array): Array; - unionBy(array?: Array, iteratee?: Iteratee): Array; - //Workaround until (...parameter: T, parameter2: U) works - unionWith(a1: Array, comparator?: Comparator): Array; - unionWith(a1: Array, a2: Array, comparator?: Comparator): Array; - unionWith(a1: Array, a2: Array, a3: Array, comparator?: Comparator): Array; - unionWith(a1: Array, a2: Array, a3: Array, a4: Array, comparator?: Comparator): Array; - uniq(array: ?Array): Array; - uniqBy(array: ?Array, iteratee?: Iteratee): Array; - uniqWith(array: ?Array, comparator?: Comparator): Array; - unzip(array: ?Array): Array; - unzipWith(array: ?Array, iteratee?: Iteratee): Array; - without(array: ?Array, ...values?: Array): Array; - xor(...array: Array>): Array; - //Workaround until (...parameter: T, parameter2: U) works - xorBy(a1: Array, iteratee?: Iteratee): Array; - xorBy(a1: Array, a2: Array, iteratee?: Iteratee): Array; - xorBy(a1: Array, a2: Array, a3: Array, iteratee?: Iteratee): Array; - xorBy(a1: Array, a2: Array, a3: Array, a4: Array, iteratee?: Iteratee): Array; - //Workaround until (...parameter: T, parameter2: U) works - xorWith(a1: Array, comparator?: Comparator): Array; - xorWith(a1: Array, a2: Array, comparator?: Comparator): Array; - xorWith(a1: Array, a2: Array, a3: Array, comparator?: Comparator): Array; - xorWith(a1: Array, a2: Array, a3: Array, a4: Array, comparator?: Comparator): Array; - zip(a1: A[], a2: B[]): Array<[A, B]>; - zip(a1: A[], a2: B[], a3: C[]): Array<[A, B, C]>; - zip(a1: A[], a2: B[], a3: C[], a4: D[]): Array<[A, B, C, D]>; - zip(a1: A[], a2: B[], a3: C[], a4: D[], a5: E[]): Array<[A, B, C, D, E]>; - - zipObject(props?: Array, values?: Array): Object; - zipObjectDeep(props?: any[], values?: any): Object; - //Workaround until (...parameter: T, parameter2: U) works - zipWith(a1: NestedArray, iteratee?: Iteratee): Array; - zipWith(a1: NestedArray, a2: NestedArray, iteratee?: Iteratee): Array; - zipWith(a1: NestedArray, a2: NestedArray, a3: NestedArray, iteratee?: Iteratee): Array; - zipWith(a1: NestedArray, a2: NestedArray, a3: NestedArray, a4: NestedArray, iteratee?: Iteratee): Array; - - // Collection - countBy(array: ?Array, iteratee?: Iteratee): Object; - countBy(object: T, iteratee?: OIteratee): Object; - // alias of _.forEach - each(array: ?Array, iteratee?: Iteratee): Array; - each(object: T, iteratee?: OIteratee): T; - // alias of _.forEachRight - eachRight(array: ?Array, iteratee?: Iteratee): Array; - eachRight(object: T, iteratee?: OIteratee): T; - every(array: ?Array, iteratee?: Iteratee): bool; - every(object: T, iteratee?: OIteratee): bool; - filter(array: ?Array, predicate?: Predicate): Array; - filter(object: T, predicate?: OPredicate): Array; - find(array: ?Array, predicate?: Predicate): T; - find(object: T, predicate?: OPredicate): V; - findLast(array: ?Array, predicate?: Predicate): T; - findLast(object: T, predicate?: OPredicate): V; - flatMap(array: ?Array, iteratee?: FlatMapIteratee): Array; - flatMapDeep(array: ?Array, iteratee?: FlatMapIteratee): Array; - flatMapDepth(array: ?Array, iteratee?: FlatMapIteratee, depth?: number): Array; - forEach(array: ?Array, iteratee?: Iteratee): Array; - forEach(object: T, iteratee?: OIteratee): T; - forEachRight(array: ?Array, iteratee?: Iteratee): Array; - forEachRight(object: T, iteratee?: OIteratee): T; - groupBy(array: ?Array, iteratee?: Iteratee): Object; - groupBy(object: T, iteratee?: OIteratee): Object; - includes(array: ?Array, value: T, fromIndex?: number): bool; - includes(object: T, value: any, fromIndex?: number): bool; - includes(str: string, value: string, fromIndex?: number): bool; - invokeMap(array: ?Array, path: ((value: T) => Array|string)|Array|string, ...args?: Array): Array; - invokeMap(object: T, path: ((value: any) => Array|string)|Array|string, ...args?: Array): Array; - keyBy(array: ?Array, iteratee?: Iteratee2): {[key: V]: T}; - keyBy(object: T, iteratee?: OIteratee): Object; - map(array: ?Array, iteratee?: MapIterator): Array; - map(object: ?T, iteratee?: OIterateeWithResult): Array; - map(str: ?string, iteratee?: (char: string, index: number, str: string) => any): string; - orderBy(array: ?Array, iteratees?: Array>|string, orders?: Array<'asc'|'desc'>|string): Array; - orderBy(object: T, iteratees?: Array>|string, orders?: Array<'asc'|'desc'>|string): Array; - partition(array: ?Array, predicate?: Predicate): NestedArray; - partition(object: T, predicate?: OPredicate): NestedArray; - reduce(array: ?Array, iteratee?: (accumulator: U, value: T, index: number, array: ?Array) => U, accumulator?: U): U; - reduce(object: T, iteratee?: (accumulator: U, value: any, key: string, object: T) => U, accumulator?: U): U; - reduceRight(array: ?Array, iteratee?: (accumulator: U, value: T, index: number, array: ?Array) => U, accumulator?: U): U; - reduceRight(object: T, iteratee?: (accumulator: U, value: any, key: string, object: T) => U, accumulator?: U): U; - reject(array: ?Array, predicate?: Predicate): Array; - reject(object: T, predicate?: OPredicate): Array; - sample(array: ?Array): T; - sample(object: T): V; - sampleSize(array: ?Array, n?: number): Array; - sampleSize(object: T, n?: number): Array; - shuffle(array: ?Array): Array; - shuffle(object: T): Array; - size(collection: Array|Object): number; - some(array: ?Array, predicate?: Predicate): bool; - some(object?: ?T, predicate?: OPredicate): bool; - sortBy(array: ?Array, ...iteratees?: Array>): Array; - sortBy(array: ?Array, iteratees?: Array>): Array; - sortBy(object: T, ...iteratees?: Array>): Array; - sortBy(object: T, iteratees?: Array>): Array; - - // Date - now(): number; - - // Function - after(n: number, fn: Function): Function; - ary(func: Function, n?: number): Function; - before(n: number, fn: Function): Function; - bind(func: Function, thisArg: any, ...partials: Array): Function; - bindKey(obj: Object, key: string, ...partials: Array): Function; - curry(func: Function, arity?: number): Function; - curryRight(func: Function, arity?: number): Function; - debounce(func: Function, wait?: number, options?: DebounceOptions): Function; - defer(func: Function, ...args?: Array): number; - delay(func: Function, wait: number, ...args?: Array): number; - flip(func: Function): Function; - memoize(func: Function, resolver?: Function): Function; - negate(predicate: Function): Function; - once(func: Function): Function; - overArgs(func: Function, ...transforms: Array): Function; - overArgs(func: Function, transforms: Array): Function; - partial(func: Function, ...partials: any[]): Function; - partialRight(func: Function, ...partials: Array): Function; - partialRight(func: Function, partials: Array): Function; - rearg(func: Function, ...indexes: Array): Function; - rearg(func: Function, indexes: Array): Function; - rest(func: Function, start?: number): Function; - spread(func: Function): Function; - throttle(func: Function, wait?: number, options?: ThrottleOptions): Function; - unary(func: Function): Function; - wrap(value: any, wrapper: Function): Function; - - // Lang - castArray(value: *): any[]; - clone(value: T): T; - cloneDeep(value: T): T; - cloneDeepWith(value: T, customizer?: ?(value: T, key: number|string, object: T, stack: any) => U): U; - cloneWith(value: T, customizer?: ?(value: T, key: number|string, object: T, stack: any) => U): U; - conformsTo(source: T, predicates: T&{[key:string]:(x:any)=>boolean}): boolean; - eq(value: any, other: any): bool; - gt(value: any, other: any): bool; - gte(value: any, other: any): bool; - isArguments(value: any): bool; - isArray(value: any): bool; - isArrayBuffer(value: any): bool; - isArrayLike(value: any): bool; - isArrayLikeObject(value: any): bool; - isBoolean(value: any): bool; - isBuffer(value: any): bool; - isDate(value: any): bool; - isElement(value: any): bool; - isEmpty(value: any): bool; - isEqual(value: any, other: any): bool; - isEqualWith(value: T, other: U, customizer?: (objValue: any, otherValue: any, key: number|string, object: T, other: U, stack: any) => bool|void): bool; - isError(value: any): bool; - isFinite(value: any): bool; - isFunction(value: Function): true; - isFunction(value: number|string|void|null|Object): false; - isInteger(value: any): bool; - isLength(value: any): bool; - isMap(value: any): bool; - isMatch(object?: ?Object, source: Object): bool; - isMatchWith(object: T, source: U, customizer?: (objValue: any, srcValue: any, key: number|string, object: T, source: U) => bool|void): bool; - isNaN(value: any): bool; - isNative(value: any): bool; - isNil(value: any): bool; - isNull(value: any): bool; - isNumber(value: any): bool; - isObject(value: any): bool; - isObjectLike(value: any): bool; - isPlainObject(value: any): bool; - isRegExp(value: any): bool; - isSafeInteger(value: any): bool; - isSet(value: any): bool; - isString(value: any): bool; - isSymbol(value: any): bool; - isTypedArray(value: any): bool; - isUndefined(value: any): bool; - isWeakMap(value: any): bool; - isWeakSet(value: any): bool; - lt(value: any, other: any): bool; - lte(value: any, other: any): bool; - toArray(value: any): Array; - toFinite(value: any): number; - toInteger(value: any): number; - toLength(value: any): number; - toNumber(value: any): number; - toPlainObject(value: any): Object; - toSafeInteger(value: any): number; - toString(value: any): string; - - // Math - add(augend: number, addend: number): number; - ceil(number: number, precision?: number): number; - divide(dividend: number, divisor: number): number; - floor(number: number, precision?: number): number; - max(array: ?Array): T; - maxBy(array: ?Array, iteratee?: Iteratee): T; - mean(array: Array<*>): number; - meanBy(array: Array, iteratee?: Iteratee): number; - min(array: ?Array): T; - minBy(array: ?Array, iteratee?: Iteratee): T; - multiply(multiplier: number, multiplicand: number): number; - round(number: number, precision?: number): number; - subtract(minuend: number, subtrahend: number): number; - sum(array: Array<*>): number; - sumBy(array: Array, iteratee?: Iteratee): number; - - // number - clamp(number: number, lower?: number, upper: number): number; - inRange(number: number, start?: number, end: number): bool; - random(lower?: number, upper?: number, floating?: bool): number; - - // Object - assign(object?: ?Object, ...sources?: Array): Object; - assignIn(a: A, b: B): A & B; - assignIn(a: A, b: B, c: C): A & B & C; - assignIn(a: A, b: B, c: C, d: D): A & B & C & D; - assignIn(a: A, b: B, c: C, d: D, e: E): A & B & C & D & E; - assignInWith(object: T, s1: A, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object; - assignInWith(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object; - assignInWith(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object; - assignInWith(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object; - assignWith(object: T, s1: A, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object; - assignWith(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object; - assignWith(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object; - assignWith(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object; - at(object?: ?Object, ...paths: Array): Array; - at(object?: ?Object, paths: Array): Array; - create(prototype: T, properties?: Object): $Supertype; - defaults(object?: ?Object, ...sources?: Array): Object; - defaultsDeep(object?: ?Object, ...sources?: Array): Object; - // alias for _.toPairs - entries(object?: ?Object): NestedArray; - // alias for _.toPairsIn - entriesIn(object?: ?Object): NestedArray; - // alias for _.assignIn - extend(a: A, b: B): A & B; - extend(a: A, b: B, c: C): A & B & C; - extend(a: A, b: B, c: C, d: D): A & B & C & D; - extend(a: A, b: B, c: C, d: D, e: E): A & B & C & D & E; - // alias for _.assignInWith - extendWith(object: T, s1: A, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object; - extendWith(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object; - extendWith(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object; - extendWith(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object; - findKey(object?: ?T, predicate?: OPredicate): string|void; - findLastKey(object?: ?T, predicate?: OPredicate): string|void; - forIn(object?: ?Object, iteratee?: OIteratee<*>): Object; - forInRight(object?: ?Object, iteratee?: OIteratee<*>): Object; - forOwn(object?: ?Object, iteratee?: OIteratee<*>): Object; - forOwnRight(object?: ?Object, iteratee?: OIteratee<*>): Object; - functions(object?: ?Object): Array; - functionsIn(object?: ?Object): Array; - get(object?: ?Object, path?: ?Array|string, defaultValue?: any): any; - has(object?: ?Object, path?: ?Array|string): bool; - hasIn(object?: ?Object, path?: ?Array|string): bool; - invert(object?: ?Object, multiVal?: bool): Object; - invertBy(object: ?Object, iteratee?: Function): Object; - invoke(object?: ?Object, path?: ?Array|string, ...args?: Array): any; - keys(object?: ?Object): Array; - keysIn(object?: ?Object): Array; - mapKeys(object?: ?Object, iteratee?: OIteratee<*>): Object; - mapValues(object?: ?Object, iteratee?: OIteratee<*>): Object; - merge(object?: ?Object, ...sources?: Array): Object; - mergeWith(object: T, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A) => any|void): Object; - mergeWith(object: T, s1: A, s2: B, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B) => any|void): Object; - mergeWith(object: T, s1: A, s2: B, s3: C, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C) => any|void): Object; - mergeWith(object: T, s1: A, s2: B, s3: C, s4: D, customizer?: (objValue: any, srcValue: any, key: string, object: T, source: A|B|C|D) => any|void): Object; - omit(object?: ?Object, ...props: Array): Object; - omit(object?: ?Object, props: Array): Object; - omitBy(object?: ?T, predicate?: OPredicate): Object; - pick(object?: ?Object, ...props: Array): Object; - pick(object?: ?Object, props: Array): Object; - pickBy(object?: ?T, predicate?: OPredicate): Object; - result(object?: ?Object, path?: ?Array|string, defaultValue?: any): any; - set(object?: ?Object, path?: ?Array|string, value: any): Object; - setWith(object: T, path?: ?Array|string, value: any, customizer?: (nsValue: any, key: string, nsObject: T) => any): Object; - toPairs(object?: ?Object|Array<*>): NestedArray; - toPairsIn(object?: ?Object): NestedArray; - transform(collection: Object|Array, iteratee?: OIteratee<*>, accumulator?: any): any; - unset(object?: ?Object, path?: ?Array|string): bool; - update(object: Object, path: string[]|string, updater: Function): Object; - updateWith(object: Object, path: string[]|string, updater: Function, customizer?: Function): Object; - values(object?: ?Object): Array; - valuesIn(object?: ?Object): Array; - - // Seq - // harder to read, but this is _() - (value: any): any; - chain(value: T): any; - tap(value: T, interceptor: (value:T)=>any): T; - thru(value: T1, interceptor: (value:T1)=>T2): T2; - // TODO: _.prototype.* - - // String - camelCase(string?: ?string): string; - capitalize(string?: string): string; - deburr(string?: string): string; - endsWith(string?: string, target?: string, position?: number): bool; - escape(string?: string): string; - escapeRegExp(string?: string): string; - kebabCase(string?: string): string; - lowerCase(string?: string): string; - lowerFirst(string?: string): string; - pad(string?: string, length?: number, chars?: string): string; - padEnd(string?: string, length?: number, chars?: string): string; - padStart(string?: string, length?: number, chars?: string): string; - parseInt(string: string, radix?: number): number; - repeat(string?: string, n?: number): string; - replace(string?: string, pattern: RegExp|string, replacement: ((string: string) => string)|string): string; - snakeCase(string?: string): string; - split(string?: string, separator: RegExp|string, limit?: number): Array; - startCase(string?: string): string; - startsWith(string?: string, target?: string, position?: number): bool; - template(string?: string, options?: TemplateSettings): Function; - toLower(string?: string): string; - toUpper(string?: string): string; - trim(string?: string, chars?: string): string; - trimEnd(string?: string, chars?: string): string; - trimStart(string?: string, chars?: string): string; - truncate(string?: string, options?: TruncateOptions): string; - unescape(string?: string): string; - upperCase(string?: string): string; - upperFirst(string?: string): string; - words(string?: string, pattern?: RegExp|string): Array; - - // Util - attempt(func: Function): any; - bindAll(object?: ?Object, methodNames: Array): Object; - bindAll(object?: ?Object, ...methodNames: Array): Object; - cond(pairs: NestedArray): Function; - conforms(source: Object): Function; - constant(value: T): () => T; - defaultTo(value: T1, default: T2): T1; - // NaN is a number instead of its own type, otherwise it would behave like null/void - defaultTo(value: T1, default: T2): T1|T2; - defaultTo(value: T1, default: T2): T2; - flow(...funcs?: Array): Function; - flow(funcs?: Array): Function; - flowRight(...funcs?: Array): Function; - flowRight(funcs?: Array): Function; - identity(value: T): T; - iteratee(func?: any): Function; - matches(source: Object): Function; - matchesProperty(path?: ?Array|string, srcValue: any): Function; - method(path?: ?Array|string, ...args?: Array): Function; - methodOf(object?: ?Object, ...args?: Array): Function; - mixin(object?: T, source: Object, options?: { chain: bool }): T; - noConflict(): Lodash; - noop(): void; - nthArg(n?: number): Function; - over(...iteratees: Array): Function; - over(iteratees: Array): Function; - overEvery(...predicates: Array): Function; - overEvery(predicates: Array): Function; - overSome(...predicates: Array): Function; - overSome(predicates: Array): Function; - property(path?: ?Array|string): Function; - propertyOf(object?: ?Object): Function; - range(start: number, end: number, step?: number): Array; - range(end: number, step?: number): Array; - rangeRight(start: number, end: number, step?: number): Array; - rangeRight(end: number, step?: number): Array; - runInContext(context?: Object): Function; - - stubArray(): Array<*>; - stubFalse(): false; - stubObject(): {}; - stubString(): ''; - stubTrue(): true; - times(n: number, iteratee?: Function): Function; - toPath(value: any): Array; - uniqueId(prefix?: string): string; - - // Properties - VERSION: string; - templateSettings: TemplateSettings; - } - - declare var exports: Lodash; -} - -declare module 'lodash/isPlainObject' { - declare module.exports: (value: any) => bool; -} - -declare module 'lodash/isFunction' { - declare module.exports: (value: Function) => boolean; -} diff --git a/flow-typed/webpack-hotmodule.js b/flow-typed/webpack-hotmodule.js new file mode 100644 index 000000000..1fa99551d --- /dev/null +++ b/flow-typed/webpack-hotmodule.js @@ -0,0 +1,31 @@ +// see: https://github.com/flowtype/flow-typed/issues/165 + +declare type ModuleHotStatus = + | 'idle' // The process is waiting for a call to check (see below) + | 'check' // The process is checking for updates + | 'prepare' // The process is getting ready for the update (e.g. downloading the updated module) + | 'ready' // The update is prepared and available + | 'dispose' // The process is calling the dispose handlers on the modules that will be replaced + | 'apply' // The process is calling the accept handlers and re-executing self-accepted modules + | 'abort' // An update was aborted, but the system is still in it's previous state + | 'fail' // An update has thrown an exception and the system's state has been compromised + ; + +declare type ModuleHotStatusHandler = (status: ModuleHotStatus) => any + +declare interface ModuleHot { + data: any; + accept(paths?: string | Array, callback?: () => any): void; + decline(paths?: string | Array): void; + dispose(callback: (data?: mixed) => any): void; + addDisposeHandler(callback: (data: mixed) => any): void; + status(): ModuleHotStatus; + check(autoApply: boolean | Object): Promise; // TODO + apply(options: Object): Promise; // TODO + addStatusHandler(callback: ModuleHotStatusHandler): void; + removeStatusHandler(callback: ModuleHotStatusHandler): void; +}; + +declare var module: { + hot?: ModuleHot, +}; diff --git a/integration-test/example.test.js b/integration-test/example.test.js index 1c64068cb..c4d747fac 100644 --- a/integration-test/example.test.js +++ b/integration-test/example.test.js @@ -53,10 +53,10 @@ describe('example page', () => { }) page = await browser.newPage() page.on('request', req => { - if (urlWhitelist.find(regexp => req.url.match(regexp))) { + if (urlWhitelist.find(regexp => req.url().match(regexp))) { req.continue() } else { - throw new Error(req.url) + throw new Error(req.url()) req.abort() } }) diff --git a/package.json b/package.json index b7b13711c..e0f7930d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "styled-components", - "version": "3.2.6", + "version": "3.3.2", "description": "Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress 💅", "typings": "typings/styled-components.d.ts", "main": "dist/styled-components.cjs.js", @@ -25,7 +25,7 @@ "test:primitives:watch": "npm run test:primitives -- --watch", "test:integration": "jest -c .jest.integration.json --runInBand --forceExit", "test:size": "bundlesize", - "danger": "cross-env NODE_ENV=test danger run -- --verbose", + "danger": "cross-env NODE_ENV=test danger ci -v", "flow": "flow check", "flow:watch": "flow-watch", "format": "eslint ./**/*.js --fix", @@ -88,7 +88,7 @@ "@types/react-native": "^0.55.7", "babel-cli": "^6.22.2", "babel-core": "^6.17.0", - "babel-eslint": "^7.1.1", + "babel-eslint": "^8.2.3", "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-external-helpers": "^6.22.0", "babel-plugin-flow-react-proptypes": "^2.1.3", @@ -97,35 +97,35 @@ "babel-plugin-transform-react-remove-prop-types": "^0.4.12", "babel-preset-env": "^1.4.0", "babel-preset-react": "^6.22.0", - "bundlesize": "^0.13.2", + "bundlesize": "^0.17.0", "chokidar": "^1.6.0", "cross-env": "^5.1.3", - "danger": "^2.0.0", + "danger": "^3.7.14", "danger-plugin-jest": "^1.1.0", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", - "eslint": "^3.15.0", - "eslint-config-airbnb": "^13.0.0", + "eslint": "^4.19.1", + "eslint-config-airbnb": "^16.1.0", "eslint-config-prettier": "^2.9.0", - "eslint-plugin-flowtype": "^2.40.1", - "eslint-plugin-flowtype-errors": "^2.0.1", - "eslint-plugin-import": "^2.2.0", - "eslint-plugin-jsx-a11y": "^2.0.2", - "eslint-plugin-prettier": "^2.4.0", - "eslint-plugin-react": "^6.8.0", + "eslint-plugin-flowtype": "^2.48.0", + "eslint-plugin-flowtype-errors": "^3.6.0", + "eslint-plugin-import": "^2.12.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-prettier": "^2.6.0", + "eslint-plugin-react": "^7.8.2", "express": "^4.14.1", - "flow-bin": "^0.47.0", + "flow-bin": "^0.73.0", "flow-watch": "^1.1.1", "husky": "^0.14.3", - "jest": "^20.0.4", - "jest-image-snapshot": "2.2.0", - "jest-styled-components": "^5.0.0", + "jest": "^23.0.0", + "jest-image-snapshot": "^2.4.1", + "jest-styled-components": "^5.0.1", "jsdom": "^9.10.0", "lint-staged": "^6.0.0", "node-watch": "^0.4.1", "npm-run-all": "^4.1.2", "prettier": "1.9.2", - "puppeteer": "^0.13.0", + "puppeteer": "^1.4.0", "raf": "^3.4.0", "react": "^16.3.0", "react-dom": "^16.0.0", @@ -155,6 +155,7 @@ "globals": { "__DEV__": true }, + "testURL": "http://localhost", "clearMocks": true, "roots": [ "/src/" @@ -164,6 +165,7 @@ "/src/test/enzymeSetup.js", "/src/test/globals.js" ], + "setupTestFrameworkScriptFile": "/test-utils/setupTestFramework.js", "testPathIgnorePatterns": [ "/src/native", "/src/primitives" @@ -178,15 +180,15 @@ "bundlesize": [ { "path": "./dist/styled-components.min.js", - "threshold": "16kB" + "maxSize": "16.5kB" }, { "path": "./dist/styled-components.cjs.min.js", - "threshold": "11.5kB" + "maxSize": "11.5kB" }, { "path": "./dist/styled-components.es.min.js", - "threshold": "11.5kB" + "maxSize": "11.5kB" } ], "collective": { diff --git a/src/hoc/withTheme.js b/src/hoc/withTheme.js index 906cb1ca9..7eab8d959 100644 --- a/src/hoc/withTheme.js +++ b/src/hoc/withTheme.js @@ -1,7 +1,5 @@ // @flow -/* globals ReactClass */ - -import React from 'react' +import React, { type ComponentType } from 'react' import PropTypes from 'prop-types' import hoistStatics from 'hoist-non-react-statics' import { @@ -12,18 +10,20 @@ import { import _isStyledComponent from '../utils/isStyledComponent' import determineTheme from '../utils/determineTheme' -const wrapWithTheme = (Component: ReactClass) => { +const wrapWithTheme = (Component: ComponentType) => { const componentName = Component.displayName || Component.name || 'Component' const isStatelessFunctionalComponent = typeof Component === 'function' && + // $FlowFixMe TODO: flow for prototype !(Component.prototype && 'isReactComponent' in Component.prototype) // NOTE: We can't pass a ref to a stateless functional component const shouldSetInnerRef = _isStyledComponent(Component) || isStatelessFunctionalComponent - class WithTheme extends React.Component { + class WithTheme extends React.Component<*, *> { static displayName = `WithTheme(${componentName})` + static defaultProps: Object // NOTE: This is so that isStyledComponent passes for the innerRef unwrapping static styledComponentId = 'withTheme' diff --git a/src/models/ServerStyleSheet.js b/src/models/ServerStyleSheet.js index 32c00b5e3..043980c99 100644 --- a/src/models/ServerStyleSheet.js +++ b/src/models/ServerStyleSheet.js @@ -76,36 +76,35 @@ export default class ServerStyleSheet { let instanceTagIndex = 0 const streamAttr = `${SC_STREAM_ATTR}="true"` - const ourStream = new stream.Readable() - // $FlowFixMe - ourStream._read = () => {} - - readableStream.on('data', chunk => { - const { tags } = instance - let html = '' - - /* retrieve html for each new style tag */ - for (; instanceTagIndex < tags.length; instanceTagIndex += 1) { - const tag = tags[instanceTagIndex] - html += tag.toHTML(streamAttr) - } - - /* force our StyleSheets to emit entirely new tags */ - instance.sealAllTags() - /* prepend style html to chunk */ - ourStream.push(html + chunk) - }) - readableStream.on('end', () => { - this.complete() - ourStream.push(null) + const transformer = new stream.Transform({ + transform: function appendStyleChunks(chunk, /* encoding */ _, callback) { + const { tags } = instance + let html = '' + + /* retrieve html for each new style tag */ + for (; instanceTagIndex < tags.length; instanceTagIndex += 1) { + const tag = tags[instanceTagIndex] + html += tag.toHTML(streamAttr) + } + + /* force our StyleSheets to emit entirely new tags */ + instance.sealAllTags() + + /* prepend style html to chunk */ + this.push(html + chunk) + callback() + }, }) + readableStream.on('end', () => this.complete()) readableStream.on('error', err => { this.complete() - ourStream.emit('error', err) + + // forward the error to the transform stream + transformer.emit('error', err) }) - return ourStream + return readableStream.pipe(transformer) } } diff --git a/src/models/StyleSheet.js b/src/models/StyleSheet.js index debcb5fc6..4d9977b02 100644 --- a/src/models/StyleSheet.js +++ b/src/models/StyleSheet.js @@ -49,7 +49,8 @@ class StyleSheet { target: ?HTMLElement = IS_BROWSER ? document.head : null, forceServer?: boolean = false ) { - this.id = sheetRunningId += 1 + sheetRunningId += 1 + this.id = sheetRunningId this.sealed = false this.forceServer = forceServer this.target = forceServer ? null : target @@ -309,7 +310,7 @@ class StyleSheet { return this.tags.map(tag => tag.toHTML()).join('') } - toReactElements() { + toReactElements(): Array<*> { const { id } = this return this.tags.map((tag, i) => { diff --git a/src/models/StyleSheetManager.js b/src/models/StyleSheetManager.js index 1edd03e24..3b9b90ded 100644 --- a/src/models/StyleSheetManager.js +++ b/src/models/StyleSheetManager.js @@ -15,12 +15,13 @@ The StyleSheetManager expects a valid target or sheet prop! `.trim() : '' -class StyleSheetManager extends Component { +type Props = { + sheet?: StyleSheet | null, + target?: HTMLElement | null, +} + +class StyleSheetManager extends Component { sheetInstance: StyleSheet - props: { - sheet?: StyleSheet | null, - target?: HTMLElement | null, - } getChildContext() { return { [CONTEXT_KEY]: this.sheetInstance } diff --git a/src/models/StyleTags.js b/src/models/StyleTags.js index 595b95e8e..06b463a4b 100644 --- a/src/models/StyleTags.js +++ b/src/models/StyleTags.js @@ -2,7 +2,7 @@ /* eslint-disable flowtype/object-type-delimiter */ /* eslint-disable react/prop-types */ -import React from 'react' +import React, { type Element } from 'react' import { IS_BROWSER, DISABLE_SPEEDY, SC_ATTR } from '../constants' import { type ExtractedComp } from '../utils/extractCompsFromCSS' import { splitByRules } from '../utils/stringifyRules' @@ -39,7 +39,7 @@ export interface Tag { removeRules(id: string): void; css(): string; toHTML(additionalAttrs: ?string): string; - toElement(): React.Element<*>; + toElement(): Element<*>; clone(): Tag; } @@ -151,7 +151,7 @@ const makeSpeedyTag = ( el: HTMLStyleElement, getImportRuleTag: ?() => Tag ): Tag => { - const names: Names = Object.create(null) + const names: Names = (Object.create(null): Object) const markers = Object.create(null) const sizes: number[] = [] @@ -165,10 +165,11 @@ const makeSpeedyTag = ( return prev } - const marker = (markers[id] = sizes.length) + markers[id] = sizes.length sizes.push(0) resetIdNames(names, id) - return marker + + return markers[id] } const insertRules = (id, cssRules, name) => { @@ -257,7 +258,7 @@ const makeBrowserTag = ( el: HTMLStyleElement, getImportRuleTag: ?() => Tag ): Tag => { - const names = Object.create(null) + const names = (Object.create(null): Object) const markers = Object.create(null) const extractImport = getImportRuleTag !== undefined @@ -272,10 +273,11 @@ const makeBrowserTag = ( return prev } - const marker = (markers[id] = makeTextNode(id)) - el.appendChild(marker) + markers[id] = makeTextNode(id) + el.appendChild(markers[id]) names[id] = Object.create(null) - return marker + + return markers[id] } const insertRules = (id, cssRules, name) => { @@ -344,7 +346,8 @@ const makeBrowserTag = ( } const makeServerTagInternal = (namesArg, markersArg): Tag<[string]> => { - const names = namesArg === undefined ? Object.create(null) : namesArg + const names = + namesArg === undefined ? (Object.create(null): Object) : namesArg const markers = markersArg === undefined ? Object.create(null) : markersArg const insertMarker = id => { diff --git a/src/models/StyledComponent.js b/src/models/StyledComponent.js index 53bc2f55b..2d04ddf53 100644 --- a/src/models/StyledComponent.js +++ b/src/models/StyledComponent.js @@ -12,6 +12,7 @@ import getComponentName from '../utils/getComponentName' import isStyledComponent from '../utils/isStyledComponent' import isTag from '../utils/isTag' import validAttr from '../utils/validAttr' +import hasInInheritanceChain from '../utils/hasInInheritanceChain' import ServerStyleSheet from './ServerStyleSheet' import StyleSheet from './StyleSheet' import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider' @@ -23,6 +24,11 @@ import type { RuleSet, Target } from '../types' // an empty execution context every single time... const STATIC_EXECUTION_CONTEXT = {} +type BaseState = { + theme?: ?Theme, + generatedClassName?: string, +} + export default (ComponentStyle: Function, constructWithOptions: Function) => { const identifiers = {} @@ -53,11 +59,13 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => { : componentId } - class BaseStyledComponent extends Component { + // $FlowFixMe + class BaseStyledComponent extends Component<*, BaseState> { static target: Target static styledComponentId: string static attrs: Object static componentStyle: Object + static defaultProps: Object static warnTooManyClasses: Function attrs = {} @@ -83,7 +91,10 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => { this.attrs = Object.keys(attrs).reduce((acc, key) => { const attr = attrs[key] // eslint-disable-next-line no-param-reassign - acc[key] = typeof attr === 'function' ? attr(context) : attr + acc[key] = + typeof attr === 'function' && !hasInInheritanceChain(attr, Component) + ? attr(context) + : attr return acc }, {}) @@ -172,10 +183,10 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => { return } - this.setState(oldState => { + this.setState(prevState => { const theme = determineTheme( nextProps, - oldState.theme, + prevState.theme, this.constructor.defaultProps ) const generatedClassName = this.generateAndInjectStyles( @@ -209,7 +220,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => { .filter(Boolean) .join(' ') - const baseProps = { + const baseProps: any = { ...this.attrs, className, } @@ -277,7 +288,7 @@ export default (ComponentStyle: Function, constructWithOptions: Function) => { ]), } - static withComponent(tag) { + static withComponent(tag: Target) { const { componentId: previousComponentId, ...optionsToCopy } = options const newComponentId = diff --git a/src/models/StyledNativeComponent.js b/src/models/StyledNativeComponent.js index 1b77fa03e..676b42981 100644 --- a/src/models/StyledNativeComponent.js +++ b/src/models/StyledNativeComponent.js @@ -6,16 +6,24 @@ import determineTheme from '../utils/determineTheme' import generateDisplayName from '../utils/generateDisplayName' import isStyledComponent from '../utils/isStyledComponent' import isTag from '../utils/isTag' +import hasInInheritanceChain from '../utils/hasInInheritanceChain' import { CHANNEL, CHANNEL_NEXT, CONTEXT_CHANNEL_SHAPE } from './ThemeProvider' import type { Theme } from './ThemeProvider' import type { RuleSet, Target } from '../types' +type State = { + theme?: ?Theme, + generatedStyles: any, +} + export default (constructWithOptions: Function, InlineStyle: Function) => { - class BaseStyledNativeComponent extends Component { + // $FlowFixMe + class BaseStyledNativeComponent extends Component<*, State> { static target: Target static styledComponentId: string static attrs: Object + static defaultProps: Object static inlineStyle: Object root: ?Object @@ -43,7 +51,10 @@ export default (constructWithOptions: Function, InlineStyle: Function) => { this.attrs = Object.keys(attrs).reduce((acc, key) => { const attr = attrs[key] // eslint-disable-next-line no-param-reassign - acc[key] = typeof attr === 'function' ? attr(context) : attr + acc[key] = + typeof attr === 'function' && !hasInInheritanceChain(attr, Component) + ? attr(context) + : attr return acc }, {}) @@ -90,10 +101,10 @@ export default (constructWithOptions: Function, InlineStyle: Function) => { theme?: Theme, [key: string]: any, }) { - this.setState(oldState => { + this.setState(prevState => { const theme = determineTheme( nextProps, - oldState.theme, + prevState.theme, this.constructor.defaultProps ) const generatedStyles = this.generateAndInjectStyles(theme, nextProps) @@ -117,7 +128,8 @@ export default (constructWithOptions: Function, InlineStyle: Function) => { console.warn( 'setNativeProps was called on a Styled Component wrapping a stateless functional component. ' + 'In this case no ref will be stored, and instead an innerRef prop will be passed on.\n' + - `Check whether the stateless functional component is passing on innerRef as a ref in ${displayName}.` + `Check whether the stateless functional component is passing on innerRef as a ref in ${displayName || + 'UnknownStyledNativeComponent'}.` ) } } @@ -154,6 +166,7 @@ export default (constructWithOptions: Function, InlineStyle: Function) => { !isStyledComponent(target) && // NOTE: We can't pass a ref to a stateless functional component (typeof target !== 'function' || + // $FlowFixMe TODO: flow for prototype (target.prototype && 'isReactComponent' in target.prototype)) ) { propsForElement.ref = this.onRef @@ -189,7 +202,7 @@ export default (constructWithOptions: Function, InlineStyle: Function) => { [CHANNEL_NEXT]: CONTEXT_CHANNEL_SHAPE, } - static withComponent(tag) { + static withComponent(tag: Target) { const { displayName: _, componentId: __, ...optionsToCopy } = options const newOptions = { ...optionsToCopy, diff --git a/src/models/ThemeProvider.js b/src/models/ThemeProvider.js index 69bb0ed6d..ba148ccdd 100644 --- a/src/models/ThemeProvider.js +++ b/src/models/ThemeProvider.js @@ -1,8 +1,6 @@ // @flow -/* globals React$Element */ -import React, { Component } from 'react' +import React, { Component, type Element } from 'react' import PropTypes from 'prop-types' -import isPlainObject from 'is-plain-object' import createBroadcast from '../utils/create-broadcast' import type { Broadcast } from '../utils/create-broadcast' import once from '../utils/once' @@ -19,7 +17,7 @@ export const CONTEXT_CHANNEL_SHAPE = PropTypes.shape({ export type Theme = { [key: string]: mixed } type ThemeProviderProps = {| - children?: React$Element, + children?: Element, theme: Theme | ((outerTheme: Theme) => void), |} @@ -39,7 +37,7 @@ const isFunction = test => typeof test === 'function' * Provide a theme to an entire react component tree via context and event listeners (have to do * both context and event emitter as pure components block context updates) */ -class ThemeProvider extends Component { +class ThemeProvider extends Component { getTheme: (theme?: Theme | ((outerTheme: Theme) => void)) => Theme outerTheme: Theme unsubscribeToOuterId: string @@ -108,7 +106,9 @@ class ThemeProvider extends Component { const mergedTheme = theme(this.outerTheme) if ( process.env.NODE_ENV !== 'production' && - !isPlainObject(mergedTheme) + (mergedTheme === null || + Array.isArray(mergedTheme) || + typeof mergedTheme !== 'object') ) { throw new Error( process.env.NODE_ENV !== 'production' @@ -118,10 +118,10 @@ class ThemeProvider extends Component { } return mergedTheme } - if (!isPlainObject(theme)) { + if (theme === null || Array.isArray(theme) || typeof theme !== 'object') { throw new Error( process.env.NODE_ENV !== 'production' - ? '[ThemeProvider] Please make your theme prop a plain object' + ? '[ThemeProvider] Please make your theme prop an object' : '' ) } diff --git a/src/no-parser/index.js b/src/no-parser/index.js index a54c159ad..60b67d841 100644 --- a/src/no-parser/index.js +++ b/src/no-parser/index.js @@ -43,6 +43,7 @@ const injectGlobal = _injectGlobal(stringifyRules, css) const styled = _styled(StyledComponent, constructWithOptions) if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console console.warn( 'Usage of the preprocessing plugin is deprecated. The no-parser bundle will be removed in the next major release. \nPlease disable the preprocess option in your Babel config and upgrade to a newer version of our plugin which removes this option entirely.' ) diff --git a/src/test/__snapshots__/ssr.test.js.snap b/src/test/__snapshots__/ssr.test.js.snap index adb990c09..7298df0c0 100644 --- a/src/test/__snapshots__/ssr.test.js.snap +++ b/src/test/__snapshots__/ssr.test.js.snap @@ -68,6 +68,8 @@ exports[`ssr should extract the CSS in a simple case 2`] = ` .sc-a {} .b{color:red;}" `; +exports[`ssr should handle errors while streaming 1`] = `[Invariant Violation: React.Children.only expected to receive a single React element child.]`; + exports[`ssr should interleave styles with rendered HTML when utilitizing streaming 1`] = ` "