├── .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 | [](https://npmjs.org/package/universal-react-redux-boilerplate)
4 | [](https://travis-ci.org/CrocoDillon/universal-react-redux-boilerplate)
5 | [](https://david-dm.org/CrocoDillon/universal-react-redux-boilerplate)
6 | [](https://david-dm.org/CrocoDillon/universal-react-redux-boilerplate?type=dev)
7 | [](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 |