├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .stylelintrc.js ├── .travis.yml ├── LICENSE.md ├── README.md ├── bin └── server ├── interfaces ├── CSSModule.js └── globals.js ├── mocha.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── .eslintrc.js ├── DataLoader.js ├── api.js ├── client.js ├── helpers │ ├── status.js │ └── status.spec.js ├── index.js ├── modules │ ├── App │ │ ├── App.js │ │ ├── App.scss │ │ ├── App.spec.js │ │ ├── Html.js │ │ ├── Html.spec.js │ │ ├── components │ │ │ ├── Header.js │ │ │ ├── Header.scss │ │ │ └── Header.spec.js │ │ └── index.js │ ├── Blog │ │ ├── Blog.js │ │ ├── Blog.scss │ │ ├── Blog.spec.js │ │ ├── components │ │ │ ├── BlogArticle.js │ │ │ ├── BlogArticle.spec.js │ │ │ ├── BlogIndex.js │ │ │ └── BlogIndex.spec.js │ │ ├── index.js │ │ └── redux.js │ ├── Home │ │ ├── Home.js │ │ ├── Home.spec.js │ │ └── index.js │ ├── NotFound │ │ ├── NotFound.js │ │ ├── NotFound.spec.js │ │ └── index.js │ └── index.js ├── reducer.js ├── routes.js ├── server.js └── store.js ├── webpack.config.js ├── webpack.isomorphic.tools.js └── webpack.server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015-node5", "stage-0"], 3 | "plugins": ["add-module-exports"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 7, 4 | ecmaFeatures: { 5 | impliedStrict: true, 6 | jsx: true, 7 | experimentalObjectRestSpread: true 8 | }, 9 | sourceType: 'module' 10 | }, 11 | parser: 'babel-eslint', 12 | env: { 13 | node: true, 14 | es6: true 15 | }, 16 | globals: { 17 | __DEV__: true, 18 | __PROD__: true, 19 | __SERVER__: true, 20 | __CLIENT__: true 21 | }, 22 | plugins: [ 23 | 'react', 24 | 'flowtype' 25 | ], 26 | rules: { 27 | 28 | // Possible Errors 29 | // http://eslint.org/docs/rules/#possible-errors 30 | 31 | // disallow await inside of loops 32 | 'no-await-in-loop': 2, 33 | // disallow comparing against -0 34 | 'no-compare-neg-zero': 2, 35 | // disallow assignment operators in conditional expressions 36 | 'no-cond-assign': [2, 'except-parens'], 37 | // disallow the use of console 38 | 'no-console': 2, 39 | // disallow constant expressions in conditions 40 | 'no-constant-condition': 2, 41 | // disallow control characters in regular expressions 42 | 'no-control-regex': 2, 43 | // disallow the use of debugger 44 | 'no-debugger': 2, 45 | // disallow duplicate arguments in function definitions 46 | 'no-dupe-args': 2, 47 | // disallow duplicate keys in object literals 48 | 'no-dupe-keys': 2, 49 | // disallow duplicate case labels 50 | 'no-duplicate-case': 2, 51 | // disallow empty block statements 52 | 'no-empty': [2, { allowEmptyCatch: true }], 53 | // disallow empty character classes in regular expressions 54 | 'no-empty-character-class': 2, 55 | // disallow reassigning exceptions in catch clauses 56 | 'no-ex-assign': 2, 57 | // disallow unnecessary boolean casts 58 | 'no-extra-boolean-cast': 2, 59 | // disallow unnecessary parentheses 60 | 'no-extra-parens': [2, 'functions'], 61 | // disallow unnecessary semicolons 62 | 'no-extra-semi': 2, 63 | // disallow reassigning function declarations 64 | 'no-func-assign': 2, 65 | // disallow function or var declarations in nested blocks 66 | 'no-inner-declarations': [2, 'both'], 67 | // disallow invalid regular expression strings in RegExp constructors 68 | 'no-invalid-regexp': 2, 69 | // disallow irregular whitespace outside of strings and comments 70 | 'no-irregular-whitespace': 2, 71 | // disallow calling global object properties as functions 72 | 'no-obj-calls': 2, 73 | // disallow use of Object.prototypes builtins directly 74 | 'no-prototype-builtins': 2, 75 | // disallow multiple spaces in regular expression literals 76 | 'no-regex-spaces': 2, 77 | // disallow sparse arrays 78 | 'no-sparse-arrays': 2, 79 | // disallow template literal placeholder syntax in regular strings 80 | 'no-template-curly-in-string': 2, 81 | // disallow confusing multiline expressions 82 | 'no-unexpected-multiline': 2, 83 | // disallow unreachable code after return, throw, continue, and break statements 84 | 'no-unreachable': 2, 85 | // disallow control flow statements in finally blocks 86 | 'no-unsafe-finally': 2, 87 | // disallow negating the left operand of relational operators 88 | 'no-unsafe-negation': 2, 89 | //require calls to isNaN() when checking for NaN 90 | 'use-isnan': 2, 91 | // enforce valid JSDoc comments 92 | 'valid-jsdoc': 0, 93 | // enforce comparing typeof expressions against valid strings 94 | 'valid-typeof': 2, 95 | 96 | // Best Practices 97 | // http://eslint.org/docs/rules/#best-practices 98 | 99 | // enforce getter and setter pairs in objects 100 | 'accessor-pairs': 2, 101 | // enforce return statements in callbacks of array methods 102 | 'array-callback-return': 0, 103 | // enforce the use of variables within the scope they are defined 104 | 'block-scoped-var': 0, 105 | // enforce that class methods utilize this 106 | 'class-methods-use-this': 0, 107 | // enforce a maximum cyclomatic complexity allowed in a program 108 | 'complexity': 0, 109 | // require return statements to either always or never specify values 110 | 'consistent-return': 0, 111 | // enforce consistent brace style for all control statements 112 | 'curly': 2, 113 | // require default cases in switch statements 114 | 'default-case': 0, 115 | // enforce consistent newlines before and after dots 116 | 'dot-location': [2, 'property'], 117 | // enforce dot notation whenever possible 118 | 'dot-notation': 0, 119 | // require the use of === and !== 120 | 'eqeqeq': [2, 'smart'], 121 | // require for-in loops to include an if statement 122 | 'guard-for-in': 2, 123 | // disallow the use of alert, confirm, and prompt 124 | 'no-alert': 2, 125 | // disallow the use of arguments.caller or arguments.callee 126 | 'no-caller': 2, 127 | // disallow lexical declarations in case clauses 128 | 'no-case-declarations': 2, 129 | // disallow division operators explicitly at the beginning of regular expressions 130 | 'no-div-regex': 0, 131 | // disallow else blocks after return statements in if statements 132 | 'no-else-return': 0, 133 | // disallow empty functions 134 | 'no-empty-function': [2, { allow: [] }], 135 | // disallow empty destructuring patterns 136 | 'no-empty-pattern': 2, 137 | // disallow null comparisons without type-checking operators 138 | 'no-eq-null': 0, 139 | // disallow the use of eval() 140 | 'no-eval': 2, 141 | // disallow extending native types 142 | 'no-extend-native': 2, 143 | // disallow unnecessary calls to .bind() 144 | 'no-extra-bind': 2, 145 | // disallow unnecessary labels 146 | 'no-extra-label': 2, 147 | // disallow fallthrough of case statements 148 | 'no-fallthrough': [2, { commentPattern: 'break' }], 149 | // disallow leading or trailing decimal points in numeric literals 150 | 'no-floating-decimal': 2, 151 | // disallow assignments to native objects or read-only global variables 152 | 'no-global-assign': 2, 153 | // disallow shorthand type conversions 154 | 'no-implicit-coercion': 0, 155 | // disallow var and named function declarations in the global scope 156 | 'no-implicit-globals': 2, 157 | // disallow the use of eval()-like methods 158 | 'no-implied-eval': 2, 159 | // disallow this keywords outside of classes or class-like objects 160 | 'no-invalid-this': 2, 161 | // disallow the use of the __iterator__ property 162 | 'no-iterator': 2, 163 | // disallow labeled statements 164 | 'no-labels': 0, 165 | // disallow unnecessary nested blocks 166 | 'no-lone-blocks': 0, 167 | // disallow function declarations and expressions inside loop statements 168 | 'no-loop-func': 2, 169 | // disallow magic numbers 170 | 'no-magic-numbers': 0, 171 | // disallow multiple spaces 172 | 'no-multi-spaces': 0, 173 | // disallow multiline strings 174 | 'no-multi-str': 0, 175 | // disallow new operators outside of assignments or comparisons 176 | 'no-new': 2, 177 | // disallow new operators with the Function object 178 | 'no-new-func': 2, 179 | // disallow new operators with the String, Number, and Boolean objects 180 | 'no-new-wrappers': 2, 181 | // disallow octal literals 182 | 'no-octal': 2, 183 | // disallow octal escape sequences in string literals 184 | 'no-octal-escape': 2, 185 | // disallow reassigning function parameters 186 | 'no-param-reassign': 0, 187 | // disallow the use of the __proto__ property 188 | 'no-proto': 2, 189 | // disallow var redeclaration 190 | 'no-redeclare': 2, 191 | // disallow certain properties on certain objects 192 | 'no-restricted-properties': 0, 193 | // disallow assignment operators in return statements 194 | 'no-return-assign': [2, 'except-parens'], 195 | // disallow unnecessary return await 196 | 'no-return-await': 0, 197 | // disallow javascript: urls 198 | 'no-script-url': 2, 199 | // disallow assignments where both sides are exactly the same 200 | 'no-self-assign': 2, 201 | // disallow comparisons where both sides are exactly the same 202 | 'no-self-compare': 2, 203 | // disallow comma operators 204 | 'no-sequences': 2, 205 | // disallow throwing literals as exceptions 206 | 'no-throw-literal': 2, 207 | // disallow unmodified loop conditions 208 | 'no-unmodified-loop-condition': 2, 209 | // disallow unused expressions 210 | 'no-unused-expressions': 0, 211 | // disallow unused labels 212 | 'no-unused-labels': 2, 213 | // disallow unnecessary calls to .call() and .apply() 214 | 'no-useless-call': 2, 215 | // disallow unnecessary concatenation of literals or template literals 216 | 'no-useless-concat': 2, 217 | // disallow unnecessary escape characters 218 | 'no-useless-escape': 2, 219 | // disallow redundant return statements 220 | 'no-useless-return': 2, 221 | // disallow void operators 222 | 'no-void': 2, 223 | // disallow specified warning terms in comments 224 | 'no-warning-comments': 0, 225 | // disallow with statements 226 | 'no-with': 2, 227 | // require using Error objects as Promise rejection reasons 228 | 'prefer-promise-reject-errors': 2, 229 | // enforce the consistent use of the radix argument when using parseInt() 230 | 'radix': [2, 'always'], 231 | // disallow async functions which have no await expression 232 | 'require-await': 0, 233 | // require var declarations be placed at the top of their containing scope 234 | 'vars-on-top': 0, 235 | // require parentheses around immediate function invocations 236 | 'wrap-iife': [2, 'outside'], 237 | // require or disallow “Yoda” conditions 238 | 'yoda': [2, 'never', { exceptRange: true }], 239 | 240 | // Strict Mode 241 | // http://eslint.org/docs/rules/#strict-mode 242 | 243 | // require or disallow strict mode directives 244 | 'strict': [2, 'never'], 245 | 246 | // Variables 247 | // http://eslint.org/docs/rules/#variables 248 | 249 | // require or disallow initialization in var declarations 250 | 'init-declarations': 0, 251 | // disallow catch clause parameters from shadowing variables in the outer scope 252 | 'no-catch-shadow': 2, 253 | // disallow deleting variables 254 | 'no-delete-var': 2, 255 | // disallow labels that share a name with a variable 256 | 'no-label-var': 2, 257 | // disallow specified global variables 258 | 'no-restricted-globals': 2, 259 | // disallow var declarations from shadowing variables in the outer scope 260 | 'no-shadow': [2, { allow: ['resolve', 'reject'] }], 261 | // disallow identifiers from shadowing restricted names 262 | 'no-shadow-restricted-names': 2, 263 | // disallow the use of undeclared variables unless mentioned in /*global */ comments 264 | 'no-undef': 2, 265 | // disallow initializing variables to undefined 266 | 'no-undef-init': 2, 267 | // disallow the use of undefined as an identifier 268 | 'no-undefined': 2, 269 | // disallow unused variables 270 | 'no-unused-vars': [2, 'local'], 271 | // disallow the use of variables before they are defined 272 | 'no-use-before-define': [2, { functions: false }], 273 | 274 | // Node.js and CommonJS 275 | // http://eslint.org/docs/rules/#nodejs-and-commonjs 276 | 277 | // require return statements after callbacks 278 | 'callback-return': 0, 279 | // require require() calls to be placed at top-level module scope 280 | 'global-require': 0, 281 | // require error handling in callbacks 282 | 'handle-callback-err': 0, 283 | // disallow require calls to be mixed with regular var declarations 284 | 'no-mixed-requires': 0, 285 | // disallow new operators with calls to require 286 | 'no-new-require': 2, 287 | // disallow string concatenation with __dirname and __filename 288 | 'no-path-concat': 2, 289 | // disallow the use of process.env 290 | 'no-process-env': 0, 291 | // disallow the use of process.exit() 292 | 'no-process-exit': 2, 293 | // disallow specified modules when loaded by require 294 | 'no-restricted-modules': 0, 295 | // disallow synchronous methods 296 | 'no-sync': 0, 297 | 298 | // Stylistic Issues 299 | // http://eslint.org/docs/rules/#stylistic-issues 300 | 301 | // enforce consistent spacing inside array brackets 302 | 'array-bracket-spacing': [2, 'never'], 303 | // enforce consistent spacing inside single-line blocks 304 | 'block-spacing': [2, 'always'], 305 | // enforce consistent brace style for blocks 306 | 'brace-style': [2, '1tbs', { allowSingleLine: true }], 307 | // enforce camelcase naming convention 308 | 'camelcase': 0, 309 | // enforce or disallow capitalization of the first letter of a comment 310 | 'capitalized-comments': 0, 311 | // require or disallow trailing commas 312 | 'comma-dangle': [2, 'never'], 313 | // enforce consistent spacing before and after commas 314 | 'comma-spacing': [2, { before: false, after: true }], 315 | // enforce consistent comma style 316 | 'comma-style': [2, 'last'], 317 | // enforce consistent spacing inside computed property brackets 318 | 'computed-property-spacing': [2, 'never'], 319 | // enforce consistent naming when capturing the current execution context 320 | 'consistent-this': 0, 321 | // enforce at least one newline at the end of files 322 | 'eol-last': [2, 'unix'], 323 | // require or disallow spacing between function identifiers and their invocations 324 | 'func-call-spacing': [2, 'never'], 325 | // require function names to match the name of the variable or property to which they are assigned 326 | 'func-name-matching': 0, 327 | // require or disallow named function expressions 328 | 'func-names': 0, 329 | // enforce the consistent use of either function declarations or expressions 330 | 'func-style': 0, 331 | // disallow specified identifiers 332 | 'id-blacklist': 0, 333 | // enforce minimum and maximum identifier lengths 334 | 'id-length': 0, 335 | // require identifiers to match a specified regular expression 336 | 'id-match': 0, 337 | // enforce consistent indentation 338 | 'indent': [2, 2], 339 | // enforce the consistent use of either double or single quotes in JSX attributes 340 | 'jsx-quotes': [2, 'prefer-double'], 341 | // enforce consistent spacing between keys and values in object literal properties 342 | 'key-spacing': [2, { beforeColon: false, afterColon: true, mode: 'minimum' }], 343 | // enforce consistent spacing before and after keywords 344 | 'keyword-spacing': [2, { before: true, after: true }], 345 | // enforce position of line comments 346 | 'line-comment-position': 0, 347 | // enforce consistent linebreak style 348 | 'linebreak-style': [2, 'unix'], 349 | // require empty lines around comments 350 | 'lines-around-comment': 0, 351 | // require or disallow newlines around directives 352 | 'lines-around-directive': 0, 353 | // enforce a maximum depth that blocks can be nested 354 | 'max-depth': 0, 355 | // enforce a maximum line length 356 | 'max-len': 0, 357 | // enforce a maximum file length 358 | 'max-lines': 0, 359 | // enforce a maximum depth that callbacks can be nested 360 | 'max-nested-callbacks': 0, 361 | // enforce a maximum number of parameters in function definitions 362 | 'max-params': 0, 363 | // enforce a maximum number of statements allowed in function blocks 364 | 'max-statements': 0, 365 | // enforce a maximum number of statements allowed per line 366 | 'max-statements-per-line': 0, 367 | // enforce newlines between operands of ternary expressions 368 | 'multiline-ternary': 0, 369 | // require constructor function names to begin with a capital letter 370 | 'new-cap': [2, { newIsCap: true, capIsNew: true }], 371 | // require parentheses when invoking a constructor with no arguments 372 | 'new-parens': 2, 373 | // require or disallow an empty line after var declarations 374 | 'newline-after-var': 0, 375 | // require an empty line before return statements 376 | 'newline-before-return': 0, 377 | // require a newline after each call in a method chain 378 | 'newline-per-chained-call': 0, 379 | // disallow Array constructors 380 | 'no-array-constructor': 2, 381 | // disallow bitwise operators 382 | 'no-bitwise': 0, 383 | // disallow continue statements 384 | 'no-continue': 0, 385 | // disallow inline comments after code 386 | 'no-inline-comments': 0, 387 | // disallow if statements as the only statement in else blocks 388 | 'no-lonely-if': 2, 389 | // disallow mixes of different operators 390 | 'no-mixed-operators': 0, 391 | // disallow mixed spaces and tabs for indentation 392 | 'no-mixed-spaces-and-tabs': 2, 393 | // disallow multiple empty lines 394 | 'no-multiple-empty-lines': [2, { max: 2, maxEOF: 0, maxBOF: 0 }], 395 | // disallow negated conditions 396 | 'no-negated-condition': 0, 397 | // disallow nested ternary expressions 398 | 'no-nested-ternary': 0, 399 | // disallow Object constructors 400 | 'no-new-object': 2, 401 | // disallow the unary operators ++ and -- 402 | 'no-plusplus': 0, 403 | // disallow specified syntax 404 | 'no-restricted-syntax': 0, 405 | // disallow tabs in file 406 | 'no-tabs': 2, 407 | // disallow ternary operators 408 | 'no-ternary': 0, 409 | // disallow trailing whitespace at the end of lines 410 | 'no-trailing-spaces': 2, 411 | // disallow dangling underscores in identifiers 412 | 'no-underscore-dangle': 0, 413 | // disallow ternary operators when simpler alternatives exist 414 | 'no-unneeded-ternary': 2, 415 | // disallow whitespace before properties 416 | 'no-whitespace-before-property': 2, 417 | // enforce the location of single-line statements 418 | 'nonblock-statement-body-position': 0, 419 | // enforce consistent line breaks inside braces 420 | 'object-curly-newline': 0, 421 | // enforce consistent spacing inside braces 422 | 'object-curly-spacing': [2, 'always'], 423 | // enforce placing object properties on separate lines 424 | 'object-property-newline': 0, 425 | // enforce variables to be declared either together or separately in functions 426 | 'one-var': 0, 427 | // require or disallow newlines around var declarations 428 | 'one-var-declaration-per-line': 0, 429 | // require or disallow assignment operator shorthand where possible 430 | 'operator-assignment': 0, 431 | // enforce consistent linebreak style for operators 432 | 'operator-linebreak': [2, 'after'], 433 | // require or disallow padding within blocks 434 | 'padded-blocks': 0, 435 | // require quotes around object literal property names 436 | 'quote-props': [2, 'consistent-as-needed'], 437 | // enforce the consistent use of either backticks, double, or single quotes 438 | 'quotes': [2, 'single', { allowTemplateLiterals: true }], 439 | // require JSDoc comments 440 | 'require-jsdoc': 0, 441 | // require or disallow semicolons instead of ASI 442 | 'semi': [2, 'never'], 443 | // enforce consistent spacing before and after semicolons 444 | 'semi-spacing': 2, 445 | // require object keys to be sorted 446 | 'sort-keys': 0, 447 | // require variables within the same declaration block to be sorted 448 | 'sort-vars': 0, 449 | // enforce consistent spacing before blocks 450 | 'space-before-blocks': 0, 451 | // enforce consistent spacing before function definition opening parenthesis 452 | 'space-before-function-paren': [2, { anonymous: 'always', named: 'never' }], 453 | // enforce consistent spacing inside parentheses 454 | 'space-in-parens': [2, 'never'], 455 | // require spacing around operators 456 | 'space-infix-ops': [2, { int32Hint: true }], 457 | // enforce consistent spacing before or after unary operators 458 | 'space-unary-ops': [2, { words: true, nonwords: false }], 459 | // enforce consistent spacing after the // or /* in a comment 460 | 'spaced-comment': [2, 'always'], 461 | // require or disallow spacing between template tags and their literals 462 | 'template-tag-spacing': 0, 463 | // require or disallow the Unicode BOM 464 | 'unicode-bom': [2, 'never'], 465 | // require parenthesis around regex literals 466 | 'wrap-regex': 0, 467 | 468 | // ECMAScript 6 469 | // http://eslint.org/docs/rules/#ecmascript-6 470 | 471 | // require braces around arrow function bodies 472 | 'arrow-body-style': [2, 'as-needed'], 473 | // require parentheses around arrow function arguments 474 | 'arrow-parens': 0, 475 | // enforce consistent spacing before and after the arrow in arrow functions 476 | 'arrow-spacing': [2, { before: true, after: true }], 477 | // require super() calls in constructors 478 | 'constructor-super': 2, 479 | // enforce consistent spacing around * operators in generator functions 480 | 'generator-star-spacing': [2, { before: false, after: true }], 481 | // disallow reassigning class members 482 | 'no-class-assign': 2, 483 | // disallow arrow functions where they could be confused with comparisons 484 | 'no-confusing-arrow': [2, { allowParens: true }], 485 | // disallow reassigning const variables 486 | 'no-const-assign': 2, 487 | // disallow duplicate class members 488 | 'no-dupe-class-members': 2, 489 | // disallow duplicate module imports 490 | 'no-duplicate-imports': [2, { includeExports: false }], 491 | // disallow new operators with the Symbol object 492 | 'no-new-symbol': 2, 493 | // disallow specified modules when loaded by import 494 | 'no-restricted-imports': 0, 495 | // disallow this/super before calling super() in constructors 496 | 'no-this-before-super': 2, 497 | // disallow unnecessary computed property keys in object literals 498 | 'no-useless-computed-key': 2, 499 | // disallow unnecessary constructors 500 | 'no-useless-constructor': 2, 501 | // disallow renaming import, export, and destructured assignments to the same name 502 | 'no-useless-rename': 0, 503 | // require let or const instead of var 504 | 'no-var': 2, 505 | // require or disallow method and property shorthand syntax for object literals 506 | 'object-shorthand': [2, 'properties'], 507 | // require arrow functions as callbacks 508 | 'prefer-arrow-callback': [2, { allowNamedFunctions: true }], 509 | // require const declarations for variables that are never reassigned after declared 510 | 'prefer-const': [2, { destructuring: 'all' }], 511 | // require destructuring from arrays and/or objects 512 | 'prefer-destructuring': 0, 513 | // disallow parseInt() in favor of binary, octal, and hexadecimal literals 514 | 'prefer-numeric-literals': 0, 515 | // require rest parameters instead of arguments 516 | 'prefer-rest-params': 2, 517 | // require spread operators instead of .apply() 518 | 'prefer-spread': 2, 519 | // require template literals instead of string concatenation 520 | 'prefer-template': 2, 521 | // require generator functions to contain yield 522 | 'require-yield': 0, 523 | // enforce spacing between rest and spread operators and their expressions 524 | 'rest-spread-spacing': [2, 'never'], 525 | // enforce sorted import declarations within modules 526 | 'sort-imports': 0, 527 | // require symbol descriptions 528 | 'symbol-description': 0, 529 | // require or disallow spacing around embedded expressions of template strings 530 | 'template-curly-spacing': [2, 'always'], 531 | // require or disallow spacing around the * in yield* expressions 532 | 'yield-star-spacing': [2, { before: false, after: true }], 533 | 534 | // ESLint-plugin-React 535 | // https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules 536 | 537 | // prevent missing displayName in a React component definition 538 | 'react/display-name': [2, { ignoreTranspilerName: true }], 539 | // forbid certain props on Components 540 | 'react/forbid-component-props': 0, 541 | // forbid certain elements 542 | 'react/forbid-elements': 0, 543 | // forbid foreign propTypes 544 | 'react/forbid-foreign-prop-types': 0, 545 | // forbid certain propTypes 546 | 'react/forbid-prop-types': 0, 547 | // prevent using Array index in key props 548 | 'react/no-array-index-key': 2, 549 | // prevent passing children as props 550 | 'react/no-children-prop': 2, 551 | // prevent usage of dangerous JSX properties 552 | 'react/no-danger': 0, 553 | // prevent problem with children and props.dangerouslySetInnerHTML 554 | 'react/no-danger-with-children': 0, 555 | // prevent usage of deprecated methods 556 | 'react/no-deprecated': 2, 557 | // prevent usage of setState in componentDidMount 558 | 'react/no-did-mount-set-state': 2, 559 | // prevent usage of setState in componentDidUpdate 560 | 'react/no-did-update-set-state': 2, 561 | // prevent direct mutation of this.state 562 | 'react/no-direct-mutation-state': 2, 563 | // prevent usage of findDOMNode 564 | 'react/no-find-dom-node': 2, 565 | // prevent usage of isMounted 566 | 'react/no-is-mounted': 2, 567 | // prevent multiple component definition per file 568 | 'react/no-multi-comp': 2, 569 | // prevent usage of the return value of React.render 570 | 'react/no-render-return-value': 2, 571 | // prevent usage of setState 572 | 'react/no-set-state': 0, 573 | // prevent using string references in ref attribute 574 | 'react/no-string-refs': 2, 575 | // prevent invalid characters from appearing in markup 576 | 'react/no-unescaped-entities': 0, 577 | // prevent usage of unknown DOM property 578 | 'react/no-unknown-property': 2, 579 | // prevent definitions of unused prop types 580 | 'react/no-unused-prop-types': 0, 581 | // prevent usage of setState in componentWillUpdate 582 | 'react/no-will-update-set-state': 2, 583 | // enforce ES5 or ES6 class for React Components 584 | 'react/prefer-es6-class': 2, 585 | // enforce stateless React Components to be written as a pure function 586 | 'react/prefer-stateless-function': 0, 587 | // prevent missing props validation in a React component definition 588 | 'react/prop-types': 2, 589 | // prevent missing React when using JSX 590 | 'react/react-in-jsx-scope': 2, 591 | // enforce a defaultProps definition for every prop that is not a required prop 592 | 'react/require-default-props': 0, 593 | // enforce React components to have a shouldComponentUpdate method 594 | 'react/require-optimization': 0, 595 | // enforce ES5 or ES6 class for returning value in render function 596 | 'react/require-render-return': 2, 597 | // prevent extra closing tags for components without children 598 | 'react/self-closing-comp': 2, 599 | // enforce component methods order 600 | 'react/sort-comp': 2, 601 | // enforce propTypes declarations alphabetical sorting 602 | 'react/sort-prop-types': 0, 603 | // enforce style prop value being an object 604 | 'react/style-prop-object': 0, 605 | // prevent void DOM elements (e.g. ,
) from receiving children 606 | 'react/void-dom-elements-no-children': 2, 607 | 608 | // ESLint-plugin-React JSX 609 | // https://github.com/yannickcr/eslint-plugin-react#jsx-specific-rules 610 | 611 | // enforce boolean attributes notation in JSX 612 | 'react/jsx-boolean-value': [2, 'never'], 613 | // validate closing bracket location in JSX 614 | 'react/jsx-closing-bracket-location': 2, 615 | // enforce or disallow spaces inside of curly braces in JSX attributes 616 | 'react/jsx-curly-spacing': [2, 'always'], 617 | // enforce or disallow spaces around equal signs in JSX attributes 618 | 'react/jsx-equals-spacing': [2, 'never'], 619 | // restrict file extensions that may contain JSX 620 | 'react/jsx-filename-extension': [2, { extensions: ['.js'] }], 621 | // enforce position of the first prop in JSX 622 | 'react/jsx-first-prop-new-line': [2, 'multiline'], 623 | // enforce event handler naming conventions in JSX 624 | 'react/jsx-handler-names': 0, 625 | // validate JSX indentation 626 | 'react/jsx-indent': [2, 2], 627 | // validate props indentation in JSX 628 | 'react/jsx-indent-props': [2, 2], 629 | // validate JSX has key prop when in array or iterator 630 | 'react/jsx-key': 2, 631 | // limit maximum of props on a single line in JSX 632 | 'react/jsx-max-props-per-line': 0, 633 | // prevent usage of .bind() and arrow functions in JSX props 634 | 'react/jsx-no-bind': 2, 635 | // prevent comments from being inserted as text nodes 636 | 'react/jsx-no-comment-textnodes': 2, 637 | // prevent duplicate props in JSX 638 | 'react/jsx-no-duplicate-props': 2, 639 | // prevent usage of unwrapped JSX strings 640 | 'react/jsx-no-literals': 0, 641 | // prevent usage of unsafe target='_blank' 642 | 'react/jsx-no-target-blank': 0, 643 | // disallow undeclared variables in JSX 644 | 'react/jsx-no-undef': 2, 645 | // enforce PascalCase for user-defined JSX components 646 | 'react/jsx-pascal-case': 2, 647 | // enforce props alphabetical sorting 648 | 'react/jsx-sort-props': 0, 649 | // validate whitespace in and around the JSX opening and closing brackets 650 | 'react/jsx-tag-spacing': [2, { 651 | closingSlash: 'never', 652 | beforeSelfClosing: 'always', 653 | afterOpening: 'never' 654 | }], 655 | // prevent React to be incorrectly marked as unused 656 | 'react/jsx-uses-react': 2, 657 | // prevent variables used in JSX to be incorrectly marked as unused 658 | 'react/jsx-uses-vars': 2, 659 | // prevent missing parentheses around multilines JSX 660 | 'react/jsx-wrap-multilines': 2, 661 | 662 | // eslint-plugin-flowtype 663 | // https://github.com/gajus/eslint-plugin-flowtype#configuration 664 | 665 | // enforces a particular style for boolean type annotations 666 | 'flowtype/boolean-style': [2, 'boolean'], 667 | // marks Flow type identifiers as defined 668 | 'flowtype/define-flow-type': 2, 669 | // enforces consistent use of trailing commas in Object and Tuple annotations 670 | 'flowtype/delimiter-dangle': 0, 671 | // enforces consistent spacing within generic type annotation parameters 672 | 'flowtype/generic-spacing': 0, 673 | // checks for duplicate properties in Object annotations 674 | 'flowtype/no-dupe-keys': 2, 675 | // disallows use of primitive constructors as types, such as Boolean, Number and String 676 | 'flowtype/no-primitive-constructor-types': 2, 677 | // disallows Flow type imports, aliases, and annotations in files missing a valid Flow file declaration 678 | 'flowtype/no-types-missing-file-annotation': 2, 679 | // warns against weak type annotations any, Object and Function 680 | 'flowtype/no-weak-types': 0, 681 | // enforces consistent separators between properties in Flow object types 682 | 'flowtype/object-type-delimiter': 0, 683 | // requires that all function parameters have type annotations 684 | 'flowtype/require-parameter-type': 0, 685 | // requires that functions have return type annotation 686 | 'flowtype/require-return-type': 0, 687 | // makes sure that files have a valid @flow annotation 688 | 'flowtype/require-valid-file-annotation': 2, 689 | // requires that all variable declarators have type annotations 690 | 'flowtype/require-variable-type': 0, 691 | // enforces consistent use of semicolons after type aliases 692 | 'flowtype/semi': [2, 'never'], 693 | // enforces sorting of Object annotations 694 | 'flowtype/sort-keys': 0, 695 | // enforces consistent spacing after the type annotation colon 696 | 'flowtype/space-after-type-colon': [2, 'always'], 697 | // enforces consistent spacing before the opening < of generic type annotation parameters 698 | 'flowtype/space-before-generic-bracket': 0, 699 | // enforces consistent spacing before the type annotation colon 700 | 'flowtype/space-before-type-colon': [2, 'never'], 701 | // enforces a consistent naming pattern for type aliases 702 | 'flowtype/type-id-match': 0, 703 | // enforces consistent spacing around union and intersection type separators (| and &) 704 | 'flowtype/union-intersection-spacing': 0, 705 | // marks Flow type alias declarations as used 706 | 'flowtype/use-flow-type': 2, 707 | // checks for simple Flow syntax errors 708 | 'flowtype/valid-syntax': 2 709 | } 710 | } 711 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [include] 2 | 3 | [ignore] 4 | /dist/.* 5 | /lib/.* 6 | 7 | .*/node_modules/stylelint/.* 8 | 9 | [libs] 10 | interfaces 11 | 12 | [options] 13 | module.name_mapper='.+\.s?css' -> 'CSSModule' 14 | module.system=haste 15 | module.use_strict=true 16 | 17 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe 18 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue 19 | 20 | esproposal.class_static_fields=enable 21 | esproposal.class_instance_fields=enable 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | lib 3 | node_modules 4 | .eslintcache 5 | webpack-assets.json 6 | webpack-stats.json 7 | *.log 8 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 4 | // Color 5 | // http://stylelint.io/user-guide/rules/#color 6 | 7 | // specify lowercase or uppercase for hex colors 8 | 'color-hex-case': 'lower', 9 | // specify short or long notation for hex colors 10 | 'color-hex-length': 'short', 11 | // require (where possible) or disallow named colors 12 | 'color-named': null, 13 | // disallow hex colors 14 | 'color-no-hex': null, 15 | // disallow invalid hex colors 16 | 'color-no-invalid-hex': true, 17 | 18 | // Font Family 19 | // http://stylelint.io/user-guide/rules/#font-family 20 | 21 | // specify whether or not quotation marks should be used around font family names 22 | 'font-family-name-quotes': 'always-where-recommended', 23 | // disallow duplicate font family names 24 | 'font-family-no-duplicate-names': true, 25 | 26 | // Font Weight 27 | // http://stylelint.io/user-guide/rules/#font-weight 28 | 29 | // require numeric or named (where possible) font-weight values 30 | 'font-weight-notation': 'numeric', 31 | 32 | // Function 33 | // http://stylelint.io/user-guide/rules/#function 34 | 35 | // specify a blacklist of disallowed functions 36 | 'function-blacklist': null, 37 | // disallow an unspaced operator within calc functions 38 | 'function-calc-no-unspaced-operator': true, 39 | // require a newline or disallow whitespace after the commas of functions 40 | 'function-comma-newline-after': 'always-multi-line', 41 | // require a newline or disallow whitespace before the commas of functions 42 | 'function-comma-newline-before': 'never-multi-line', 43 | // require a single space or disallow whitespace after the commas of functions 44 | 'function-comma-space-after': 'always-single-line', 45 | // require a single space or disallow whitespace before the commas of functions 46 | 'function-comma-space-before': 'never', 47 | // disallow direction values in linear-gradient() calls that are not valid according to the standard syntax 48 | 'function-linear-gradient-no-nonstandard-direction': true, 49 | // limit the number of adjacent empty lines within functions 50 | 'function-max-empty-lines': null, 51 | // specify lowercase or uppercase for function names 52 | 'function-name-case': 'lower', 53 | // require a newline or disallow whitespace on the inside of the parentheses of functions 54 | 'function-parentheses-newline-inside': 'always-multi-line', 55 | // require a single space or disallow whitespace on the inside of the parentheses of functions 56 | 'function-parentheses-space-inside': 'never', 57 | // require or disallow data URIs for urls 58 | 'function-url-data-uris': null, 59 | // disallow scheme-relative urls 60 | 'function-url-no-scheme-relative': true, 61 | // require or disallow quotes for urls 62 | 'function-url-quotes': 'always', 63 | // specify a whitelist of allowed url schemes 64 | 'function-url-scheme-whitelist': null, 65 | // specify a whitelist of allowed functions 66 | 'function-whitelist': null, 67 | // require or disallow whitespace after functions 68 | 'function-whitespace-after': 'always', 69 | 70 | // Number 71 | // http://stylelint.io/user-guide/rules/#number 72 | 73 | // require or disallow a leading zero for fractional numbers less than 1 74 | 'number-leading-zero': 'always', 75 | // limit the number of decimal places allowed in numbers 76 | 'number-max-precision': 4, 77 | // disallow trailing zeros in numbers 78 | 'number-no-trailing-zeros': true, 79 | 80 | // String 81 | // http://stylelint.io/user-guide/rules/#string 82 | 83 | // disallow (unescaped) newlines in strings 84 | 'string-no-newline': true, 85 | // specify single or double quotes around strings 86 | 'string-quotes': 'single', 87 | 88 | // Length 89 | // http://stylelint.io/user-guide/rules/#length 90 | 91 | // disallow units for zero lengths 92 | 'length-zero-no-unit': true, 93 | 94 | // Time 95 | // http://stylelint.io/user-guide/rules/#time 96 | 97 | // specify the minimum number of milliseconds for time values 98 | 'time-min-milliseconds': 100, 99 | 100 | // Unit 101 | // http://stylelint.io/user-guide/rules/#unit 102 | 103 | // specify a blacklist of disallowed units 104 | 'unit-blacklist': null, 105 | // specify lowercase or uppercase for units 106 | 'unit-case': 'lower', 107 | // disallow unknown units 108 | 'unit-no-unknown': true, 109 | // specify a whitelist of allowed units 110 | 'unit-whitelist': null, 111 | 112 | // Value 113 | // http://stylelint.io/user-guide/rules/#value 114 | 115 | // specify lowercase or uppercase for keywords values 116 | 'value-keyword-case': 'lower', 117 | // disallow vendor prefixes for values 118 | 'value-no-vendor-prefix': true, 119 | 120 | // Value List 121 | // http://stylelint.io/user-guide/rules/#value-list 122 | 123 | // require a newline or disallow whitespace after the commas of value lists 124 | 'value-list-comma-newline-after': 'always-multi-line', 125 | // require a newline or disallow whitespace before the commas of value lists 126 | 'value-list-comma-newline-before': 'never-multi-line', 127 | // require a single space or disallow whitespace after the commas of value lists 128 | 'value-list-comma-space-after': 'always-single-line', 129 | // require a single space or disallow whitespace before the commas of value lists 130 | 'value-list-comma-space-before': 'never', 131 | // limit the number of adjacent empty lines within value lists 132 | 'value-list-max-empty-lines': 0, 133 | 134 | // Custom Property 135 | // http://stylelint.io/user-guide/rules/#custom-property 136 | 137 | // require or disallow an empty line before custom properties 138 | 'custom-property-empty-line-before': 'always', 139 | // disallow custom properties outside of :root rules 140 | 'custom-property-no-outside-root': null, 141 | // specify a pattern for custom properties 142 | 'custom-property-pattern': null, 143 | 144 | // Shorthand Property 145 | // http://stylelint.io/user-guide/rules/#shorthand-property 146 | 147 | // disallow redundant values in shorthand properties 148 | 'shorthand-property-no-redundant-values': true, 149 | 150 | // Property 151 | // http://stylelint.io/user-guide/rules/#property 152 | 153 | // specify a blacklist of disallowed properties 154 | 'property-blacklist': null, 155 | // specify lowercase or uppercase for properties 156 | 'property-case': 'lower', 157 | // disallow unknown properties 158 | 'property-no-unknown': true, 159 | // disallow vendor prefixes for properties 160 | 'property-no-vendor-prefix': true, 161 | // specify a whitelist of allowed properties 162 | 'property-whitelist': null, 163 | 164 | // Keyframe declaration 165 | // http://stylelint.io/user-guide/rules/#keyframe-declaration 166 | 167 | // disallow !important within keyframe declarations 168 | 'keyframe-declaration-no-important': true, 169 | 170 | // Declaration 171 | // http://stylelint.io/user-guide/rules/#declaration 172 | 173 | // require a single space or disallow whitespace after the bang of declarations 174 | 'declaration-bang-space-after': 'never', 175 | // require a single space or disallow whitespace before the bang of declarations 176 | 'declaration-bang-space-before': 'always', 177 | // require a newline or disallow whitespace after the colon of declarations 178 | 'declaration-colon-newline-after': 'always-multi-line', 179 | // require a single space or disallow whitespace after the colon of declarations 180 | 'declaration-colon-space-after': 'always-single-line', 181 | // require a single space or disallow whitespace before the colon of declarations 182 | 'declaration-colon-space-before': 'never', 183 | // require or disallow an empty line before declarations 184 | 'declaration-empty-line-before': null, 185 | // disallow !important within declarations 186 | 'declaration-no-important': null, 187 | // specify a blacklist of disallowed property and unit pairs within declarations 188 | 'declaration-property-unit-blacklist': {}, 189 | // specify a whitelist of allowed property and unit pairs within declarations 190 | 'declaration-property-unit-whitelist': {}, 191 | // specify a blacklist of disallowed property and value pairs within declarations 192 | 'declaration-property-value-blacklist': {}, 193 | // specify a whitelist of allowed property and value pairs within declarations 194 | 'declaration-property-value-whitelist': {}, 195 | 196 | // Declaration Block 197 | // http://stylelint.io/user-guide/rules/#declaration-block 198 | 199 | // disallow duplicate properties within declaration blocks 200 | 'declaration-block-no-duplicate-properties': true, 201 | // disallow longhand properties that can be combined into one shorthand property 202 | 'declaration-block-no-redundant-longhand-properties': true, 203 | // disallow shorthand properties that override related longhand properties within declaration blocks 204 | 'declaration-block-no-shorthand-property-overrides': true, 205 | // specify the order of properties within declaration blocks 206 | 'declaration-block-properties-order': null, 207 | // require a newline or disallow whitespace after the semicolons of declaration blocks 208 | 'declaration-block-semicolon-newline-after': 'always', 209 | // require a newline or disallow whitespace before the semicolons of declaration blocks 210 | 'declaration-block-semicolon-newline-before': 'never-multi-line', 211 | // require a single space or disallow whitespace after the semicolons of declaration blocks 212 | 'declaration-block-semicolon-space-after': 'always-single-line', 213 | // require a single space or disallow whitespace before the semicolons of declaration blocks 214 | 'declaration-block-semicolon-space-before': 'never', 215 | // limit the number of declaration within single line declaration blocks 216 | 'declaration-block-single-line-max-declarations': null, 217 | // require or disallow a trailing semicolon within declaration blocks 218 | 'declaration-block-trailing-semicolon': 'always', 219 | 220 | // Block 221 | // http://stylelint.io/user-guide/rules/#block 222 | 223 | // require or disallow an empty line before the closing brace of blocks 224 | 'block-closing-brace-empty-line-before': 'never', 225 | // require a newline or disallow whitespace after the closing brace of blocks 226 | 'block-closing-brace-newline-after': 'always', 227 | // require a newline or disallow whitespace before the closing brace of blocks 228 | 'block-closing-brace-newline-before': 'always', 229 | // require a single space or disallow whitespace after the closing brace of blocks 230 | 'block-closing-brace-space-after': 'always-single-line', 231 | // require a single space or disallow whitespace before the closing brace of blocks 232 | 'block-closing-brace-space-before': 'always-single-line', 233 | // disallow empty blocks 234 | 'block-no-empty': true, 235 | // require a newline after the opening brace of blocks 236 | 'block-opening-brace-newline-after': 'always', 237 | // require a newline or disallow whitespace before the opening brace of blocks 238 | 'block-opening-brace-newline-before': null, 239 | // require a single space or disallow whitespace after the opening brace of blocks 240 | 'block-opening-brace-space-after': 'always-single-line', 241 | // require a single space or disallow whitespace before the opening brace of blocks 242 | 'block-opening-brace-space-before': 'always', 243 | 244 | // Selector 245 | // http://stylelint.io/user-guide/rules/#selector 246 | 247 | // require a single space or disallow whitespace on the inside of the brackets within attribute selectors 248 | 'selector-attribute-brackets-space-inside': 'never', 249 | // specify a blacklist of disallowed attribute operators 250 | 'selector-attribute-operator-blacklist': [], 251 | // require a single space or disallow whitespace after operators within attribute selectors 252 | 'selector-attribute-operator-space-after': 'never', 253 | // require a single space or disallow whitespace before operators within attribute selectors 254 | 'selector-attribute-operator-space-before': 'never', 255 | // specify a whitelist of allowed attribute operators 256 | 'selector-attribute-operator-whitelist': null, 257 | // require or disallow quotes for attribute values 258 | 'selector-attribute-quotes': 'always', 259 | // specify a pattern for class selectors 260 | 'selector-class-pattern': null, 261 | // require a single space or disallow whitespace after the combinators of selectors 262 | 'selector-combinator-space-after': 'always', 263 | // require a single space or disallow whitespace before the combinators of selectors 264 | 'selector-combinator-space-before': 'always', 265 | // disallow non-space characters for descendant combinators of selectors 266 | 'selector-descendant-combinator-no-non-space': true, 267 | // specify a pattern for id selectors 268 | 'selector-id-pattern': null, 269 | // limit the number of compound selectors in a selector 270 | 'selector-max-compound-selectors': 3, 271 | // limit the number of id selectors in a selector 272 | 'selector-max-id': 0, 273 | // limit the specificity of selectors 274 | 'selector-max-specificity': null, 275 | // specify a pattern for the selectors of rules nested within rules 276 | 'selector-nested-pattern': null, 277 | // disallow attribute selectors 278 | 'selector-no-attribute': null, 279 | // disallow combinators in selectors 280 | 'selector-no-combinator': null, 281 | // disallow qualifying a selector by type 282 | 'selector-no-qualifying-type': true, 283 | // disallow type selectors 284 | 'selector-no-type': null, 285 | // disallow the universal selector 286 | 'selector-no-universal': null, 287 | // disallow vendor prefixes for selectors 288 | 'selector-no-vendor-prefix': true, 289 | // specify a blacklist of disallowed pseudo-class selectors 290 | 'selector-pseudo-class-blacklist': [], 291 | // specify lowercase or uppercase for pseudo-class selectors 292 | 'selector-pseudo-class-case': 'lower', 293 | // disallow unknown pseudo-class selectors 294 | 'selector-pseudo-class-no-unknown': true, 295 | // require a single space or disallow whitespace on the inside of the parentheses within pseudo-class selectors 296 | 'selector-pseudo-class-parentheses-space-inside': 'never', 297 | // specify a whitelist of allowed pseudo-class selectors 298 | 'selector-pseudo-class-whitelist': null, 299 | // specify lowercase or uppercase for pseudo-element selectors 300 | 'selector-pseudo-element-case': 'lower', 301 | // specify single or double colon notation for applicable pseudo-elements 302 | 'selector-pseudo-element-colon-notation': 'double', 303 | // disallow unknown pseudo-element selectors 304 | 'selector-pseudo-element-no-unknown': true, 305 | // specify lowercase or uppercase for type selector 306 | 'selector-type-case': 'lower', 307 | // disallow unknown type selectors 308 | 'selector-type-no-unknown': true, 309 | // limit the number of adjacent empty lines within selectors 310 | 'selector-max-empty-lines': 0, 311 | 312 | // Selector List 313 | // http://stylelint.io/user-guide/rules/#selector-list 314 | 315 | // require a newline or disallow whitespace after the commas of selector lists 316 | 'selector-list-comma-newline-after': 'always-multi-line', 317 | // require a newline or disallow whitespace before the commas of selector lists 318 | 'selector-list-comma-newline-before': 'never-multi-line', 319 | // require a single space or disallow whitespace after the commas of selector lists 320 | 'selector-list-comma-space-after': 'always-single-line', 321 | // require a single space or disallow whitespace before the commas of selector lists 322 | 'selector-list-comma-space-before': 'never', 323 | 324 | // Rule 325 | // http://stylelint.io/user-guide/rules/#rule 326 | 327 | // require or disallow an empty line before rules 328 | 'rule-empty-line-before': null, 329 | 330 | // Media Feature 331 | // http://stylelint.io/user-guide/rules/#media-feature 332 | 333 | // require a single space or disallow whitespace after the colon in media features 334 | 'media-feature-colon-space-after': 'always', 335 | // require a single space or disallow whitespace before the colon in media features 336 | 'media-feature-colon-space-before': 'never', 337 | // specify a blacklist of disallowed media feature names 338 | 'media-feature-name-blacklist': null, 339 | // specify lowercase or uppercase for media feature names 340 | 'media-feature-name-case': 'lower', 341 | // disallow unknown media feature names 342 | 'media-feature-name-no-unknown': null, 343 | // disallow vendor prefixes for media feature names 344 | 'media-feature-name-no-vendor-prefix': true, 345 | // specify a whitelist of allowed media feature names 346 | 'media-feature-name-whitelist': null, 347 | // require a single space or disallow whitespace on the inside of the parentheses within media features 348 | 'media-feature-parentheses-space-inside': 'never', 349 | // require a single space or disallow whitespace after the range operator in media features 350 | 'media-feature-range-operator-space-after': 'always', 351 | // require a single space or disallow whitespace before the range operator in media features 352 | 'media-feature-range-operator-space-before': 'always', 353 | 354 | // Custom Media 355 | // http://stylelint.io/user-guide/rules/#custom-media 356 | 357 | // specify a pattern for custom media query names 358 | 'custom-media-pattern': null, 359 | 360 | // Media Query List 361 | // http://stylelint.io/user-guide/rules/#media-query-list 362 | 363 | // require a newline or disallow whitespace after the commas of media query lists 364 | 'media-query-list-comma-newline-after': 'always-multi-line', 365 | // require a newline or disallow whitespace before the commas of media query lists 366 | 'media-query-list-comma-newline-before': 'never-multi-line', 367 | // require a single space or disallow whitespace after the commas of media query lists 368 | 'media-query-list-comma-space-after': 'always-single-line', 369 | // require a single space or disallow whitespace before the commas of media query lists 370 | 'media-query-list-comma-space-before': 'never', 371 | 372 | // At-Rule 373 | // http://stylelint.io/user-guide/rules/#at-rule 374 | 375 | // specify a blacklist of disallowed at-rules 376 | 'at-rule-blacklist': [], 377 | // require or disallow an empty line before at-rules 378 | 'at-rule-empty-line-before': 'always', 379 | // specify lowercase or uppercase for at-rules names 380 | 'at-rule-name-case': 'lower', 381 | // require a newline after at-rule names 382 | 'at-rule-name-newline-after': 'always-multi-line', 383 | // require a single space after at-rule names 384 | 'at-rule-name-space-after': 'always-single-line', 385 | // disallow unknown at-rules 386 | 'at-rule-no-unknown': true, 387 | // disallow vendor prefixes for at-rules 388 | 'at-rule-no-vendor-prefix': true, 389 | // require a newline after the semicolon of at-rules 390 | 'at-rule-semicolon-newline-after': 'always', 391 | // require a single space or disallow whitespace before the semicolons of at rules 392 | 'at-rule-semicolon-space-before': 'never', 393 | // specify a whitelist of allowed at-rules 394 | 'at-rule-whitelist': null, 395 | 396 | // Comment 397 | // http://stylelint.io/user-guide/rules/#comment 398 | 399 | // require or disallow an empty line before comments 400 | 'comment-empty-line-before': 'always', 401 | // disallow empty comments 402 | 'comment-no-empty': true, 403 | // require or disallow whitespace on the inside of comment markers 404 | 'comment-whitespace-inside': 'always', 405 | // specify a blacklist of disallowed words within comments 406 | 'comment-word-blacklist': null, 407 | 408 | // General / Sheet 409 | //http://stylelint.io/user-guide/rules/#general--sheet 410 | 411 | // specify indentation 412 | 'indentation': 2, 413 | // limit the number of adjacent empty lines 414 | 'max-empty-lines': 2, 415 | // limit the length of a line 416 | 'max-line-length': null, 417 | // limit the depth of nesting 418 | 'max-nesting-depth': 2, 419 | // disallow selectors of lower specificity from coming after overriding selectors of higher specificity 420 | 'no-descending-specificity': null, 421 | // disallow duplicate selectors 422 | 'no-duplicate-selectors': true, 423 | // disallow empty sources 424 | 'no-empty-source': true, 425 | // disallow end-of-line whitespace 426 | 'no-eol-whitespace': true, 427 | // disallow extra semicolons 428 | 'no-extra-semicolons': true, 429 | // disallow colors that are suspiciously close to being identical 430 | 'no-indistinguishable-colors': null, 431 | // disallow double-slash comments (//...) which are not supported by CSS 432 | 'no-invalid-double-slash-comments': null, 433 | // disallow missing end-of-source newlines 434 | 'no-missing-end-of-source-newline': true, 435 | // disallow animation names that do not correspond to a @keyframes declaration 436 | 'no-unknown-animations': true, 437 | // disallow features that are unsupported by the browsers that you are targeting 438 | 'no-unsupported-browser-features': null 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | script: 5 | - npm run check 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | [CC0 1.0 Universal (CC0 1.0)](http://creativecommons.org/publicdomain/zero/1.0/) 2 | 3 | # Public Domain Dedication 4 | 5 | ## No Copyright 6 | 7 | The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. 8 | 9 | You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See Other Information below. 10 | 11 | ## Other Information 12 | 13 | - In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. 14 | - Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. 15 | - When using or citing the work, you should not imply endorsement by the author or the affirmer. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Universal React + Redux Boilerplate 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/universal-react-redux-boilerplate.svg)](https://npmjs.org/package/universal-react-redux-boilerplate) 4 | [![Build Status](https://img.shields.io/travis/CrocoDillon/universal-react-redux-boilerplate.svg?style=flat)](https://travis-ci.org/CrocoDillon/universal-react-redux-boilerplate) 5 | [![dependencies Status](https://david-dm.org/CrocoDillon/universal-react-redux-boilerplate/status.svg)](https://david-dm.org/CrocoDillon/universal-react-redux-boilerplate) 6 | [![devDependencies Status](https://david-dm.org/CrocoDillon/universal-react-redux-boilerplate/dev-status.svg)](https://david-dm.org/CrocoDillon/universal-react-redux-boilerplate?type=dev) 7 | [![Package Quality](http://npm.packagequality.com/shield/universal-react-redux-boilerplate.svg)](http://packagequality.com/#?package=universal-react-redux-boilerplate) 8 | 9 | Boilerplate with all the good stuff but without the JavaScript fatigue. Made by [Dillon de Voor](https://twitter.com/CrocoDillon), follow me for updates and more! 10 | 11 | Includes Koa 2, React 15, Webpack 2 and React Hot Loader 3. See section [**“Feature rich”**](#feature-rich) for what other awesome features you can expect. 12 | 13 | _This is v2 of this boilerplate. See the [v1 branch](https://github.com/CrocoDillon/universal-react-redux-boilerplate/tree/v1) for the previous version._ 14 | 15 | ## Why another boilerplate? 16 | 17 | Because this boilerplate is different than most boilerplates out there. Most boilerplates will just give you a rather complex starting point but not how they got to that starting point. Ever heard ot _JavaScript fatigue_? These boilerplates are not helping. 18 | 19 | This boilerplate attempts to solve this by providing a series of logical steps to build your own boilerplate. Of course you’re free to just use this one too but I encourage you to at least take a look at each step to get a basic understanding of each part of the app. 20 | 21 | The steps are in the form of commits and I really hope this makes React and all the tooling around React more comprehensible, even for beginners. If you like it, please spread the word! 22 | 23 | ## Feature rich 24 | 25 | Despite my aim to make this the easiest understandable boilerplate you will find lots of features useful for development and for production. The finished boilerplate will include the following: 26 | 27 | - **Universal** or isomorphic. However you’re calling it... “Server-side rendering is not a fallback; client-side rendering is an enhancement” ([source](https://adactio.com/journal/9963)). 28 | - [**React**](https://facebook.github.io/react/) for obvious reasons, [**React Router**](https://github.com/reactjs/react-router) to deal with routing and [**React Helmet**](https://github.com/nfl/react-helmet) to manage changes in the document head. 29 | - Hot Module Replacement using [**React Hot Loader 3**](http://gaearon.github.io/react-hot-loader/). 30 | - [**Redux**](http://redux.js.org/) to keep application state predictable and [**Redux DevTools**](https://github.com/gaearon/redux-devtools) to help with debugging. 31 | - Server using [**Koa**](http://koajs.com/), the next generation web framework developed by the team behind Express. 32 | - [**Babel**](http://babeljs.io/) so we can use the latest ECMAScript additions and even the ones that have not made it to the standard yet, use with caution. 33 | - Client bundle created by [**Webpack 2**](http://webpack.github.io/), which includes _tree-shaking_. 34 | - [**Mocha**](https://mochajs.org/) and [**Enzyme**](http://airbnb.io/enzyme/) to run your tests so your app (and you) stay healthy. 35 | - It’s _your_ code style, but [**ESLint**](http://eslint.org/) helps keep it consistent. 36 | - Static type checking using [**Flow**](http://flowtype.org/) can help you find errors quickly. 37 | - Development and production build processes 38 | 39 | ## Let’s get started! 40 | 41 | ```bash 42 | git clone https://github.com/CrocoDillon/universal-react-redux-boilerplate.git 43 | cd universal-react-redux-boilerplate 44 | npm install 45 | ``` 46 | 47 | ### Running the development server 48 | 49 | ```bash 50 | npm run dev 51 | ``` 52 | 53 | ### Running the production server 54 | 55 | ```bash 56 | npm run build 57 | npm start 58 | ``` 59 | 60 | ### Checking code quality 61 | 62 | Run ESLint, Mocha and Flow: 63 | 64 | ```bash 65 | npm run check 66 | ``` 67 | 68 | Or separately: 69 | 70 | ```bash 71 | npm run lint 72 | npm run test 73 | npm run flow 74 | ``` 75 | 76 | Or even lint JS and CSS separately: 77 | 78 | ```bash 79 | npm run lint:js 80 | npm run lint:css 81 | ``` 82 | 83 | ## Step by step 84 | 85 | Here is a summary of each step, where each step also represents one commit. Each step is like a milestone that brings us closer to the finished boilerplate. Feel free to modify to your needs! 86 | 87 | ### React with JSX, transpiled by Babel and bundled with Webpack 88 | 89 | You might want to use ECMAScript 6 (and beyond) features like modules, at the very least you will want to use JSX (React just isn’t the same without it). To be able to do that we will transpile our code using Babel. In addition to Babel we will use Webpack to create a bundle for the client. 90 | 91 | Because the options passed to Babel need to be different for the server and the client, we won’t include a `.babelrc` options file. Instead, we pass the options to `babel-register` for the server and in the Webpack config for the client. This might lead to some duplication but makes the separation more verbose. 92 | 93 | To make use of a Webpack 2 feature called [_tree-shaking_](https://gist.github.com/sokra/27b24881210b56bbaff7), we have to use the Babel preset `es2015-webpack` so Babel doesn’t transform ES6 modules to CommonJS. 94 | 95 | Oh and do yourself a favor, make it a habit to always specify `displayName` and `propTypes` (if applicable) to React components. 96 | 97 | ### Setting up ESLint 98 | 99 | Before starting your project it’s probably a good idea to set up some rules about code style. ESLint is awesome to enforce these rules because it’s pluggable and fully configurable. Linting is not about “best practices” and other people’s opinions, it’s about code style consistency, maintainability and preventing errors. The rules are up to you or your team, ESLint will do the rest. 100 | 101 | The `.eslintrc.js` file in this boilerplate is absolutely huge but don’t be intimidated by it. We could go the easy way and _extend_ `eslint:recommended` or for example `eslint-config-airbnb` but remember, it’s about your rules. I included every single standard ESLint rule and every single `eslint-plugin-react` rule whether they are enabled or not. That way all the rules are in one place which makes it easier to make them fit your needs. 102 | 103 | ### Setting up Mocha and Enzyme 104 | 105 | It’s also a good idea to start testing early. We use Mocha and Enzyme (which provides some nice testing utilities for React), along with Chai as assertion library. Even though we are not using it yet I also added Sinon to create spies, stubs and mocks and some Chai plugins to make assertions more expressive. 106 | 107 | Files containing tests are named `*.spec.js` and kept as close to the module they are testing as possible. If there are more than a few test files in one directory it can still be a good idea to put them in a subdirectory to keep things clean. 108 | 109 | ### Setting up Flow 110 | 111 | The last step in ensuring code quality is static type checking with Flow. This might require some annotations in your code which will be stripped out by a Babel plugin included in the React preset. Not everyone will be comfortable with that so let’s call this step optional. 112 | 113 | Now is a good time to add continuous integration which will run all our checks whenever we push to GitHub. And the badges... they are awesome! 114 | 115 | ### Development strategy using Webpack middleware and React Hot Loader 116 | 117 | Restarting the server and rebuilding the bundle every time you make a code change is kinda boring, we need something better. Using Webpack’s dev middleware we can automatically update the bundle on code changes. The bundle is served from memory and rebuilds are much faster. And it gets even better, using Webpack’s hot middleware together with React Hot Loader we can apply the updates to a running React app, no need for a page refresh. It is _awesome_! Hot reloading on the server is done by plugging into the Webpack compiler and trashing the require cache when Webpack detects changes in the app source. 118 | 119 | ### CSS Modules, Sass and Autoprefixer with CSS hot loading 120 | 121 | This is a little bit tricky to set up, but bear with me. The goal is to have Sass, CSS Modules and Autoprefixer working together with server-side rendering, Webpack, hot loading, sourcemaps and of course linting. 122 | 123 | Most of the work is already done by adding some additional loaders to Webpack. Webpack knows how to import (or require) an asset other than JavaScript as it passes it trough a series of loaders specified in the config file. Webpack makes those imports work for the client, but for the server we need something else. This is where `webpack-isomorphic-tools` come in handy. This module keeps track of what is generated by Webpack (and its loaders) and makes importing these assets work on the server as well. 124 | 125 | Setting up `webpack-isomorphic-tools` is not trivial, but it’s worth it. 126 | 127 | ### Production strategy using hashes for cache invalidation 128 | 129 | In production we want to get rid of the overhead of transpiling by pre-building server and client code. We also want hashed assets to make use of far-future caching. These asserts can be deployed to a CDN, by changing the `publicPath` in `webpack.config.js` to point to the CDN the paths are updated automatically. 130 | 131 | ### Routing with React Router 132 | 133 | While not your only choice, React Router definitely is the most popular choice when it comes to routing. It supports server-side routing and it supports browser History API on the client. 134 | 135 | What React Router doesn’t solve out of the box is handling of meta data like title or status. For title (and other `` related meta data) there is React Helmet and for HTTP status when rendering on the server we use a small helper utility. Both are used React component’s render functions to support dynamically changing them, for example when a blog article is not found. 136 | 137 | ### Managing application state with Redux 138 | 139 | Sooner or later you might need some more advanced state management than React gives you with component state. Redux is a good choice. Since we’re building a blog here it makes sense to keep loaded articles in this state. Redux features a store, reducers and actions, which are often grouped together. For this boilerplate I want to try a different approach and instead of grouping by nature we will group by domain. If you want to know more about this approach I encourage you to read [“Rules For Structuring (Redux) Applications”](http://jaysoo.ca/2016/02/28/organizing-redux-application/) and [“A Better File Structure For React/Redux Applications”](http://marmelab.com/blog/2015/12/17/react-directory-structure.html). 140 | -------------------------------------------------------------------------------- /bin/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path') 4 | 5 | // Globals 6 | const NODE_ENV = process.env.NODE_ENV || 'development' 7 | global.__DEV__ = NODE_ENV !== 'production' 8 | global.__PROD__ = NODE_ENV === 'production' 9 | global.__SERVER__ = true 10 | global.__CLIENT__ = false 11 | 12 | if (__DEV__) { 13 | // Bootstrap babel-register 14 | require('babel-register') 15 | 16 | // Bootstrap webpack (required for webpack-isomorphic-tools) 17 | require('../webpack.server') 18 | } 19 | 20 | const basePath = path.resolve(__dirname, __DEV__ ? '../src' : '../lib') 21 | const WebpackTools = require('webpack-isomorphic-tools') 22 | const webpackToolsConfig = require('../webpack.isomorphic.tools') 23 | 24 | global.webpackTools = new WebpackTools(webpackToolsConfig) 25 | .server(basePath, () => { 26 | const server = require(basePath) 27 | 28 | server.listen(3000, () => { 29 | console.info('Server is running!') // eslint-disable-line no-console 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /interfaces/CSSModule.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare module CSSModule { 3 | declare var exports: { [key: string]: string }; 4 | } 5 | -------------------------------------------------------------------------------- /interfaces/globals.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare var __DEV__: boolean; 3 | declare var __PROD__: boolean; 4 | declare var __SERVER__: boolean; 5 | declare var __CLIENT__: boolean; 6 | -------------------------------------------------------------------------------- /mocha.config.js: -------------------------------------------------------------------------------- 1 | // Bootstrap babel-register 2 | require('babel-register') 3 | 4 | // Ensure correct NODE_ENV 5 | if (process.env.NODE_ENV !== 'test') { 6 | throw new Error('Running tests require NODE_ENV=test') 7 | } 8 | 9 | // Globals 10 | global.__DEV__ = true 11 | global.__PROD__ = false 12 | global.__SERVER__ = true 13 | global.__CLIENT__ = false 14 | 15 | // Set up chai and sinon 16 | const chai = require('chai') 17 | const sinon = require('sinon') 18 | 19 | chai.use(require('chai-as-promised')) 20 | chai.use(require('sinon-chai')) 21 | chai.use(require('chai-enzyme')()) 22 | 23 | global.expect = chai.expect 24 | global.sinon = sinon 25 | 26 | // Set up jsdom 27 | const jsdom = require('jsdom') 28 | 29 | const document = new jsdom.JSDOM() 30 | 31 | global.document = document 32 | global.window = document.defaultView 33 | global.navigator = { userAgent: 'node.js' } 34 | 35 | // Hook for CSS Module imports enables using classes in tests 36 | const hook = require('css-modules-require-hook') 37 | const sass = require('node-sass') 38 | 39 | hook({ 40 | extensions: ['.scss'], 41 | generateScopedName: '[local]__[hash:base64:4]', 42 | preprocessCss: (css, file) => sass.renderSync({ file }).css 43 | }) 44 | 45 | // Load tests 46 | const glob = require('glob') 47 | glob.sync('./src/**/*.spec.js').forEach(require) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "universal-react-redux-boilerplate", 3 | "version": "2.0.0", 4 | "description": "Step by step creation of a Universal React + Redux Boilerplate", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node --trace-warnings ./bin/server", 8 | "dev-debug": "node --inspect --debug-brk --trace-warnings ./bin/server", 9 | "start": "cross-env NODE_ENV=production node ./bin/server", 10 | "build": "npm run build:server && npm run build:client", 11 | "build:server": "cross-env NODE_ENV=production babel src --out-dir lib --copy-files", 12 | "build:client": "cross-env NODE_ENV=production webpack --progress", 13 | "clean": "rimraf dist lib", 14 | "check": "npm run lint && npm run test && npm run flow", 15 | "lint": "npm run lint:js && npm run lint:css", 16 | "lint:js": "eslint --cache .", 17 | "lint:css": "stylelint src/**/*.scss --syntax scss", 18 | "test": "cross-env NODE_ENV=test mocha mocha.config.js", 19 | "flow": "flow", 20 | "flow:stop": "flow stop" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/CrocoDillon/universal-react-redux-boilerplate.git" 25 | }, 26 | "keywords": [ 27 | "isomorphic", 28 | "universal", 29 | "react", 30 | "redux", 31 | "boilerplate" 32 | ], 33 | "author": "Dillon de Voor (http://crocodillon.com)", 34 | "license": "CC0-1.0", 35 | "dependencies": { 36 | "isomorphic-fetch": "^2.2.1", 37 | "koa": "^2.3.0", 38 | "koa-static": "^4.0.0", 39 | "prop-types": "^15.5.10", 40 | "react": "^15.6.1", 41 | "react-dom": "^15.6.1", 42 | "react-helmet": "^5.1.3", 43 | "react-redux": "^5.0.5", 44 | "react-router-config": "^1.0.0-beta.3", 45 | "react-router-dom": "^4.1.1", 46 | "redux": "^3.7.1", 47 | "redux-thunk": "^2.2.0", 48 | "webpack-isomorphic-tools": "^3.0.3" 49 | }, 50 | "devDependencies": { 51 | "autoprefixer": "^7.1.2", 52 | "babel-cli": "^6.24.1", 53 | "babel-eslint": "^7.2.3", 54 | "babel-loader": "^7.1.1", 55 | "babel-plugin-add-module-exports": "^0.2.1", 56 | "babel-preset-es2015": "^6.24.1", 57 | "babel-preset-es2015-node5": "^1.2.0", 58 | "babel-preset-react": "^6.24.1", 59 | "babel-preset-stage-0": "^6.24.1", 60 | "babel-register": "^6.24.1", 61 | "chai": "^4.0.2", 62 | "chai-as-promised": "^7.1.1", 63 | "chai-enzyme": "^0.8.0", 64 | "cross-env": "^5.0.1", 65 | "css-loader": "^0.28.4", 66 | "css-modules-require-hook": "^4.0.6", 67 | "enzyme": "^2.9.1", 68 | "eslint": "^4.1.1", 69 | "eslint-plugin-flowtype": "^2.34.1", 70 | "eslint-plugin-react": "^7.1.0", 71 | "extract-text-webpack-plugin": "^2.1.2", 72 | "flow-bin": "^0.49.1", 73 | "glob": "^7.1.2", 74 | "jsdom": "^11.1.0", 75 | "mocha": "^3.4.2", 76 | "node-sass": "^4.5.3", 77 | "postcss-loader": "^2.0.6", 78 | "react-hot-loader": "^3.0.0-beta.7", 79 | "react-test-renderer": "^15.6.1", 80 | "rimraf": "^2.6.1", 81 | "sass-loader": "^6.0.6", 82 | "sinon": "^2.3.6", 83 | "sinon-chai": "^2.11.0", 84 | "style-loader": "^0.18.2", 85 | "stylelint": "^7.12.0", 86 | "webpack": "^3.1.0", 87 | "webpack-dev-middleware": "^1.11.0", 88 | "webpack-hot-middleware": "^2.18.2" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({ 4 | browsers: ['last 2 versions'] 5 | }) 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/DataLoader.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import { object } from 'prop-types' 3 | import { withRouter } from 'react-router-dom' 4 | import { matchRoutes, renderRoutes } from 'react-router-config' 5 | 6 | import routes from './routes' 7 | 8 | export const fetchData = (store, location) => { 9 | const branch = matchRoutes(routes, location) 10 | 11 | const promises = branch.map(({ route, match }) => { 12 | if (route.component.fetchData) { 13 | return route.component.fetchData(store, match) 14 | } 15 | }) 16 | 17 | return Promise.all(promises) 18 | } 19 | 20 | class DataLoader extends Component { 21 | 22 | static displayName = 'DataLoader'; 23 | 24 | static contextTypes = { 25 | store: object 26 | }; 27 | 28 | static propTypes = { 29 | location: object.isRequired 30 | }; 31 | 32 | componentWillReceiveProps(nextProps) { 33 | const navigated = nextProps.location !== this.props.location 34 | 35 | if (navigated) { 36 | const { store } = this.context 37 | fetchData(store, nextProps.location.pathname) 38 | } 39 | } 40 | 41 | render() { 42 | return renderRoutes(routes) 43 | } 44 | } 45 | 46 | export default withRouter(DataLoader) 47 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | // Fake database 2 | const articles = [ 3 | { 4 | slug: 'the-wizard-of-oz', 5 | title: 'The Wizard of Oz', 6 | body: 'Dorothy followed her through many of the beautiful rooms in her castle until they came to the kitchen, where the Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.' 7 | }, 8 | { 9 | slug: 'alice-in-wonderland', 10 | title: 'Alice in Wonderland', 11 | body: 'Alice thought the whole thing very absurd, but they all looked so grave that she did not dare to laugh; and, as she could not think of anything to say, she simply bowed, and took the thimble, looking as solemn as she could.' 12 | }, 13 | { 14 | slug: 'moby-dick', 15 | title: 'Moby Dick', 16 | body: 'In man, breathing is incessantly going on—one breath only serving for two or three pulsations; so that whatever other business he has to attend to, waking or sleeping, breathe he must, or die he will. But the Sperm Whale only breathes about one seventh or Sunday of his time.' 17 | }, 18 | { 19 | slug: 'around-the-world-in-80-days', 20 | title: 'Around the World in 80 Days', 21 | body: 'The old rajah was not dead, then, since he rose of a sudden, like a spectre, took up his wife in his arms, and descended from the pyre in the midst of the clouds of smoke, which only heightened his ghostly appearance.' 22 | }, 23 | { 24 | slug: 'the-war-of-the-worlds', 25 | title: 'The War of the Worlds', 26 | body: 'And while the Martians behind me were thus preparing for their next sally, and in front of me Humanity gathered for the battle, I made my way with infinite pains and labour from the fire and smoke of burning Weybridge towards London.' 27 | } 28 | ] 29 | 30 | // Warning: in a real production app you would probably want something more 31 | // advanced like `koa-router` to handle routing, `mongoose` for the models 32 | // (if your database is MongoDB) and controllers/actions for each resource. 33 | export default (ctx, next) => { 34 | const [prefix, resource, slug] = ctx.path.substring(1).split('/') 35 | 36 | if (prefix !== 'api') { 37 | return next() 38 | } 39 | 40 | switch (resource) { 41 | case 'articles': 42 | if (slug) { 43 | const article = articles.find(a => a.slug === slug) 44 | ctx.assert(article, 404, 'Article not found') 45 | 46 | ctx.body = article 47 | } else { 48 | ctx.body = articles.map(({ body, ...rest }) => rest) // eslint-disable-line no-unused-vars 49 | } 50 | break 51 | default: 52 | ctx.throw(404) 53 | break 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import { BrowserRouter } from 'react-router-dom' 5 | 6 | import DataLoader from './DataLoader' 7 | import configureStore from './store' 8 | 9 | const store = configureStore(window.__INITIAL_STATE__) 10 | 11 | if (__DEV__) { 12 | const { AppContainer } = require('react-hot-loader') 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | , 21 | document.getElementById('app') 22 | ) 23 | // Hot reloading on the client 24 | if (module.hot) { 25 | module.hot.accept() 26 | } 27 | } else { 28 | ReactDOM.render( 29 | 30 | 31 | 32 | 33 | , 34 | document.getElementById('app') 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/helpers/status.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type HTTPStatus = 100|101|102|200|201|202|203|204|205|206|207|208|226|300|301|302|303|304|305|307|308|400|401|402|403|404|405|406|407|408|409|410|411|412|413|414|415|416|417|418|421|422|423|424|426|428|429|431|444|451|499|500|501|502|503|504|505|506|507|508|510|511|599 3 | 4 | let status: HTTPStatus = 200 5 | 6 | export default function (value: HTTPStatus): void { 7 | status = value 8 | } 9 | 10 | export function rewind(): HTTPStatus { 11 | const value = status 12 | status = 200 13 | return value 14 | } 15 | -------------------------------------------------------------------------------- /src/helpers/status.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global expect, sinon */ 3 | import status, { rewind } from './status' 4 | 5 | describe('HTTPStatus helper', () => { 6 | 7 | beforeEach(() => { 8 | rewind() // Ensure a fresh start 9 | }) 10 | 11 | it('defaults to 200', () => { 12 | expect(rewind()).to.be.equal(200) 13 | }) 14 | 15 | it('allows you to overwrite status', () => { 16 | status(400) 17 | expect(rewind()).to.be.equal(400) 18 | status(404) 19 | expect(rewind()).to.be.equal(404) 20 | status(500) 21 | expect(rewind()).to.be.equal(500) 22 | }) 23 | 24 | it('defaults back to 200 after rewind', () => { 25 | status(404) 26 | rewind() 27 | expect(rewind()).to.be.equal(200) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | 3 | import api from './api' 4 | 5 | const server = new Koa() 6 | 7 | if (__DEV__) { 8 | const webpack = require('../webpack.server').default 9 | webpack(server) 10 | } else { 11 | const serve = require('koa-static') 12 | server.use(serve('dist')) 13 | } 14 | 15 | server.use(api) 16 | 17 | server.use(async ctx => { 18 | // Dynamic require enables hot reloading on the server 19 | const { render } = require('./server') 20 | const { status, redirect, body } = await render(ctx.url) 21 | 22 | ctx.status = status 23 | 24 | if (redirect) { 25 | ctx.redirect(redirect) 26 | } else { 27 | ctx.body = body 28 | } 29 | }) 30 | 31 | export default server 32 | -------------------------------------------------------------------------------- /src/modules/App/App.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react' 3 | import { object } from 'prop-types' 4 | import { renderRoutes } from 'react-router-config' 5 | 6 | import Header from './components/Header' 7 | 8 | class App extends Component { 9 | 10 | static displayName = 'App'; 11 | 12 | static propTypes = { 13 | route: object.isRequired 14 | }; 15 | 16 | render() { 17 | const styles = require('./App.scss') 18 | 19 | const { route } = this.props 20 | 21 | return ( 22 |
23 |
24 | { renderRoutes(route.routes) } 25 |
26 | ) 27 | } 28 | } 29 | 30 | export default App 31 | -------------------------------------------------------------------------------- /src/modules/App/App.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: inherit; 5 | } 6 | 7 | html { 8 | box-sizing: border-box; 9 | color: #464646; 10 | font: 400 100%/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; /* stylelint-disable-line value-keyword-case */ 11 | } 12 | 13 | body { 14 | margin: 0; 15 | } 16 | 17 | main { 18 | padding: 0.5em; 19 | } 20 | 21 | h1 { 22 | margin: 0 0 0.75em; 23 | color: #546e7a; 24 | font-size: 2em; 25 | font-weight: 700; 26 | } 27 | 28 | a { 29 | color: inherit; 30 | } 31 | 32 | .container { 33 | max-width: 80em; 34 | margin: 0 auto; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/App/App.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* global expect, sinon */ 3 | import React from 'react' 4 | import { shallow } from 'enzyme' 5 | 6 | import App from './App' 7 | 8 | describe('', () => { 9 | 10 | function setup() { 11 | const wrapper = shallow() 12 | const instance = wrapper.instance() 13 | 14 | return { wrapper, instance } 15 | } 16 | 17 | it('renders', () => { 18 | const { wrapper, instance } = setup() 19 | 20 | expect(wrapper).to.be.ok 21 | expect(instance).to.be.ok 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/modules/App/Html.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react' 3 | import { string, object, objectOf, shape } from 'prop-types' 4 | 5 | // `getStyles` is to prevent FOUC in development 6 | const getStyles = assets => 7 | Object 8 | .keys(assets) 9 | .map(key => assets[key]) 10 | .reduce((acc, { _style }) => { 11 | if (_style) { 12 | acc += _style 13 | } 14 | return acc 15 | }, '') 16 | 17 | const getInitialState = state => { 18 | const json = JSON.stringify(state).replace(' { 23 | const { markup, state, assets: { styles, javascript, assets }, helmet } = props 24 | 25 | return ( 26 | 27 | 28 | 29 | { helmet && helmet.title.toComponent() } 30 | 31 | { 32 | Object.keys(styles).map(key => ( 33 | 34 | )) 35 | } 36 | { __DEV__ &&