diff --git a/00 Boilerplate/.babelrc b/00 Boilerplate/.babelrc deleted file mode 100644 index 03dfd13..0000000 --- a/00 Boilerplate/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} diff --git a/00 Boilerplate/package-lock.json b/00 Boilerplate/package-lock.json deleted file mode 100644 index e5043a2..0000000 --- a/00 Boilerplate/package-lock.json +++ /dev/null @@ -1,10547 +0,0 @@ -{ - "name": "sample", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, - "requires": { - "mime-types": "2.1.18", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", - "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", - "dev": true, - "requires": { - "acorn": "5.5.3" - } - }, - "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", - "dev": true, - "requires": { - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" - } - }, - "ajv-keywords": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", - "integrity": "sha1-rCsnk5xUPpXSwG5/f1wnvkqlQ74=", - "dev": true - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", - "dev": true - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "1.9.1" - } - }, - "any-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.2.0.tgz", - "integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "3.1.10", - "normalize-path": "2.1.1" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", - "dev": true - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "1.0.3" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, - "requires": { - "util": "0.10.3" - } - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "ast-types": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.3.tgz", - "integrity": "sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA==", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "atob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.0.tgz", - "integrity": "sha512-SuiKH8vbsOyCALjA/+EINmt/Kdl+TQPrtFgW7XZZcwtryFu9e5kQoX3bjCW6mIvGH1fbeAZZuvwGR5IlBRznGw==", - "dev": true - }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000827", - "normalize-range": "0.1.2", - "num2fraction": "1.2.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "awesome-typescript-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.0.0.tgz", - "integrity": "sha512-/80vlBnWp5IlERQ0sxRDQfz5voqht02MRNgUdbn90rKHrope6eh0PYr0qepD2TpYYnCSvq0DzdWc8udHM0KefA==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "enhanced-resolve": "4.0.0", - "loader-utils": "1.1.0", - "lodash": "4.17.5", - "micromatch": "3.1.10", - "mkdirp": "0.5.1", - "source-map-support": "0.5.4" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.5", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - } - } - }, - "babel-helper-bindify-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", - "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-explode-class": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", - "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", - "dev": true, - "requires": { - "babel-helper-bindify-decorators": "6.24.1", - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-async-generators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", - "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", - "dev": true - }, - "babel-plugin-syntax-class-constructor-call": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", - "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", - "dev": true - }, - "babel-plugin-syntax-class-properties": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", - "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", - "dev": true - }, - "babel-plugin-syntax-decorators": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", - "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", - "dev": true - }, - "babel-plugin-syntax-dynamic-import": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", - "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-export-extensions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", - "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", - "dev": true - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-generator-functions": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", - "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-generators": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "6.24.1", - "babel-plugin-syntax-async-functions": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-class-constructor-call": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", - "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", - "dev": true, - "requires": { - "babel-plugin-syntax-class-constructor-call": "6.18.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-class-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", - "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-plugin-syntax-class-properties": "6.13.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-decorators": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", - "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", - "dev": true, - "requires": { - "babel-helper-explode-class": "6.24.1", - "babel-plugin-syntax-decorators": "6.13.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", - "integrity": "sha1-DYOUApt9xqvhqX7xgeAHWN0uXYo=", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "6.26.0", - "babel-runtime": "6.26.0", - "regexpu-core": "2.0.0" - }, - "dependencies": { - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - } - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", - "babel-plugin-syntax-exponentiation-operator": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-export-extensions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", - "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", - "dev": true, - "requires": { - "babel-plugin-syntax-export-extensions": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "dev": true, - "requires": { - "babel-plugin-syntax-flow": "6.18.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "6.13.0", - "babel-runtime": "6.26.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "0.10.1" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0" - } - }, - "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.11.3", - "invariant": "2.2.4", - "semver": "5.5.0" - }, - "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "1.0.30000827", - "electron-to-chromium": "1.3.42" - } - } - } - }, - "babel-preset-es2015": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-amd": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", - "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", - "babel-plugin-transform-es2015-modules-umd": "6.24.1", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "6.24.1", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "6.24.1", - "babel-plugin-transform-regenerator": "6.26.0" - } - }, - "babel-preset-stage-1": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", - "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", - "dev": true, - "requires": { - "babel-plugin-transform-class-constructor-call": "6.24.1", - "babel-plugin-transform-export-extensions": "6.22.0", - "babel-preset-stage-2": "6.24.1" - } - }, - "babel-preset-stage-2": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", - "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", - "dev": true, - "requires": { - "babel-plugin-syntax-dynamic-import": "6.18.0", - "babel-plugin-transform-class-properties": "6.24.1", - "babel-plugin-transform-decorators": "6.24.1", - "babel-preset-stage-3": "6.24.1" - } - }, - "babel-preset-stage-3": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", - "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", - "dev": true, - "requires": { - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-async-generator-functions": "6.24.1", - "babel-plugin-transform-async-to-generator": "6.24.1", - "babel-plugin-transform-exponentiation-operator": "6.24.1", - "babel-plugin-transform-object-rest-spread": "6.26.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "6.26.0", - "babel-runtime": "6.26.0", - "core-js": "2.5.5", - "home-or-tmp": "2.0.0", - "lodash": "4.17.5", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" - }, - "dependencies": { - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "2.5.5", - "regenerator-runtime": "0.11.1" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "base64-js": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz", - "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "binaryextensions": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", - "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==", - "dev": true - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.3", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "2.1.1", - "deep-equal": "1.0.1", - "dns-equal": "1.0.0", - "dns-txt": "2.0.2", - "multicast-dns": "6.2.3", - "multicast-dns-service-types": "1.1.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "bootstrap": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.0.tgz", - "integrity": "sha512-kCo82nE8qYVfOa/Z3hL98CPgPIEkh6iPdiJrUJMQ9n9r0+6PEET7cmhLlV0XVYmEj5QtKIOaSGMLxy5jSFhKog==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "1.0.3", - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "browserify-cipher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", - "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", - "dev": true, - "requires": { - "browserify-aes": "1.2.0", - "browserify-des": "1.0.0", - "evp_bytestokey": "1.0.3" - } - }, - "browserify-des": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", - "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "des.js": "1.0.0", - "inherits": "2.0.3" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "randombytes": "2.0.6" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "elliptic": "6.4.0", - "inherits": "2.0.3", - "parse-asn1": "5.1.0" - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "1.0.6" - } - }, - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "1.0.30000827", - "electron-to-chromium": "1.3.42" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "1.2.3", - "ieee754": "1.1.11", - "isarray": "1.0.0" - } - }, - "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", - "dev": true - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", - "dev": true, - "requires": { - "bluebird": "3.5.1", - "chownr": "1.0.1", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lru-cache": "4.1.2", - "mississippi": "2.0.0", - "mkdirp": "0.5.1", - "move-concurrently": "1.0.1", - "promise-inflight": "1.0.1", - "rimraf": "2.6.2", - "ssri": "5.3.0", - "unique-filename": "1.1.0", - "y18n": "4.0.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" - } - }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", - "dev": true, - "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "requires": { - "prepend-http": "2.0.0", - "query-string": "5.1.1", - "sort-keys": "2.0.0" - } - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dev": true, - "requires": { - "decode-uri-component": "0.2.0", - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" - } - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - } - } - }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, - "requires": { - "no-case": "2.3.2", - "upper-case": "1.1.3" - } - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" - } - }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-db": "1.0.30000827", - "lodash.memoize": "4.1.2", - "lodash.uniq": "4.5.0" - } - }, - "caniuse-db": { - "version": "1.0.30000827", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000827.tgz", - "integrity": "sha1-vSg53Rlgk7RMKMF/k1ExQMnZJYg=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000827", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000827.tgz", - "integrity": "sha512-j9Q9hP5AhqOARNP6fLdctr3XrGhF921sBSycudf4E+8RCWpFT3rJdTfp/5o8LDp6p0NJTpYWEpBFiM+QEDzA6g==", - "dev": true - }, - "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.3.0" - } - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", - "dev": true, - "requires": { - "anymatch": "2.0.0", - "async-each": "1.0.1", - "braces": "2.3.2", - "fsevents": "1.1.3", - "glob-parent": "3.1.0", - "inherits": "2.0.3", - "is-binary-path": "1.0.1", - "is-glob": "4.0.0", - "normalize-path": "2.1.1", - "path-is-absolute": "1.0.1", - "readdirp": "2.1.0", - "upath": "1.0.4" - } - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true - }, - "chrome-trace-event": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-0.1.2.tgz", - "integrity": "sha1-kPNohdU0WlBiEzLwcXtZWIPV2YI=", - "dev": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "1.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", - "dev": true, - "requires": { - "source-map": "0.5.7" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "2.0.0" - } - }, - "cli-spinners": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", - "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", - "dev": true - }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, - "requires": { - "colors": "1.0.3" - }, - "dependencies": { - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - } - } - }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true, - "requires": { - "slice-ansi": "0.0.4", - "string-width": "1.0.2" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", - "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", - "dev": true, - "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "1.0.0" - } - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "process-nextick-args": "2.0.0", - "readable-stream": "2.3.6" - } - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "1.5.1" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "1.0.4", - "color-convert": "1.9.1", - "color-string": "0.3.0" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "0.11.4", - "css-color-names": "0.0.4", - "has": "1.0.1" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "compressible": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", - "integrity": "sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k=", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - }, - "compression": { - "version": "1.7.2", - "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", - "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", - "dev": true, - "requires": { - "accepts": "1.3.5", - "bytes": "3.0.0", - "compressible": "2.0.13", - "debug": "2.6.9", - "on-headers": "1.0.1", - "safe-buffer": "5.1.1", - "vary": "1.1.2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "1.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "typedarray": "0.0.6" - } - }, - "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", - "dev": true - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "0.1.4" - } - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "1.2.0", - "fs-write-stream-atomic": "1.0.10", - "iferr": "0.1.5", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "create-ecdh": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", - "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "elliptic": "6.4.0" - } - }, - "create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "sha.js": "2.4.11" - } - }, - "create-hmac": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", - "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", - "dev": true, - "requires": { - "cipher-base": "1.0.4", - "create-hash": "1.1.3", - "inherits": "2.0.3", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "1.0.0", - "browserify-sign": "4.0.4", - "create-ecdh": "4.0.0", - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "diffie-hellman": "5.0.2", - "inherits": "2.0.3", - "pbkdf2": "3.0.14", - "public-encrypt": "4.0.0", - "randombytes": "2.0.6", - "randomfill": "1.0.4" - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-loader": { - "version": "0.28.11", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", - "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "css-selector-tokenizer": "0.7.0", - "cssnano": "3.10.0", - "icss-utils": "2.1.0", - "loader-utils": "1.1.0", - "lodash.camelcase": "4.3.0", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-modules-extract-imports": "1.2.0", - "postcss-modules-local-by-default": "1.2.0", - "postcss-modules-scope": "1.1.0", - "postcss-modules-values": "1.3.0", - "postcss-value-parser": "3.3.0", - "source-list-map": "2.0.0" - } - }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", - "domutils": "1.5.1", - "nth-check": "1.0.1" - } - }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "dev": true, - "requires": { - "cssesc": "0.1.0", - "fastparse": "1.1.1", - "regexpu-core": "1.0.0" - } - }, - "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", - "dev": true - }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", - "dev": true - }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "6.7.7", - "decamelize": "1.2.0", - "defined": "1.0.0", - "has": "1.0.1", - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-calc": "5.3.1", - "postcss-colormin": "2.2.2", - "postcss-convert-values": "2.6.1", - "postcss-discard-comments": "2.0.4", - "postcss-discard-duplicates": "2.1.0", - "postcss-discard-empty": "2.1.0", - "postcss-discard-overridden": "0.1.1", - "postcss-discard-unused": "2.2.3", - "postcss-filter-plugins": "2.0.2", - "postcss-merge-idents": "2.1.7", - "postcss-merge-longhand": "2.0.2", - "postcss-merge-rules": "2.1.2", - "postcss-minify-font-values": "1.0.5", - "postcss-minify-gradients": "1.0.5", - "postcss-minify-params": "1.2.2", - "postcss-minify-selectors": "2.1.1", - "postcss-normalize-charset": "1.1.1", - "postcss-normalize-url": "3.0.8", - "postcss-ordered-values": "2.2.3", - "postcss-reduce-idents": "2.4.0", - "postcss-reduce-initial": "1.0.1", - "postcss-reduce-transforms": "1.0.4", - "postcss-svgo": "2.1.6", - "postcss-unique-selectors": "2.0.2", - "postcss-value-parser": "3.3.0", - "postcss-zindex": "2.2.0" - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "1.2.3", - "source-map": "0.5.7" - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "1.0.2" - } - }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, - "dargs": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", - "integrity": "sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=", - "dev": true - }, - "date-fns": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", - "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", - "dev": true - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "1.0.0" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", - "dev": true - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "dev": true, - "requires": { - "globby": "6.1.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "p-map": "1.2.0", - "pify": "3.0.0", - "rimraf": "2.6.2" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-conflict": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", - "integrity": "sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "detect-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", - "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", - "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "miller-rabin": "4.0.1", - "randombytes": "2.0.6" - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "requires": { - "ip": "1.1.5", - "safe-buffer": "5.1.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "1.1.1" - } - }, - "dom-converter": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", - "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", - "dev": true, - "requires": { - "utila": "0.3.3" - }, - "dependencies": { - "utila": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", - "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", - "dev": true - } - } - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, - "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true - }, - "domhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", - "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "duplexify": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", - "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "stream-shift": "1.0.0" - } - }, - "editions": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", - "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "ejs": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.8.tgz", - "integrity": "sha512-QIDZL54fyV8MDcAsO91BMH1ft2qGGaHIJsJIA/+t+7uvXol1dm413fPcUgUb4k8F/9457rx4/KFE4XfDifrQxQ==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.42", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.42.tgz", - "integrity": "sha1-lcM78B0MxAVVauyJn+Yf1NduoPk=", - "dev": true - }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "dev": true - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0", - "hash.js": "1.1.3", - "hmac-drbg": "1.0.1", - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "1.4.0" - } - }, - "enhanced-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz", - "integrity": "sha512-jox/62b2GofV1qTUQTMPEJSDIGycS43evqYzD/KVtEb9OCoki9cnacUPxCrZa7JfPzZSYOCZhu9O9luaMxAX8g==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "memory-fs": "0.4.1", - "tapable": "1.0.0" - } - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", - "dev": true - }, - "envinfo": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-4.4.2.tgz", - "integrity": "sha512-5rfRs+m+6pwoKRCFqpsA5+qsLngFms1aWPrxfKbrObCzQaPc3M3yPloZx+BL9UE3dK58cxw36XVQbFRSCCfGSQ==", - "dev": true - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "1.0.1" - } - }, - "error": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", - "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", - "dev": true, - "requires": { - "string-template": "0.2.1", - "xtend": "4.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es-abstract": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", - "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", - "dev": true, - "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "eventemitter3": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", - "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=", - "dev": true - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true - }, - "eventsource": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", - "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", - "dev": true, - "requires": { - "original": "1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "1.3.4", - "safe-buffer": "5.1.1" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "2.2.3" - }, - "dependencies": { - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "1.0.1" - } - }, - "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", - "dev": true, - "requires": { - "accepts": "1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "1.4.0", - "type-is": "1.6.16", - "utils-merge": "1.0.1", - "vary": "1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.19", - "tmp": "0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", - "dev": true - }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "requires": { - "websocket-driver": "0.7.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5" - } - }, - "file-loader": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", - "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.4.0", - "unpipe": "1.0.0" - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "make-dir": "1.2.0", - "pkg-dir": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "dev": true, - "requires": { - "readable-stream": "2.3.6" - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, - "flow-parser": { - "version": "0.69.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.69.0.tgz", - "integrity": "sha1-N4tRKNbQtVSosvFqTKPhq5ZJ8A4=", - "dev": true - }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "2.3.6" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.6.39" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.39", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.2", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "gh-got": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-6.0.0.tgz", - "integrity": "sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==", - "dev": true, - "requires": { - "got": "7.1.0", - "is-plain-obj": "1.1.0" - }, - "dependencies": { - "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "dev": true, - "requires": { - "decompress-response": "3.3.0", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-plain-obj": "1.1.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "isurl": "1.0.0", - "lowercase-keys": "1.0.1", - "p-cancelable": "0.3.0", - "p-timeout": "1.2.1", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "url-parse-lax": "1.0.0", - "url-to-options": "1.0.1" - } - }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", - "dev": true - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "dev": true, - "requires": { - "p-finally": "1.0.0" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "1.0.4" - } - } - } - }, - "github-username": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-4.1.0.tgz", - "integrity": "sha1-y+KABBiDIG2kISrp5LXxacML9Bc=", - "dev": true, - "requires": { - "gh-got": "6.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-all": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.1.0.tgz", - "integrity": "sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=", - "dev": true, - "requires": { - "glob": "7.1.2", - "yargs": "1.2.6" - }, - "dependencies": { - "minimist": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", - "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", - "dev": true - }, - "yargs": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.2.6.tgz", - "integrity": "sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=", - "dev": true, - "requires": { - "minimist": "0.1.0" - } - } - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "1.0.2", - "is-windows": "1.0.2", - "resolve-dir": "1.0.1" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "2.0.2", - "homedir-polyfill": "1.0.1", - "ini": "1.3.5", - "is-windows": "1.0.2", - "which": "1.3.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "got": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.0.tgz", - "integrity": "sha512-kBNy/S2CGwrYgDSec5KTWGKUvupwkkTVAjIsVFF2shXO13xpZdFP4d4kxa//CLX2tN/rV0aYwK8vY6UKWGn2vQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "0.7.0", - "cacheable-request": "2.1.4", - "decompress-response": "3.3.0", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "into-stream": "3.1.0", - "is-retry-allowed": "1.1.0", - "isurl": "1.0.0", - "lowercase-keys": "1.0.1", - "mimic-response": "1.0.0", - "p-cancelable": "0.4.1", - "p-timeout": "2.0.1", - "pify": "3.0.0", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "url-parse-lax": "3.0.0", - "url-to-options": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "grouped-queue": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.3.tgz", - "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", - "dev": true, - "requires": { - "lodash": "4.17.5" - } - }, - "handle-thing": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", - "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", - "dev": true - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, - "requires": { - "function-bind": "1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-color": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true - }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "dev": true, - "requires": { - "has-symbol-support-x": "1.4.2" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "minimalistic-assert": "1.0.0" - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "1.1.3", - "minimalistic-assert": "1.0.0", - "minimalistic-crypto-utils": "1.0.1" - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "1.0.0" - } - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", - "dev": true - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "obuf": "1.1.2", - "readable-stream": "2.3.6", - "wbuf": "1.7.3" - } - }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", - "dev": true - }, - "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", - "dev": true - }, - "html-minifier": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.14.tgz", - "integrity": "sha512-sZjw6zhQgyUnIlIPU+W80XpRjWjdxHtNcxjfyOskOsCTDKytcfLY04wsQY/83Yqb4ndoiD2FtauiL7Yg6uUQFQ==", - "dev": true, - "requires": { - "camel-case": "3.0.0", - "clean-css": "4.1.11", - "commander": "2.15.1", - "he": "1.1.1", - "param-case": "2.1.1", - "relateurl": "0.2.7", - "uglify-js": "3.3.20" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-js": { - "version": "3.3.20", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.20.tgz", - "integrity": "sha512-WpLkWCf9sGvGZnIvBV0PNID9BATQNT/IXKAmqegfKzIPcTmTV3FP8NQpoogQkt/Y402x2sOFdaHUmqFY9IZp+g==", - "dev": true, - "requires": { - "commander": "2.15.1", - "source-map": "0.6.1" - } - } - } - }, - "html-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", - "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", - "dev": true, - "requires": { - "html-minifier": "3.5.14", - "loader-utils": "0.2.17", - "lodash": "4.17.5", - "pretty-error": "2.1.1", - "tapable": "1.0.0", - "toposort": "1.0.6", - "util.promisify": "1.0.0" - }, - "dependencies": { - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1", - "object-assign": "4.1.1" - } - } - } - }, - "htmlparser2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", - "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", - "dev": true, - "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.1.0", - "domutils": "1.1.6", - "readable-stream": "1.0.34" - }, - "dependencies": { - "domutils": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", - "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": "1.4.0" - } - }, - "http-parser-js": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.11.tgz", - "integrity": "sha512-QCR5O2AjjMW8Mo4HyI1ctFcv+O99j/0g367V3YoVnrNw5hkDvAWZD0lWGcc+F4yN3V55USPCVix4efb75HxFfA==", - "dev": true - }, - "http-proxy": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz", - "integrity": "sha1-Bt/ykpUr9k2+hHH6nfcwZtTzd0I=", - "dev": true, - "requires": { - "eventemitter3": "1.2.0", - "requires-port": "1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", - "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", - "dev": true, - "requires": { - "http-proxy": "1.16.2", - "is-glob": "3.1.0", - "lodash": "4.17.5", - "micromatch": "2.3.11" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - } - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - } - } - } - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, - "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", - "dev": true, - "requires": { - "postcss": "6.0.21" - }, - "dependencies": { - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "ieee754": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", - "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "2.0.0", - "resolve-cwd": "2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "2.0.1" - } - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "dev": true, - "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.2.0", - "figures": "2.0.0", - "lodash": "4.17.5", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "5.5.9", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "internal-ip": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", - "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", - "dev": true, - "requires": { - "meow": "3.7.0" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "dev": true, - "requires": { - "from2": "2.3.0", - "p-is-promise": "1.1.0" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "1.3.1" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", - "dev": true - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "1.11.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, - "is-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", - "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", - "dev": true, - "requires": { - "symbol-observable": "0.2.4" - }, - "dependencies": { - "symbol-observable": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", - "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", - "dev": true - } - } - }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "1.0.1" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "1.0.1" - } - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", - "dev": true, - "requires": { - "scoped-regex": "1.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "1.1.1" - } - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "istextorbinary": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", - "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", - "dev": true, - "requires": { - "binaryextensions": "2.1.1", - "editions": "1.3.4", - "textextensions": "2.2.0" - } - }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "dev": true, - "requires": { - "has-to-string-tag-x": "1.4.1", - "is-object": "1.0.1" - } - }, - "js-base64": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.3.tgz", - "integrity": "sha512-H7ErYLM34CvDMto3GbD6xD0JLUGYXR3QTcH6B/tr4Hi/QpSThnCsIp+Sy5FRTw3B0d6py4HcNkW7nO/wdtGWEw==", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "1.0.10", - "esprima": "2.7.3" - } - }, - "jscodeshift": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.5.0.tgz", - "integrity": "sha512-JAcQINNMFpdzzpKJN8k5xXjF3XDuckB1/48uScSzcnNyK199iWEc9AxKL9OoX5144M2w5zEx9Qs4/E/eBZZUlw==", - "dev": true, - "requires": { - "babel-plugin-transform-flow-strip-types": "6.22.0", - "babel-preset-es2015": "6.24.1", - "babel-preset-stage-1": "6.24.1", - "babel-register": "6.26.0", - "babylon": "7.0.0-beta.44", - "colors": "1.1.2", - "flow-parser": "0.69.0", - "lodash": "4.17.5", - "micromatch": "2.3.11", - "neo-async": "2.5.1", - "node-dir": "0.1.8", - "nomnom": "1.8.1", - "recast": "0.14.7", - "temp": "0.8.3", - "write-file-atomic": "1.3.4" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "babylon": { - "version": "7.0.0-beta.44", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", - "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - } - } - }, - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "killable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", - "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "1.0.0" - } - }, - "listr": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.13.0.tgz", - "integrity": "sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "figures": "1.7.0", - "indent-string": "2.1.0", - "is-observable": "0.2.0", - "is-promise": "2.1.0", - "is-stream": "1.1.0", - "listr-silent-renderer": "1.1.1", - "listr-update-renderer": "0.4.0", - "listr-verbose-renderer": "0.4.1", - "log-symbols": "1.0.2", - "log-update": "1.0.2", - "ora": "0.2.3", - "p-map": "1.2.0", - "rxjs": "5.5.9", - "stream-to-observable": "0.2.0", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "1.1.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true - }, - "listr-update-renderer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz", - "integrity": "sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "cli-truncate": "0.2.1", - "elegant-spinner": "1.0.1", - "figures": "1.7.0", - "indent-string": "3.2.0", - "log-symbols": "1.0.2", - "log-update": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "1.1.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-verbose-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", - "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "date-fns": "1.29.0", - "figures": "1.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "1.0.1" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", - "dev": true - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "3.2.0", - "emojis-list": "2.1.0", - "json5": "0.5.1" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", - "dev": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "2.3.2" - } - }, - "log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", - "dev": true, - "requires": { - "ansi-escapes": "1.4.0", - "cli-cursor": "1.0.2" - }, - "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - } - } - }, - "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", - "dev": true - }, - "loglevelnext": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.4.tgz", - "integrity": "sha512-V3N6LAJAiGwa/zjtvmgs2tyeiCJ23bGNhxXN8R+v7k6TNlSlTz40mIyZYdmO762eBnEFymn0Mhha+WuAhnwMBg==", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, - "requires": { - "js-tokens": "3.0.2" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "0.4.1", - "signal-exit": "3.0.2" - } - }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", - "dev": true - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "macaddress": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/macaddress/-/macaddress-0.2.8.tgz", - "integrity": "sha1-WQTcU3w57G2+/q6QIycTX6hRHxI=", - "dev": true - }, - "make-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", - "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "1.0.1" - } - }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, - "requires": { - "hash-base": "3.0.4", - "inherits": "2.0.3" - }, - "dependencies": { - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - } - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "1.2.0" - } - }, - "mem-fs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.3.tgz", - "integrity": "sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=", - "dev": true, - "requires": { - "through2": "2.0.3", - "vinyl": "1.2.0", - "vinyl-file": "2.0.0" - } - }, - "mem-fs-editor": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz", - "integrity": "sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "deep-extend": "0.4.2", - "ejs": "2.5.8", - "glob": "7.1.2", - "globby": "6.1.0", - "mkdirp": "0.5.1", - "multimatch": "2.1.0", - "rimraf": "2.6.2", - "through2": "2.0.3", - "vinyl": "2.1.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", - "dev": true - }, - "vinyl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", - "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", - "dev": true, - "requires": { - "clone": "2.1.2", - "clone-buffer": "1.0.0", - "clone-stats": "1.0.0", - "cloneable-readable": "1.1.2", - "remove-trailing-separator": "1.1.0", - "replace-ext": "1.0.0" - } - } - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "0.1.7", - "readable-stream": "2.3.6" - } - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.6.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.4.0", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "brorand": "1.1.0" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "1.33.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "mimic-response": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", - "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=", - "dev": true - }, - "mini-css-extract-plugin": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.0.tgz", - "integrity": "sha512-2Zik6PhUZ/MbiboG6SDS9UTPL4XXy4qnyGjSdCIWRrr8xb6PwLtHE+AYOjkXJWdF0OG8vo/yrJ8CgS5WbMpzIg==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "webpack-sources": "1.1.0" - } - }, - "minimalistic-assert": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "duplexify": "3.5.4", - "end-of-stream": "1.4.1", - "flush-write-stream": "1.0.3", - "from2": "2.3.0", - "parallel-transform": "1.1.0", - "pump": "2.0.1", - "pumpify": "1.4.0", - "stream-each": "1.2.2", - "through2": "2.0.3" - } - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "1.2.0", - "copy-concurrently": "1.0.5", - "fs-write-stream-atomic": "1.0.10", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "run-queue": "1.0.3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "1.3.1", - "thunky": "1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "dev": true, - "requires": { - "array-differ": "1.0.0", - "array-union": "1.0.2", - "arrify": "1.0.1", - "minimatch": "3.0.4" - } - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "neo-async": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", - "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==", - "dev": true - }, - "nice-try": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", - "dev": true - }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "requires": { - "lower-case": "1.1.4" - } - }, - "node-dir": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz", - "integrity": "sha1-VfuN62mQcHB/tn+RpGDwRIKUx30=", - "dev": true - }, - "node-forge": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", - "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=", - "dev": true - }, - "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, - "requires": { - "assert": "1.4.1", - "browserify-zlib": "0.2.0", - "buffer": "4.9.1", - "console-browserify": "1.1.0", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "domain-browser": "1.2.0", - "events": "1.1.1", - "https-browserify": "1.0.0", - "os-browserify": "0.3.0", - "path-browserify": "0.0.0", - "process": "0.11.10", - "punycode": "1.4.1", - "querystring-es3": "0.2.1", - "readable-stream": "2.3.6", - "stream-browserify": "2.0.1", - "stream-http": "2.8.1", - "string_decoder": "1.1.1", - "timers-browserify": "2.0.6", - "tty-browserify": "0.0.0", - "url": "0.11.0", - "util": "0.10.3", - "vm-browserify": "0.0.4" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "nomnom": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", - "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", - "dev": true, - "requires": { - "chalk": "0.4.0", - "underscore": "1.6.0" - }, - "dependencies": { - "ansi-styles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", - "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", - "dev": true - }, - "chalk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", - "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "dev": true, - "requires": { - "ansi-styles": "1.0.0", - "has-color": "0.1.7", - "strip-ansi": "0.1.1" - } - }, - "strip-ansi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", - "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", - "dev": true - } - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.3" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "1.1.0" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "prepend-http": "1.0.4", - "query-string": "4.3.4", - "sort-keys": "1.1.2" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "2.0.1" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "dev": true, - "requires": { - "boolbase": "1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "3.0.1" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "1.2.0" - } - }, - "opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", - "dev": true, - "requires": { - "is-wsl": "1.1.0" - } - }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-spinners": "0.1.2", - "object-assign": "4.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "original": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.0.tgz", - "integrity": "sha1-kUf5P6FpbQS+YeAb1QuurKZWvTs=", - "dev": true, - "requires": { - "url-parse": "1.0.5" - }, - "dependencies": { - "url-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.0.5.tgz", - "integrity": "sha1-CFSGBCKv3P7+tsllxmLUgAFpkns=", - "dev": true, - "requires": { - "querystringify": "0.0.4", - "requires-port": "1.0.0" - } - } - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", - "dev": true - }, - "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", - "dev": true, - "requires": { - "p-reduce": "1.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true - }, - "p-lazy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-lazy/-/p-lazy-1.0.0.tgz", - "integrity": "sha1-7FPIAvLuOsKPFmzILQsrAt4nqDU=", - "dev": true - }, - "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "dev": true, - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "1.2.0" - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true - }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "dev": true, - "requires": { - "p-finally": "1.0.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, - "requires": { - "cyclist": "0.2.2", - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", - "dev": true, - "requires": { - "no-case": "2.3.2" - } - }, - "parse-asn1": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", - "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", - "dev": true, - "requires": { - "asn1.js": "4.10.1", - "browserify-aes": "1.2.0", - "create-hash": "1.1.3", - "evp_bytestokey": "1.0.3", - "pbkdf2": "3.0.14" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - } - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "1.3.1" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "pbkdf2": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", - "dev": true, - "requires": { - "create-hash": "1.1.3", - "create-hmac": "1.1.6", - "ripemd160": "2.0.1", - "safe-buffer": "5.1.1", - "sha.js": "2.4.11" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "2.1.0" - } - }, - "portfinder": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", - "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", - "dev": true, - "requires": { - "async": "1.5.2", - "debug": "2.6.9", - "mkdirp": "0.5.1" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "1.1.3", - "js-base64": "2.4.3", - "source-map": "0.5.7", - "supports-color": "3.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-message-helpers": "2.0.0", - "reduce-css-calc": "1.3.0" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "1.1.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz", - "integrity": "sha1-bYWGJTTXNaxCDkqFgG4fXUKG2Ew=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "uniqid": "4.1.1" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "1.7.7", - "caniuse-api": "1.6.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3", - "vendors": "1.0.1" - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "uniqs": "2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-selector-parser": "2.2.3" - } - }, - "postcss-modules-extract-imports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", - "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", - "dev": true, - "requires": { - "postcss": "6.0.21" - }, - "dependencies": { - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "dev": true, - "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.21" - }, - "dependencies": { - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "dev": true, - "requires": { - "css-selector-tokenizer": "0.7.0", - "postcss": "6.0.21" - }, - "dependencies": { - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "dev": true, - "requires": { - "icss-replace-symbols": "1.1.0", - "postcss": "6.0.21" - }, - "dependencies": { - "postcss": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.21.tgz", - "integrity": "sha512-y/bKfbQz2Nn/QBC08bwvYUxEFOVGfPIUOTsJ2CK5inzlXW9SdYR1x4pEsG9blRAF/PX+wRNdOah+gx/hv4q7dw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "source-map": "0.6.1", - "supports-color": "5.3.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "2.1.0", - "normalize-url": "1.9.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "5.2.18" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "1.0.2", - "indexes-of": "1.0.1", - "uniq": "1.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "2.1.0", - "postcss": "5.2.18", - "postcss-value-parser": "3.3.0", - "svgo": "0.7.2" - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "1.0.2", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", - "dev": true - }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "1.0.1", - "postcss": "5.2.18", - "uniqs": "2.0.0" - } - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "prettier": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.11.1.tgz", - "integrity": "sha512-T/KD65Ot0PB97xTrG8afQ46x3oiVhnfGjGESSI9NWYcG92+OUPZKkwHqGWXH2t9jK1crnQjubECW0FuOth+hxw==", - "dev": true - }, - "pretty-bytes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", - "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", - "dev": true - }, - "pretty-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", - "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", - "dev": true, - "requires": { - "renderkid": "2.0.1", - "utila": "0.4.0" - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "dev": true, - "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.6.0" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", - "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", - "dev": true, - "requires": { - "bn.js": "4.11.8", - "browserify-rsa": "4.0.1", - "create-hash": "1.1.3", - "parse-asn1": "5.1.0", - "randombytes": "2.0.6" - } - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" - } - }, - "pumpify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz", - "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", - "dev": true, - "requires": { - "duplexify": "3.5.4", - "inherits": "2.0.3", - "pump": "2.0.1" - } - }, - "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "4.1.1", - "strict-uri-encode": "1.1.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", - "integrity": "sha1-DPf4T5Rj/wrlHExLFC2VvjdyTZw=", - "dev": true - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "2.0.6", - "safe-buffer": "5.1.1" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.4.0" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } - } - }, - "read-chunk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", - "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", - "dev": true, - "requires": { - "pify": "3.0.0", - "safe-buffer": "5.1.1" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "readable-stream": "2.3.6", - "set-immediate-shim": "1.0.1" - } - }, - "recast": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.14.7.tgz", - "integrity": "sha512-/nwm9pkrcWagN40JeJhkPaRxiHXBRkXyRh/hgU088Z/v+qCy+zIHHY6bC6o7NaKAxPqtE6nD8zBH1LfU0/Wx6A==", - "dev": true, - "requires": { - "ast-types": "0.11.3", - "esprima": "4.0.0", - "private": "0.1.8", - "source-map": "0.6.1" - }, - "dependencies": { - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "1.7.0" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "math-expression-evaluator": "1.2.17", - "reduce-function-call": "1.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "regenerate": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "private": "0.1.8" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" - } - }, - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "dev": true, - "requires": { - "regenerate": "1.3.3", - "regjsgen": "0.2.0", - "regjsparser": "0.1.5" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "0.5.0" - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "renderkid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", - "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", - "dev": true, - "requires": { - "css-select": "1.2.0", - "dom-converter": "0.1.4", - "htmlparser2": "3.3.0", - "strip-ansi": "3.0.1", - "utila": "0.3.3" - }, - "dependencies": { - "utila": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", - "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", - "dev": true - } - } - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "1.0.2" - } - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.0.tgz", - "integrity": "sha512-QdgZ5bjR1WAlpLaO5yHepFvC+o3rCr6wpfE2tpJNMkXdulf2jKomQBdNRQITF3ZKHNlT71syG98yQP03gasgnA==", - "dev": true, - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "3.0.0" - } - }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "2.0.2", - "global-modules": "1.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "1.0.1" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", - "dev": true, - "requires": { - "hash-base": "2.0.2", - "inherits": "2.0.3" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "2.1.0" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "1.2.0" - } - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "4.0.8" - } - }, - "rxjs": { - "version": "5.5.9", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", - "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", - "dev": true, - "requires": { - "symbol-observable": "1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "0.1.15" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", - "dev": true, - "requires": { - "ajv": "6.4.0", - "ajv-keywords": "3.1.0" - } - }, - "scoped-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", - "dev": true - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "selfsigned": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.2.tgz", - "integrity": "sha1-tESVgNmZKbZbEKSDiTAaZZIIh1g=", - "dev": true, - "requires": { - "node-forge": "0.7.1" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.3", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.4.0" - } - }, - "serialize-javascript": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.4.0.tgz", - "integrity": "sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=", - "dev": true - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "1.3.5", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "1.0.3", - "http-errors": "1.6.3", - "mime-types": "2.1.18", - "parseurl": "1.3.2" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, - "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.2" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "safe-buffer": "5.1.1" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shelljs": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", - "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", - "dev": true, - "requires": { - "glob": "7.1.2", - "interpret": "1.1.0", - "rechoir": "0.6.2" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", - "dev": true, - "requires": { - "faye-websocket": "0.10.0", - "uuid": "3.2.1" - } - }, - "sockjs-client": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz", - "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", - "dev": true, - "requires": { - "debug": "2.6.9", - "eventsource": "0.1.6", - "faye-websocket": "0.11.1", - "inherits": "2.0.3", - "json3": "3.3.2", - "url-parse": "1.3.0" - }, - "dependencies": { - "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", - "dev": true, - "requires": { - "websocket-driver": "0.7.0" - } - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "1.1.0" - } - }, - "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", - "dev": true, - "requires": { - "atob": "2.1.0", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" - } - }, - "source-map-support": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", - "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", - "dev": true, - "requires": { - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true - }, - "spdy": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", - "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", - "dev": true, - "requires": { - "debug": "2.6.9", - "handle-thing": "1.2.5", - "http-deceiver": "1.2.7", - "safe-buffer": "5.1.1", - "select-hose": "2.0.0", - "spdy-transport": "2.1.0" - } - }, - "spdy-transport": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", - "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", - "dev": true, - "requires": { - "debug": "2.6.9", - "detect-node": "2.0.3", - "hpack.js": "2.1.6", - "obuf": "1.1.2", - "readable-stream": "2.3.6", - "safe-buffer": "5.1.1", - "wbuf": "1.7.3" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "3.0.2" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - } - } - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - } - }, - "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", - "dev": true, - "requires": { - "end-of-stream": "1.4.1", - "stream-shift": "1.0.0" - } - }, - "stream-http": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.1.tgz", - "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", - "dev": true, - "requires": { - "builtin-status-codes": "3.0.0", - "inherits": "2.0.3", - "readable-stream": "2.3.6", - "to-arraybuffer": "1.0.1", - "xtend": "4.0.1" - } - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "stream-to-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", - "integrity": "sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=", - "dev": true, - "requires": { - "any-observable": "0.2.0" - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - }, - "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "dev": true, - "requires": { - "first-chunk-stream": "2.0.0", - "strip-bom": "2.0.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "4.0.1" - } - }, - "style-loader": { - "version": "0.20.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.20.3.tgz", - "integrity": "sha512-2I7AVP73MvK33U7B9TKlYZAqdROyMXDYSMvHLX43qy3GCOaJNiV6i0v/sv9idWIaQ42Yn2dNv79Q5mKXbKhAZg==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" - } - }, - "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "1.0.4", - "colors": "1.1.2", - "csso": "2.3.2", - "js-yaml": "3.7.0", - "mkdirp": "0.5.1", - "sax": "1.2.4", - "whet.extend": "0.9.9" - } - }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - }, - "tapable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", - "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", - "dev": true - }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2", - "rimraf": "2.2.8" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "textextensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz", - "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "2.3.6", - "xtend": "4.0.1" - } - }, - "thunky": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz", - "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=", - "dev": true - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, - "timers-browserify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", - "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", - "dev": true, - "requires": { - "setimmediate": "1.0.5" - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2" - } - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" - } - }, - "toposort": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.6.tgz", - "integrity": "sha1-wxdI5V0hDv/AD9zcfW5o19e7nOw=", - "dev": true - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.18" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.1.tgz", - "integrity": "sha512-Ao/f6d/4EPLq0YwzsQz8iXflezpTkQzqAyenTiw4kCUGr1uPiFLC3+fZ+gMZz6eeI/qdRUqvC+HxIJzUAzEFdg==", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "2.13.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "uglifyjs-webpack-plugin": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.4.tgz", - "integrity": "sha512-z0IbjpW8b3O/OVn+TTZN4pI29RN1zktFBXLIzzfZ+++cUtZ1ERSlLWgpE/5OERuEUs1ijVQnpYAkSlpoVmQmSQ==", - "dev": true, - "requires": { - "cacache": "10.0.4", - "find-cache-dir": "1.0.0", - "schema-utils": "0.4.5", - "serialize-javascript": "1.4.0", - "source-map": "0.6.1", - "uglify-es": "3.3.9", - "webpack-sources": "1.1.0", - "worker-farm": "1.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", - "dev": true - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" - } - } - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqid": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz", - "integrity": "sha1-iSIN32t1GuUrX3JISGNShZa7hME=", - "dev": true, - "requires": { - "macaddress": "0.2.8" - } - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", - "dev": true, - "requires": { - "unique-slug": "2.0.0" - } - }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "dev": true, - "requires": { - "imurmurhash": "0.1.4" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "untildify": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.2.tgz", - "integrity": "sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=", - "dev": true - }, - "upath": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", - "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", - "dev": true - }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true - }, - "uri-js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", - "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", - "dev": true, - "requires": { - "punycode": "2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-join": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", - "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", - "dev": true - }, - "url-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.0.1.tgz", - "integrity": "sha512-rAonpHy7231fmweBKUFe0bYnlGDty77E+fm53NZdij7j/YOpyGzc7ttqG1nAXl3aRs0k41o0PC3TvGXQiw2Zvw==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "mime": "2.2.2", - "schema-utils": "0.4.5" - }, - "dependencies": { - "mime": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.2.2.tgz", - "integrity": "sha512-A7PDg4s48MkqFEcYg2b069m3DXOEq7hx+9q9rIFrSSYfzsh35GX+LOVMQ8Au0ko7d8bSQCIAuzkjp0vCtwENlQ==", - "dev": true - } - } - }, - "url-parse": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.3.0.tgz", - "integrity": "sha512-zPvPA3T7P6M+0iNsgX+iAcAz4GshKrowtQBHHc/28tVsBc8jK7VRCNX+2GEcoE6zDB6XqXhcyiUWPVZY6C70Cg==", - "dev": true, - "requires": { - "querystringify": "1.0.0", - "requires-port": "1.0.0" - }, - "dependencies": { - "querystringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", - "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=", - "dev": true - } - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "2.0.0" - }, - "dependencies": { - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - } - } - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true - }, - "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "1.1.2", - "object.getownpropertydescriptors": "2.0.3" - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true - }, - "v8-compile-cache": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz", - "integrity": "sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "dev": true, - "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vendors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.1.tgz", - "integrity": "sha1-N61zyO5Bf7PVgOeFMSMH0nSEfyI=", - "dev": true - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "1.0.4", - "clone-stats": "0.0.1", - "replace-ext": "0.0.1" - } - }, - "vinyl-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", - "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0", - "strip-bom-stream": "2.0.0", - "vinyl": "1.2.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "watchpack": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.5.0.tgz", - "integrity": "sha512-RSlipNQB1u48cq0wH/BNfCu1tD/cJ8ydFIkNYhp9o+3d+8unClkIovpW5qpFPgmL9OE48wfAnlZydXByWP82AA==", - "dev": true, - "requires": { - "chokidar": "2.0.3", - "graceful-fs": "4.1.11", - "neo-async": "2.5.1" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "1.0.0" - } - }, - "webpack": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.5.0.tgz", - "integrity": "sha512-6GrZsvQJnG7o7mjbfjp6s5CyMfdopjt1A/X8LcYwceis9ySjqBX6Lusso2wNZ06utHj2ZvfL6L3f7hfgVeJP6g==", - "dev": true, - "requires": { - "acorn": "5.5.3", - "acorn-dynamic-import": "3.0.0", - "ajv": "6.4.0", - "ajv-keywords": "3.1.0", - "chrome-trace-event": "0.1.2", - "enhanced-resolve": "4.0.0", - "eslint-scope": "3.7.1", - "loader-runner": "2.3.0", - "loader-utils": "1.1.0", - "memory-fs": "0.4.1", - "micromatch": "3.1.10", - "mkdirp": "0.5.1", - "neo-async": "2.5.1", - "node-libs-browser": "2.1.0", - "schema-utils": "0.4.5", - "tapable": "1.0.0", - "uglifyjs-webpack-plugin": "1.2.4", - "watchpack": "1.5.0", - "webpack-sources": "1.1.0" - } - }, - "webpack-addons": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/webpack-addons/-/webpack-addons-1.1.5.tgz", - "integrity": "sha512-MGO0nVniCLFAQz1qv22zM02QPjcpAoJdy7ED0i3Zy7SY1IecgXCm460ib7H/Wq7e9oL5VL6S2BxaObxwIcag0g==", - "dev": true, - "requires": { - "jscodeshift": "0.4.1" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "ast-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz", - "integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } - }, - "jscodeshift": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.4.1.tgz", - "integrity": "sha512-iOX6If+hsw0q99V3n31t4f5VlD1TQZddH08xbT65ZqA7T4Vkx68emrDZMUOLVvCEAJ6NpAk7DECe3fjC/t52AQ==", - "dev": true, - "requires": { - "async": "1.5.2", - "babel-plugin-transform-flow-strip-types": "6.22.0", - "babel-preset-es2015": "6.24.1", - "babel-preset-stage-1": "6.24.1", - "babel-register": "6.26.0", - "babylon": "6.18.0", - "colors": "1.1.2", - "flow-parser": "0.69.0", - "lodash": "4.17.5", - "micromatch": "2.3.11", - "node-dir": "0.1.8", - "nomnom": "1.8.1", - "recast": "0.12.9", - "temp": "0.8.3", - "write-file-atomic": "1.3.4" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" - } - }, - "recast": { - "version": "0.12.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz", - "integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==", - "dev": true, - "requires": { - "ast-types": "0.10.1", - "core-js": "2.5.5", - "esprima": "4.0.0", - "private": "0.1.8", - "source-map": "0.6.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "webpack-cli": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.0.14.tgz", - "integrity": "sha512-gRoWaxSi2JWiYsn1QgOTb6ENwIeSvN1YExZ+kJ0STsTZK7bWPElW+BBBv1UnTbvcPC3v7E17mK8hlFX8DOYSGw==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "cross-spawn": "6.0.5", - "diff": "3.5.0", - "enhanced-resolve": "4.0.0", - "envinfo": "4.4.2", - "glob-all": "3.1.0", - "global-modules": "1.0.0", - "got": "8.3.0", - "import-local": "1.0.0", - "inquirer": "5.2.0", - "interpret": "1.1.0", - "jscodeshift": "0.5.0", - "listr": "0.13.0", - "loader-utils": "1.1.0", - "lodash": "4.17.5", - "log-symbols": "2.2.0", - "mkdirp": "0.5.1", - "p-each-series": "1.0.0", - "p-lazy": "1.0.0", - "prettier": "1.11.1", - "supports-color": "5.3.0", - "v8-compile-cache": "1.1.2", - "webpack-addons": "1.1.5", - "yargs": "11.1.0", - "yeoman-environment": "2.0.6", - "yeoman-generator": "2.0.3" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "1.0.4", - "path-key": "2.0.1", - "semver": "5.5.0", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", - "dev": true, - "requires": { - "cliui": "4.0.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" - } - } - } - }, - "webpack-dev-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz", - "integrity": "sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==", - "dev": true, - "requires": { - "loud-rejection": "1.6.0", - "memory-fs": "0.4.1", - "mime": "2.3.1", - "path-is-absolute": "1.0.1", - "range-parser": "1.2.0", - "url-join": "2.0.5", - "webpack-log": "1.2.0" - }, - "dependencies": { - "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", - "dev": true - } - } - }, - "webpack-dev-server": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.0.tgz", - "integrity": "sha512-ap7Fth7oh4sthC0nJkvRm2W3SaWryBeR19DWIcAwJlcooN0tB2fEKuZqckYR3uaJ6wXPCK1xMWAQWXhV5xVe8g==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "array-includes": "3.0.3", - "bonjour": "3.5.0", - "chokidar": "2.0.3", - "compression": "1.7.2", - "connect-history-api-fallback": "1.5.0", - "debug": "3.1.0", - "del": "3.0.0", - "express": "4.16.3", - "html-entities": "1.2.1", - "http-proxy-middleware": "0.17.4", - "import-local": "1.0.0", - "internal-ip": "1.2.0", - "ip": "1.1.5", - "killable": "1.0.0", - "loglevel": "1.6.1", - "opn": "5.3.0", - "portfinder": "1.0.13", - "selfsigned": "1.10.2", - "serve-index": "1.9.1", - "sockjs": "0.3.19", - "sockjs-client": "1.1.4", - "spdy": "3.4.7", - "strip-ansi": "3.0.1", - "supports-color": "5.3.0", - "webpack-dev-middleware": "2.0.6", - "webpack-log": "1.2.0", - "yargs": "9.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "webpack-log": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", - "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "log-symbols": "2.2.0", - "loglevelnext": "1.0.4", - "uuid": "3.2.1" - } - }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", - "dev": true, - "requires": { - "source-list-map": "2.0.0", - "source-map": "0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "websocket-driver": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", - "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", - "dev": true, - "requires": { - "http-parser-js": "0.4.11", - "websocket-extensions": "0.1.3" - } - }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", - "dev": true - }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", - "dev": true, - "requires": { - "errno": "0.1.7" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "slide": "1.1.6" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", - "dev": true, - "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "2.3.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "yeoman-environment": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.0.6.tgz", - "integrity": "sha512-jzHBTTy8EPI4ImV8dpUMt+Q5zELkSU5xvGpndHcHudQ4tqN6YgIWaCGmRFl+HDchwRUkcgyjQ+n6/w5zlJBCPg==", - "dev": true, - "requires": { - "chalk": "2.3.2", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "globby": "6.1.0", - "grouped-queue": "0.3.3", - "inquirer": "3.3.0", - "is-scoped": "1.0.0", - "lodash": "4.17.5", - "log-symbols": "2.2.0", - "mem-fs": "1.1.3", - "text-table": "0.2.0", - "untildify": "3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.3.2", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.2.0", - "figures": "2.0.0", - "lodash": "4.17.5", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "yeoman-generator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.3.tgz", - "integrity": "sha512-mODmrZ26a94djmGZZuIiomSGlN4wULdou29ZwcySupb2e9FdvoCl7Ps2FqHFjEHio3kOl/iBeaNqrnx3C3NwWg==", - "dev": true, - "requires": { - "async": "2.6.0", - "chalk": "2.3.2", - "cli-table": "0.3.1", - "cross-spawn": "5.1.0", - "dargs": "5.1.0", - "dateformat": "3.0.3", - "debug": "3.1.0", - "detect-conflict": "1.0.1", - "error": "7.0.2", - "find-up": "2.1.0", - "github-username": "4.1.0", - "istextorbinary": "2.2.1", - "lodash": "4.17.5", - "make-dir": "1.2.0", - "mem-fs-editor": "3.0.2", - "minimist": "1.2.0", - "pretty-bytes": "4.0.2", - "read-chunk": "2.1.0", - "read-pkg-up": "3.0.0", - "rimraf": "2.6.2", - "run-async": "2.3.0", - "shelljs": "0.8.1", - "text-table": "0.2.0", - "through2": "2.0.3", - "yeoman-environment": "2.0.6" - }, - "dependencies": { - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "dev": true, - "requires": { - "lodash": "4.17.5" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "1.3.1", - "json-parse-better-errors": "1.0.2" - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.4.0", - "path-type": "3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "2.1.0", - "read-pkg": "3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - } - } -} diff --git a/00 Boilerplate/package.json b/00 Boilerplate/package.json deleted file mode 100644 index 19c0ea2..0000000 --- a/00 Boilerplate/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "sample", - "version": "1.0.0", - "description": "In this sample we are going to setup the basic plumbing to \"build\" our project and launch it in a dev server.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --mode development --inline --hot --open", - "build": "webpack --mode development", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "awesome-typescript-loader": "^5.0.0", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.11", - "file-loader": "^1.1.11", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.4.0", - "style-loader": "^0.20.3", - "typescript": "^2.8.1", - "url-loader": "^1.0.1", - "webpack": "^4.5.0", - "webpack-cli": "^2.0.14", - "webpack-dev-server": "3.1.0" - }, - "dependencies": { - "bootstrap": "^4.1.0" - } -} diff --git a/00_Boilerplate/.babelrc b/00_Boilerplate/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/00_Boilerplate/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/00_Boilerplate/package.json b/00_Boilerplate/package.json new file mode 100644 index 0000000..fac46f1 --- /dev/null +++ b/00_Boilerplate/package.json @@ -0,0 +1,31 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + } +} diff --git a/00 Boilerplate/readme.md b/00_Boilerplate/readme.md similarity index 51% rename from 00 Boilerplate/readme.md rename to 00_Boilerplate/readme.md index 873b48b..b9e4f84 100644 --- a/00 Boilerplate/readme.md +++ b/00_Boilerplate/readme.md @@ -1,24 +1,23 @@ # 00 Boilerplate -In this sample we are going to setup the basic plumbing to "build" our project and launch it in a dev server. +In this sample we setup the basic plumbing to "build" our project and launch it in a dev server. -We won't install anything related about React, just some basic plumbing. In sample 01 we will start by importing -React and ReactDOM. +We won't install anything related to React, but some basic plumbing. React and ReactDOM are imported in sample 01. -We will setup an initial npm project, give support to TypeScript, and install React.
-Then we will create a **helloworld.ts** sample. +We setup an initial npm project, give support to TypeScript, and install React.
+Then we create a **helloworld.ts** sample. Summary steps: - Prerequisites: Install Node.js -- Initialize **package.json** (with `npm init`) +- Initialize **[./package.json](./package.json)** (with `npm init`) - Install: - Webpack and webpack-dev-server. - TypeScript. - Babel. - Bootstrap. -- Setup **webpack.config.js** -- Create a test js file. +- Setup **[./webpack.config.js](./webpack.config.js)** +- Create a test JS file. - Create a simple HTML file. # Prerequisites @@ -31,49 +30,49 @@ Install [Node.js and npm](https://nodejs.org/en/) (v8.9.1) if they are not alrea - Create and navigate to the folder where you are going to create the empty project. -- Execute `npm init`, you will be prompted to answer some information request -about the project (e.g. set name to _samplereact_ and description to _Sample working with React,TypeScript and Webpack_). -Once you have successfully fullfilled them a **package.json** file we will generated. +- Execute `npm init`. You are prompted to answer some questions about the project (e.g. set name to _samplereact_ and description to _Sample working with React, TypeScript and Webpack_). +Once you have successfully answered them, a **[./package.json](./package.json)** file is generated. - ``` + ```bash npm init ``` - Install **webpack** as a development dependency. - ``` + ```bash npm install webpack webpack-cli --save-dev ``` - Install **webpack-dev-server** locally, as a development dependency (the reason to install it locally and not globally is to be easy to setup, e.g. can be launched on a clean machine without having to install anything globally but nodejs). - ``` + ```bash npm install webpack-dev-server --save-dev ``` -- Let's install a list of plugins and loaders that will add powers to -our webpack configuration (handling CSS, TypeScript...). +- Let's install a list of plugins and loaders to add capabilities to our webpack configuration (handling CSS, TypeScript...). - ``` + ```bash npm install css-loader style-loader file-loader url-loader html-webpack-plugin awesome-typescript-loader mini-css-extract-plugin --save-dev ``` -- Let's add two commands to our **package.json** to build and start. +- Let's add two commands to our **[./package.json](./package.json)**: build and start. + +_[./package.json](./package.json)_ ```diff "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development" }, - ``` -- Let's install locally TypeScript: +- Let's install TypeScript locally: -``` +```bash npm install typescript --save-dev ``` -- We need as well to drop a **tsconfig.json** file in the root folder of our project +- We need as well to drop a **[./tsconfig.json](./tsconfig.json)** file in the root folder of our project +_[./tsconfig.json](./tsconfig.json)_ ```json { "compilerOptions": { @@ -94,89 +93,88 @@ npm install typescript --save-dev } ``` - - Now, we need to transpile ES6 to ES5. Let's install **babel-core** and **babel-preset-env**. - + - Now, we need to transpile ES6 to ES5. Let's install **@babel/cli**, **@babel/core**, **@babel/preset-env** and **@babel/polyfill**. +```bash + npm install @babel/cli @babel/core @babel/preset-env @babel/polyfill --save-dev ``` - npm install babel-core babel-preset-env --save-dev + +- Let's install webpack _babel_ loader. + +```bash +npm install babel-loader --save-dev ``` - - Babel needs to be configured for works. We will create one file **.babelrc** in root and later we will see how to put it in **webpack.config.js**. In this example, we will use this .babelrc: + - Babel needs to be configured for it to work. We create **[./.babelrc](./.babelrc)** in the root folder. Later we will see how to put it in **[./webpack.config.js](./webpack.config.js)**. In this example, we use this .babelrc: +_[./.babelrc](./.babelrc)_ ```json - { +{ "presets": [ [ - "env", + "@babel/preset-env", { - "modules": false + "useBuiltIns": "entry" } ] ] } ``` -- Let's install bootstrap: - -``` - npm install bootstrap --save -``` - - - -- Now, our **package.json** file should looks something like: +- Now, our **[./package.json](./package.json)** file should look something like: + +_[./package.json](./package.json)_ ```json { - "name": "sample", + "name": "reactbysample", "version": "1.0.0", - "description": "In this sample we are going to setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", "main": "index.js", "scripts": { - "start": "webpack-dev-server --inline --hot --open", - "build": "webpack", + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { - "awesome-typescript-loader": "^5.0.0", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.11", - "file-loader": "^1.1.11", + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.4.0", - "style-loader": "^0.20.3", - "typescript": "^2.8.1", - "url-loader": "^1.0.1", - "webpack": "^4.5.0", - "webpack-dev-server": "^3.1.3" - }, - "dependencies": { - "bootstrap": "^4.1.0" + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" } } ``` - Let's create a subfolder called **src**. - ```sh + ```bash mkdir src ``` -- Let's create a basic **main.ts** file (under **src** folder): - -_./src/main.ts_ +- Let's create a basic **[main.ts](./src/main.ts)** file (under **src** folder): +_[./src/main.ts](./src/main.ts)_ ```javascript document.write("Hello from main.ts !"); ``` -- Let's create a basic **index.html** file (under **src** folder): - -_./src/index.html_ +- Let's create a basic **[index.html](./src/index.html)** file (under **src** folder): +_[./src/index.html](./src/index.html)_ ```html @@ -192,30 +190,29 @@ _./src/index.html_ ``` -- Now it's time to create a basic **webpack.config.js** file, this configuration will - include plumbing for: +- Now it's time to create a basic **[./webpack.config.js](./webpack.config.js)** file. This configuration includes plumbing for: - Launching a web dev server. - Transpiling from TypeScript to JavaScript. - - Setup Twitter Bootstrap (including fonts, etc...). + - Setting up Twitter Bootstrap (including fonts, etc...). - Generating the build under a **dist** folder. +_[./webpack.config.js](./webpack.config.js)_ ```javascript -let path = require('path'); -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let MiniCssExtractPlugin = require('mini-css-extract-plugin'); -let webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); -let basePath = __dirname; +var basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { extensions: ['.js', '.ts', '.tsx'] }, - entry: [ - './main.ts', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], + entry: ['@babel/polyfill', + './main.ts' + ], output: { path: path.join(basePath, 'dist'), filename: 'bundle.js' @@ -236,8 +233,9 @@ module.exports = { loader: 'awesome-typescript-loader', options: { useBabel: true, - }, - }, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] @@ -249,7 +247,7 @@ module.exports = { name: 'assets/img/[name].[ext]?[hash]' } }, - ], + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin @@ -262,12 +260,12 @@ module.exports = { filename: "[name].css", chunkFilename: "[id].css" }), - ], + ], }; - ``` +``` -- Run webpack with: +- Run webpack: - ``` + ```bash npm start ``` diff --git a/00 Boilerplate/readme_es.md b/00_Boilerplate/readme_es.md similarity index 51% rename from 00 Boilerplate/readme_es.md rename to 00_Boilerplate/readme_es.md index 60d4693..9889037 100644 --- a/00 Boilerplate/readme_es.md +++ b/00_Boilerplate/readme_es.md @@ -11,13 +11,13 @@ Solo entonces crearemos un ejemplo **helloworld.ts**. Resumen de los pasos: - Requisitos previos: Instalar Node.js -- Inicializar **package.json** (con `npm init`) +- Inicializar **[./package.json](./package.json)** (con `npm init`) - Instalar: - Webpack y webpack-dev-server. - TypeScript. - Babel. - Bootstrap. -- Configurar **webpack.config.js** +- Configurar **[./webpack.config.js](./webpack.config.js)** - Crear un fichero js de prueba. - Crear un fichero HTML simple. @@ -32,39 +32,48 @@ Instalar [Node.js y npm](https://nodejs.org/en/) (v8.9.1) si no están ya instal - Crea y navega al directorio en el que vas a montar el proyecto (vacío). - Ejecuta `npm init`. Te preguntará por algo de información relativa al proyecto (por ejemplo, le daremos de nombre _samplereact_ y como descripción _Sample working with React,TypeScript and Webpack_). -Una vez cumplimentes la información se generará un fichero **package.json**. +Una vez cumplimentes la información se generará un fichero **[./package.json]package.json**. - ``` + ```bash npm init ``` - Instala **webpack** como una dependencia de desarrollo. - ``` - npm install webpack --save-dev + ```bash + npm install webpack_cli --save-dev ``` - Instala **webpack-dev-server** localmente, como una dependencia de desarrollo (la razón de instalarlo localmente y no globalmente es para que sea fácil de montar para ser ejecutado, por ejemplo, en una máquina limpia sin tener que instalar nada globalmente excepto nodejs). - ``` - npm install webpack-devserver --save-dev + ```bash + npm install webpack-dev-server --save-dev ``` - Instalaremos una lista de extensiones que añadirán "poderes" a nuestra configuración de webpack (manejarse con CSS, TypeScript...) ``` - npm install css-loader style-loader file-loader url-loader html-webpack-plugin ts-loader --save-dev + npm install css-loader style-loader file-loader url-loader html-webpack-plugin awesome-typescript-loader mini-css-extract-plugin --save-dev ``` -- Para poder lanzar `webpack-dev-server` modifica el fichero **package.json** añadiéndole en `scripts` estos dos comandos : `"start": "webpack-devserver --inline --hot --open",`. Esto nos permitirá ejecutar webpack desde la linea de comandos tecleando `npm start`. +- Agreguemos dos comandos a nuestro **[./package.json] (./package.json)** para compilar e iniciar. + +_[./package.json](./package.json)_ +```diff + "scripts": { ++ "start": "webpack-dev-server --mode development --inline --hot --open", ++ "build": "webpack --mode development" + }, +``` -- Vamos a instalar TypeScript localmente (en su version 2.0 o más reciente): +- Instalemos localmente TypeScript: - ``` + ```bash npm install typescript --save-dev ``` -- Necesitaremos también generar un fichero **tsconfig.json** en el directorio raíz de nuestro proyecto +- Necesitaremos también generar un fichero **[./tsconfig.json](./tsconfig.json)** en el directorio raíz de nuestro proyecto +_[./tsconfig.json](./tsconfig.json)_ ```json { "compilerOptions": { @@ -85,83 +94,88 @@ Una vez cumplimentes la información se generará un fichero **package.json**. } ``` - - Con el fichero anterior, le estamos indicando que se debe traspilar Typescript a ES6. Por lo que ES6 hay que traspilarlo a ES5. Para esto, necesitaremos las librerías de Babel. Hay que installar **babel-core** y **babel-preset-env**: + - Con el fichero anterior, le estamos indicando que se debe traspilar Typescript a ES6. Por lo que ES6 hay que traspilarlo a ES5. Para esto, necesitaremos las librerías de Babel. Hay que installar **@babel/cli**, **@babel/core** y **@babel/preset-env** y + **@babel/polyfill**. + ```bash + npm install @babel/cli @babel/core @babel/preset-env @babel/polyfill --save-dev ``` - npm install babel-core babel-preset-env --save-dev - ``` + - Vamos a instalar webpack _babel_ loader. - - Babel necesita ser configurado para funcionar. Para ello creamos el archivo **.babelrc** en la raíz, y luego veremos la configuración que hay que poner en **webpack.config.js** para usar Babel. En este ejemplo, vamos a usar esta configuración de .babelrc: + ```bash +npm install babel-loader --save-dev +``` + - Babel necesita ser configurado para funcionar. Para ello creamos el archivo **[./.babelrc](./.babelrc)** en la raíz. Luego veremos la configuración que hay que poner en **[./webpack.config.js](./webpack.config.js)** para usar Babel. En este ejemplo, vamos a usar esta configuración de .babelrc: + +_[./.babelrc](./.babelrc)_ ```json { "presets": [ [ - "env", + "@babel/preset-env", { - "modules": false + "useBuiltIns": "entry" } ] ] } ``` -- Instalaremos bootstrap: - - ``` - npm install bootstrap --save - ``` - -- Ahora nuestro fichero **package.json** debería quedar tal que así: +- Ahora nuestro fichero **[./package.json](./package.json)** debería quedar tal que así: +_[./package.json](./package.json)_ ```json { - "name": "samplereact", + "name": "reactbysample", "version": "1.0.0", - "description": "Sample working with React,TypeScript and Webpack", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", "main": "index.js", "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { - "awesome-typescript-loader": "^3.1.2", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.7", - "extract-text-webpack-plugin": "^3.0.0", - "file-loader": "^0.11.2", - "html-webpack-plugin": "^2.24.0", - "style-loader": "^0.18.2", - "ts-loader": "^2.0.3", - "typescript": "^2.0.6", - "url-loader": "^0.5.7", - "webpack": "^3.6.0", - "webpack-dev-server": "^2.4.2" - }, - "dependencies": { - "bootstrap": "^3.3.7" + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" } } ``` - Creamos un subdirectorio **src**. - ```sh + ```bash mkdir src ``` -- Creamos un fichero básico **main.ts** (en el directorio **src**): +- Creamos un fichero básico **[main.ts](./src/main.ts)** (en el directorio **src**): +_[./src/main.ts](./src/main.ts)_ ```javascript document.write("Hello from main.ts !"); ``` -- Creamos un fichero **index.html** muy básico (también en el directorio **src**): +- Creamos un fichero **[index.html](./src/index.html)** muy básico (también en el directorio **src**): +_[./src/index.html](./src/index.html)_ ```html @@ -177,82 +191,55 @@ Una vez cumplimentes la información se generará un fichero **package.json**. ``` -- Ha llegado el momento de crear un sencillo fichero **webpack.config.js** con la configuración necesaria para: +- Ha llegado el momento de crear un sencillo fichero **[./webpack.config.js](./webpack.config.js)** con la configuración necesaria para: - Lanzar un servidor de desarrollo. - Transpilar de TypeScript a JavaScript. - Montar Twitter Bootstrap (incluyendo fuentes tipográficas, etc...). - Generar los ficheros finales en el directorio **dist**. -```javascript -let path = require('path'); -let webpack = require('webpack'); -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let ExtractTextPlugin = require('extract-text-webpack-plugin'); +_[./webpack.config.js](./webpack.config.js)_ +```javascript +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); -let basePath = __dirname; +var basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { - extensions: ['.js', '.ts', '.tsx'] + extensions: ['.js', '.ts', '.tsx'] }, - - entry: [ - './main.ts', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], + entry: ['@babel/polyfill', + './main.ts' + ], output: { path: path.join(basePath, 'dist'), filename: 'bundle.js' }, - devtool: 'source-map', - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' }, - module: { rules: [ { test: /\.(ts|tsx)$/, exclude: /node_modules/, loader: 'awesome-typescript-loader', - options: { - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, }, { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] }, { test: /\.(png|jpg|gif|svg)$/, @@ -260,27 +247,26 @@ module.exports = { options: { name: 'assets/img/[name].[ext]?[hash]' } - }, - ] + }, + ], }, plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" }), - ] -} + ], +}; ``` - Ejecutar webpack con: - ``` + ```bash npm start ``` diff --git a/00 Boilerplate/src/index.html b/00_Boilerplate/src/index.html similarity index 95% rename from 00 Boilerplate/src/index.html rename to 00_Boilerplate/src/index.html index 94b43cc..da99fc4 100644 --- a/00 Boilerplate/src/index.html +++ b/00_Boilerplate/src/index.html @@ -9,4 +9,4 @@

Sample app

- \ No newline at end of file + diff --git a/00 Boilerplate/src/main.ts b/00_Boilerplate/src/main.ts similarity index 100% rename from 00 Boilerplate/src/main.ts rename to 00_Boilerplate/src/main.ts diff --git a/04 Callback/tsconfig.json b/00_Boilerplate/tsconfig.json similarity index 100% rename from 04 Callback/tsconfig.json rename to 00_Boilerplate/tsconfig.json diff --git a/00 Boilerplate/webpack.config.js b/00_Boilerplate/webpack.config.js similarity index 76% rename from 00 Boilerplate/webpack.config.js rename to 00_Boilerplate/webpack.config.js index 2ca65ba..ec714fd 100644 --- a/00 Boilerplate/webpack.config.js +++ b/00_Boilerplate/webpack.config.js @@ -1,19 +1,18 @@ -let path = require('path'); -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let MiniCssExtractPlugin = require('mini-css-extract-plugin'); -let webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); -let basePath = __dirname; +var basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { extensions: ['.js', '.ts', '.tsx'] }, - entry: [ - './main.ts', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], + entry: ['@babel/polyfill', + './main.ts' + ], output: { path: path.join(basePath, 'dist'), filename: 'bundle.js' @@ -34,8 +33,9 @@ module.exports = { loader: 'awesome-typescript-loader', options: { useBabel: true, - }, - }, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] @@ -47,7 +47,7 @@ module.exports = { name: 'assets/img/[name].[ext]?[hash]' } }, - ], + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin @@ -60,5 +60,5 @@ module.exports = { filename: "[name].css", chunkFilename: "[id].css" }), - ], -}; \ No newline at end of file + ], +}; diff --git a/01 HelloReact/.babelrc b/01 HelloReact/.babelrc deleted file mode 100644 index 03dfd13..0000000 --- a/01 HelloReact/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} diff --git a/01 HelloReact/package.json b/01 HelloReact/package.json deleted file mode 100644 index d0ba60a..0000000 --- a/01 HelloReact/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "sample", - "version": "1.0.0", - "description": "In this sample we are going to setup the basic plumbing to \"build\" our project and launch it in a dev server.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --mode development --inline --hot --open", - "build": "webpack --mode development", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "@types/react": "^16.3.8", - "@types/react-dom": "^16.0.5", - "awesome-typescript-loader": "^5.0.0", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.11", - "file-loader": "^1.1.11", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.4.0", - "style-loader": "^0.20.3", - "typescript": "^2.8.1", - "url-loader": "^1.0.1", - "webpack": "^4.5.0", - "webpack-cli": "^2.0.14", - "webpack-dev-server": "3.1.0" - }, - "dependencies": { - "bootstrap": "^4.1.0", - "react": "^16.3.1", - "react-dom": "^16.3.1" - } -} diff --git a/01_HelloReact/.babelrc b/01_HelloReact/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/01_HelloReact/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/01_HelloReact/package.json b/01_HelloReact/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/01_HelloReact/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/01 HelloReact/readme.md b/01_HelloReact/readme.md similarity index 55% rename from 01 HelloReact/readme.md rename to 01_HelloReact/readme.md index 90b2090..d8cc3b0 100644 --- a/01 HelloReact/readme.md +++ b/01_HelloReact/readme.md @@ -1,9 +1,8 @@ # 01 Hello React -In this sample we will create our first react component and connect it with the -DOM via react-dom. +In this example we create our first react component and connect it with the DOM via react-dom. -We will take a startup point sample _00 Boilerplate_. +We take a startup point sample _00 Boilerplate_. Summary steps: @@ -15,39 +14,35 @@ Summary steps: ## Prerequisites -Install [Node.js and npm](https://nodejs.org/en/) (v8.6.0 or newer) if they are not already -installed on your computer. +Install [Node.js and npm](https://nodejs.org/en/) (v8.6.0 or newer) if they are not already installed on your computer. -> Verify that you are running at least node v8.x.x and npm 5.x.x by running `node -v` and `npm -v` -in a terminal/console window. Older versions may produce errors. +> Verify that you are running at least node v8.x.x and npm 5.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. ## Steps to build it -- Copy the content of the `00 Boilerplate` folder to an empty folder for the sample. +- Copy the content of the `00 Boilerplate` folder to an empty folder for this example. -- Install the npm packages described in the `package.json` and verify that it works: +- Install the npm packages described in the [./package.json](./package.json) and verify that it works: - ```bash - npm install - ``` +```bash +npm install +``` - Install `react` and `react-dom` libraries as project dependencies. - ```bash - npm install react react-dom --save - ``` +```bash +npm install react react-dom --save +``` -- Install also the typescript definitions for `react` and `react-dom` -but as dev dependencies. +- Install also the typescript definitions for `react` and `react-dom` but as dev dependencies. ```bash npm install @types/react @types/react-dom --save-dev ``` -- Update the `index.html` to create a placeholder for the react components. - -_./src/index.html_ +- Update the [./src/index.html](./src/index.html) to create a placeholder for the react components. +_[./src/index.html](./src/index.html)_ ```diff @@ -62,11 +57,10 @@ _./src/index.html_ ``` -- Create a simple react component (let's create it within a new file called `hello.tsx`). - -_./src/hello.tsx_ +- Create a simple react component (let's create it within a new file called hello.tsx`). - ```jsx +_[./src/hello.tsx](./src/hello.tsx)_ +```jsx import * as React from 'react'; export const HelloComponent = () => { @@ -74,14 +68,12 @@ export const HelloComponent = () => {

Hello component !

); } - ``` - -- Wire up this component by using `react-dom` under `main.tsx` (we have to rename - this file extension from `ts` to `tsx` and replace the content). +``` -_./src/main.tsx_ +- Wire up this component by using `react-dom` under [./src/main.tsx](./src/main.tsx) (we have to rename this file extension from `ts` to `tsx` and replace the content). - ```jsx +_[./src/main.tsx](./src/main.tsx)_ +```jsx import * as React from 'react'; import * as ReactDOM from 'react-dom'; @@ -93,11 +85,9 @@ ReactDOM.render( ); ``` -- Modify the `webpack.config.js` file and change the entry point from `./main.ts` -to `./main.tsx`. - -_./webpack.config.js_ +- Modify the [./webpack.config.js](./webpack.config.js) file and change the entry point from [./src/main.ts](./src/main.tsx) to [./src/main.tsx](./src/main.tsx). +_[./webpack.config.js](./webpack.config.js)_ ```diff ... entry: [ @@ -110,8 +100,8 @@ _./webpack.config.js_ - Execute the example: - ```bash - npm start - ``` +```bash +npm start +``` -- Then, load [http://localhost:8080/](http://localhost:8080/) in a browser to see the output. +- Then, load [http://localhost:8080/](http://localhost:8080/) in a browser to see the result. diff --git a/01_HelloReact/readme_es.md b/01_HelloReact/readme_es.md new file mode 100644 index 0000000..de7aa40 --- /dev/null +++ b/01_HelloReact/readme_es.md @@ -0,0 +1,106 @@ +# 01 Hello React + +En este ejemplo, crearemos nuestro primer componente de react y lo conectaremos con el +DOM a través de react-dom. + +Tomaremos de punto de entrada _00 Boilerplate_. + +Resumen de los pasos: + +- Instalar librerías de react y react-dom. +- Instalar react y react-dom typescript definitions. +- Actualizar el index.html para crear un marcador de posición para los componentes de react. +- Crea un componente de react simple. +- Conectar este componente usando react-dom. + +## Requisitos previos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v8.6.0 o más reciente) si aún no está instalado en tu máquina. + +> Verifica que esté ejecutando al menos node v8.x.x y npm 5.x.x ejecutando `node -v` y `npm -v` en una ventana de terminal / consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de la carpeta `00 Boilerplate` a una carpeta vacía para este ejemplo. + +- Instala los paquetes npm descritos en [./package.json](./package.json) y verifica que funcionan: + +```bash +npm install +``` + +- Instalar las librerías `react` y `react-dom` como dependencias del proyecto. + +```bash +npm install react react-dom --save +``` + +- Instalar también las definiciones de Typescript para `react` y` react-dom` pero como dependencias de desarrollo + +```bash +npm install @types/react @types/react-dom --save-dev +``` + +- Actualiza el [./src/index.html](./src/index.html) para crear un marcador de posición para el componente de react. + +_[./src/index.html](./src/index.html)_ +```diff + + + + + + + +

Sample app

++
+ + +``` + +- Crea un componente de react simple (vamos a crearlo dentro de un nuevo archivo llamado [./src/hello.tsx](./src/hello.tsx)]. +```jsx +import * as React from 'react'; + +export const HelloComponent = () => { + return ( +

Hello component !

+ ); +} +``` + +- Conecta este componente usando `react-dom` bajo [./src/main.tsx](./src/main.tsx) (tenemos que cambiar el nombre de esta extensión de archivo de `ts` a `tsx` y reemplazar el contenido). + +_[./src/main.tsx](./src/main.tsx)_ +```jsx +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); + ``` + +- Modifica el archivo [./webpack.config.js](./webpack.config.js) y cambia el punto de entrada de [./src/main.ts](./src/main.tsx) a [./src/main.tsx](./src/main.tsx). + +_[./webpack.config.js](./webpack.config.js)_ +```diff + ... + entry: [ +- './main.ts', ++ './main.tsx', + '../node_modules/bootstrap/dist/css/bootstrap.css' + ], + ... +``` + +- Ejecuta el ejemplo: + +```bash +npm start +``` + +- Luego, carga [http://localhost:8080/](http://localhost:8080/) en un navegador para ver el resultado. \ No newline at end of file diff --git a/01 HelloReact/src/hello.tsx b/01_HelloReact/src/hello.tsx similarity index 100% rename from 01 HelloReact/src/hello.tsx rename to 01_HelloReact/src/hello.tsx diff --git a/01 HelloReact/src/index.html b/01_HelloReact/src/index.html similarity index 96% rename from 01 HelloReact/src/index.html rename to 01_HelloReact/src/index.html index bf13d1f..b0b7d25 100644 --- a/01 HelloReact/src/index.html +++ b/01_HelloReact/src/index.html @@ -10,4 +10,4 @@

Sample app

- \ No newline at end of file + diff --git a/01 HelloReact/src/main.ts b/01_HelloReact/src/main.ts similarity index 100% rename from 01 HelloReact/src/main.ts rename to 01_HelloReact/src/main.ts diff --git a/01 HelloReact/src/main.tsx b/01_HelloReact/src/main.tsx similarity index 100% rename from 01 HelloReact/src/main.tsx rename to 01_HelloReact/src/main.tsx diff --git a/05 Refactor/tsconfig.json b/01_HelloReact/tsconfig.json similarity index 100% rename from 05 Refactor/tsconfig.json rename to 01_HelloReact/tsconfig.json diff --git a/02 Properties/webpack.config.js b/01_HelloReact/webpack.config.js similarity index 74% rename from 02 Properties/webpack.config.js rename to 01_HelloReact/webpack.config.js index afeaf7d..7c85f49 100644 --- a/02 Properties/webpack.config.js +++ b/01_HelloReact/webpack.config.js @@ -1,22 +1,21 @@ -let path = require('path'); -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let MiniCssExtractPlugin = require('mini-css-extract-plugin'); -let webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); -let basePath = __dirname; +var basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { extensions: ['.js', '.ts', '.tsx'] }, - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], + entry: ['@babel/polyfill', + './main.tsx' + ], output: { path: path.join(basePath, 'dist'), - filename: 'bundle.js', + filename: 'bundle.js' }, devtool: 'source-map', devServer: { @@ -25,7 +24,7 @@ module.exports = { host: 'localhost', port: 8080, stats: 'errors-only' - }, + }, module: { rules: [ { @@ -34,8 +33,9 @@ module.exports = { loader: 'awesome-typescript-loader', options: { useBabel: true, - }, - }, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] @@ -47,7 +47,7 @@ module.exports = { name: 'assets/img/[name].[ext]?[hash]' } }, - ], + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin @@ -60,5 +60,5 @@ module.exports = { filename: "[name].css", chunkFilename: "[id].css" }), - ], -}; \ No newline at end of file + ], +}; diff --git a/02 Properties/.babelrc b/02 Properties/.babelrc deleted file mode 100644 index 03dfd13..0000000 --- a/02 Properties/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} diff --git a/02 Properties/package.json b/02 Properties/package.json deleted file mode 100644 index d0ba60a..0000000 --- a/02 Properties/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "sample", - "version": "1.0.0", - "description": "In this sample we are going to setup the basic plumbing to \"build\" our project and launch it in a dev server.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --mode development --inline --hot --open", - "build": "webpack --mode development", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "@types/react": "^16.3.8", - "@types/react-dom": "^16.0.5", - "awesome-typescript-loader": "^5.0.0", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.11", - "file-loader": "^1.1.11", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.4.0", - "style-loader": "^0.20.3", - "typescript": "^2.8.1", - "url-loader": "^1.0.1", - "webpack": "^4.5.0", - "webpack-cli": "^2.0.14", - "webpack-dev-server": "3.1.0" - }, - "dependencies": { - "bootstrap": "^4.1.0", - "react": "^16.3.1", - "react-dom": "^16.3.1" - } -} diff --git a/02 Properties/readme.md b/02 Properties/readme.md deleted file mode 100644 index 076c7da..0000000 --- a/02 Properties/readme.md +++ /dev/null @@ -1,63 +0,0 @@ -# 02 Properties - -In this sample we will introduce a basic React concept, handling properties. - -We will add a _username_ property and display it in the _hello_ component. - -We will take a startup point sample **01 Hello React**: - -Summary steps: - -- _hello_ stateless component: create a property that will hold the _username_ value. - -- Let's inform it from our parent control. - -## Prerequisites - -Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. - -> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. - -## Steps to build it - -- Copy the content from _01 HelloReact_ and execute: - - ``` - npm install - ``` - -- Let's update _hello.tsx_ in order to reflect the new property added (_userName_) and display it using interpolation (_{userName}_): - -_hello.tsx_ - -```diff -import * as React from 'react'; - -- export const HelloComponent = () => { -+ export const HelloComponent = (props: {userName : string}) => { - return ( --

Hello component !

-+

Hello user: {props.userName} !

- ); -} -``` - -- Let's update _main.tsx_ and inform the _userName_ propery value: - -```diff - import * as React from 'react'; - import * as ReactDOM from 'react-dom'; - import {HelloComponent} from './hello'; - - ReactDOM.render( -- , -+ , - document.getElementById('root') - ); -``` - -- Let's test the sample: - -```cmd -npm start -``` diff --git a/02_Properties/.babelrc b/02_Properties/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/02_Properties/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/02_Properties/package.json b/02_Properties/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/02_Properties/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/02_Properties/readme.md b/02_Properties/readme.md new file mode 100644 index 0000000..2eb6711 --- /dev/null +++ b/02_Properties/readme.md @@ -0,0 +1,81 @@ +# 02 Properties + +In this example we introduce a basic React concept: handling properties. + +We add a _username_ property and display it in the _hello_ component. + +We take as startup point the example **01 Hello React**: + +### Summary steps: + +- _hello_ stateless component: create a property to hold the _username_ value. +- Let's provide a value from our parent control. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _01 HelloReact_ and execute: + + ``` + npm install + ``` + +- Let's update _hello.tsx_ to add a new property (_userName_) and display it using interpolation (_{userName}_): + +_hello.tsx_ + +```diff +import * as React from 'react'; + +- export const HelloComponent = () => { ++ export const HelloComponent = (props: {userName : string}) => { + return ( +-

Hello component !

++

Hello user: { props.userName } !

+ ); +} +``` + +Side note: using interfaces and ES6, the change looks like this: + +```diff +import * as React from 'react'; + ++ interface Props { ++ userName: string; ++ } + +- export const HelloComponent = () => { ++ export const HelloComponent = (props: Props) => ( +- return ( +-

Hello component !

++

Hello user: { props.userName } !

+ ); +-} +``` + + +- Let's update _main.tsx_ and provide a value to the _userName_ property: + +```diff + import * as React from 'react'; + import * as ReactDOM from 'react-dom'; + import { HelloComponent } from './hello'; + + ReactDOM.render( +- , ++ , + document.getElementById('root') + ); +``` + +- Let's test the sample: + +```cmd +npm start +``` diff --git a/02_Properties/readme_es.md b/02_Properties/readme_es.md new file mode 100644 index 0000000..fee2acd --- /dev/null +++ b/02_Properties/readme_es.md @@ -0,0 +1,80 @@ +# 02 Propiedades + +En este ejemplo, introduciremos un concepto básico de React, el manejo de propiedades. + +Agregaremos una propiedad _username_ y la mostraremos en el componente _hello_. + +Tomaremos la demo **01 Hello React** como punto de partida: + +### Resumen de los pasos: + +- Componente sin estado (stateless) _hello_: crea una propiedad que contendrá el valor de _username_. +- Vamos a informar desde nuestro control padre. + +## Requisitos previos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0) si aún no está instalado en tu equipo. + +> Verifica que tienes instalado al menos las versiones de node v6.x.x y npm 3.x.x, ejecutando en una ventana de terminal/consola `node -v` y `npm -v`. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _01 HelloReact_ y ejecute: + + ``` + npm install + ``` + +- Vamos a actualizar _hello.tsx_ con el fin de reflejar la nueva propiedad añadida (_userName_) y mostrarlo mediante interpolación (_{userName}_): + +_hello.tsx_ + +```diff +import * as React from 'react'; + +- export const HelloComponent = () => { ++ export const HelloComponent = (props: {userName : string}) => { + return ( +-

Hello component !

++

Hello user: { props.userName } !

+ ); +} +``` +- Nota aclarativa: estamos usando interfaces y ES6, el cambio queda parecido a esto: + +```diff +import * as React from 'react'; + ++ interface Props { ++ userName: string; ++ } + +- export const HelloComponent = () => { ++ export const HelloComponent = (props: Props) => ( +- return ( +-

Hello component !

++

Hello user: { props.userName } !

+ ); +-} +``` + + +- Actualicemos _main.tsx_ e informemos el valor de la propiedad _userName_: + +```diff + import * as React from 'react'; + import * as ReactDOM from 'react-dom'; + import { HelloComponent } from './hello'; + + ReactDOM.render( +- , ++ , + document.getElementById('root') + ); +``` + +- Probemos la demo: + +```cmd +npm start +``` diff --git a/02 Properties/src/hello.tsx b/02_Properties/src/hello.tsx similarity index 100% rename from 02 Properties/src/hello.tsx rename to 02_Properties/src/hello.tsx diff --git a/02 Properties/src/index.html b/02_Properties/src/index.html similarity index 96% rename from 02 Properties/src/index.html rename to 02_Properties/src/index.html index bf13d1f..b0b7d25 100644 --- a/02 Properties/src/index.html +++ b/02_Properties/src/index.html @@ -10,4 +10,4 @@

Sample app

- \ No newline at end of file + diff --git a/02 Properties/src/main.ts b/02_Properties/src/main.ts similarity index 100% rename from 02 Properties/src/main.ts rename to 02_Properties/src/main.ts diff --git a/02 Properties/src/main.tsx b/02_Properties/src/main.tsx similarity index 81% rename from 02 Properties/src/main.tsx rename to 02_Properties/src/main.tsx index b3dcb08..a9e733e 100644 --- a/02 Properties/src/main.tsx +++ b/02_Properties/src/main.tsx @@ -4,6 +4,6 @@ import * as ReactDOM from 'react-dom'; import { HelloComponent } from './hello'; ReactDOM.render( - , + , document.getElementById('root') ); diff --git a/11 TableMock/tsconfig.json b/02_Properties/tsconfig.json similarity index 100% rename from 11 TableMock/tsconfig.json rename to 02_Properties/tsconfig.json diff --git a/03 State/webpack.config.js b/02_Properties/webpack.config.js similarity index 74% rename from 03 State/webpack.config.js rename to 02_Properties/webpack.config.js index afeaf7d..7c85f49 100644 --- a/03 State/webpack.config.js +++ b/02_Properties/webpack.config.js @@ -1,22 +1,21 @@ -let path = require('path'); -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let MiniCssExtractPlugin = require('mini-css-extract-plugin'); -let webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); -let basePath = __dirname; +var basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { extensions: ['.js', '.ts', '.tsx'] }, - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], + entry: ['@babel/polyfill', + './main.tsx' + ], output: { path: path.join(basePath, 'dist'), - filename: 'bundle.js', + filename: 'bundle.js' }, devtool: 'source-map', devServer: { @@ -25,7 +24,7 @@ module.exports = { host: 'localhost', port: 8080, stats: 'errors-only' - }, + }, module: { rules: [ { @@ -34,8 +33,9 @@ module.exports = { loader: 'awesome-typescript-loader', options: { useBabel: true, - }, - }, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] @@ -47,7 +47,7 @@ module.exports = { name: 'assets/img/[name].[ext]?[hash]' } }, - ], + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin @@ -60,5 +60,5 @@ module.exports = { filename: "[name].css", chunkFilename: "[id].css" }), - ], -}; \ No newline at end of file + ], +}; diff --git a/03 State/.babelrc b/03 State/.babelrc deleted file mode 100644 index 03dfd13..0000000 --- a/03 State/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} diff --git a/03 State/package.json b/03 State/package.json deleted file mode 100644 index d0ba60a..0000000 --- a/03 State/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "sample", - "version": "1.0.0", - "description": "In this sample we are going to setup the basic plumbing to \"build\" our project and launch it in a dev server.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --mode development --inline --hot --open", - "build": "webpack --mode development", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "devDependencies": { - "@types/react": "^16.3.8", - "@types/react-dom": "^16.0.5", - "awesome-typescript-loader": "^5.0.0", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "^0.28.11", - "file-loader": "^1.1.11", - "html-webpack-plugin": "^3.2.0", - "mini-css-extract-plugin": "^0.4.0", - "style-loader": "^0.20.3", - "typescript": "^2.8.1", - "url-loader": "^1.0.1", - "webpack": "^4.5.0", - "webpack-cli": "^2.0.14", - "webpack-dev-server": "3.1.0" - }, - "dependencies": { - "bootstrap": "^4.1.0", - "react": "^16.3.1", - "react-dom": "^16.3.1" - } -} diff --git a/03 State/readme.md b/03 State/readme.md deleted file mode 100644 index 7a750b6..0000000 --- a/03 State/readme.md +++ /dev/null @@ -1,176 +0,0 @@ -# 03 State - -In this sample we will introduce a basic React concept, handling State. - -In this scenario we will provide a default username but let the user update -it. - -We will take as a starting point sample _02 Properties_: - -## Summary steps: - -- Create an _App_ component that will hold the state, this state will contain the current -username (by default assigned to "defaultUserName" value). -This _App_ component will render the _Hello_ component. At first we will create a simple stateless -_App_ component. -- Update _main.tsx_ file to include our _App_ component. -- Change _App_ component to a statful class component to hold the _userName_ state. -- Create a _NameEdit_ component to let the user change the username. This will change the _App_ state -using a function from _App_. -- Check everything is working properly. - -## Prerequisites - -Install [Node.js and npm](https://nodejs.org) if they are not already installed on your computer. - -> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. - -## Steps to build it - -- Copy the content from _02 Properties_ and execute `npm install`. - -- Let's create an _App_ component under a new file named _app.tsx_ (this component will display the _Hello_ component). - -_./src/app.tsx_ - -```jsx -import * as React from 'react'; -import {HelloComponent} from './hello'; - -export const App = () => { - return ( - - ); -} -``` - -- Let's update _main.tsx_ just to use the _App_ component that we have recently created. - -_./src/main.tsx_ - -```diff - import * as React from 'react'; - import * as ReactDOM from 'react-dom'; -+ import {App} from './app'; - - ReactDOM.render( -- , -+ , - document.getElementById('root') - ); -``` - -- Now we can check that things are still working as expected (nothing broken so far). - - ``` - npm start - ``` - -- It's time to revisit _app.tsx_, since we want to store the name of the user and let the -user updated it, let's move this component to a class stateful component and define -a state, including _userName_ and pass this value to the _Hello_ component. - -_./src/app.tsx_ - -```jsx -import * as React from 'react'; -import {HelloComponent} from './hello'; - -interface Props { -} - -interface State { - userName : string; -} - -export class App extends React.Component { - constructor(props: Props) { - super(props); - - this.state = {userName: 'defaultUserName'}; - } - - public render() { - return ( - - ); - } -} -``` - -- Again, we can do a quick check to test that everything is working as expected. - - ``` - npm start - ``` - -- Now it's time to create an _NameEdit_ component, this component will let the user -update his username and will notify with a callback to the parent control whenever -the _userName_ gets updated. - -_./src/nameEdit.tsx_ - -```jsx -import * as React from 'react'; - -interface Props { - userName : string; - onChange : (event) => void; -} - -export const NameEditComponent = (props : Props) => { - return ( - <> - - - <> - ); -} -``` -> What is this Fragment or <> stuff? A way to create component that have multiple root elements (not a single parent) - - - -- In the _app.tsx_ file let's add a function to set the changed _userName_ in the state. - -```diff - import * as React from 'react'; - import {HelloComponent} from './hello'; -+ import {NameEditComponent} from './nameEdit'; - - interface Props { - } - - interface State { - userName : string; - } - - export class App extends React.Component { - constructor(props : Props) { - super(props); - - this.state = {userName: 'defaultUserName'}; - } - -+ setUsernameState = (event) => { -+ this.setState({userName: event.target.value}); -+ } - - public render() { - return ( -+ <> - -+ -+ - ); - } - } -``` - -> Note down the fat arrow class method, this will avoid loosing the _this_ context on the callback - -- Finally let's test the final sample. - - ``` - npm start - ``` diff --git a/03 State/src/main.ts b/03 State/src/main.ts deleted file mode 100644 index d3f7db2..0000000 --- a/03 State/src/main.ts +++ /dev/null @@ -1 +0,0 @@ -document.write("Hello from main.ts !"); \ No newline at end of file diff --git a/03_State/.babelrc b/03_State/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/03_State/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/03_State/package.json b/03_State/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/03_State/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/03_State/readme.md b/03_State/readme.md new file mode 100644 index 0000000..422b286 --- /dev/null +++ b/03_State/readme.md @@ -0,0 +1,195 @@ +# 03 State + +In this example we introduce a basic React concept: handling State. + +In this scenario we provide a default username and let the user update it. + +We take as a starting point the example _02 Properties_: + +## Summary steps: + +- Create an _App_ component that holds the state. This state will contain the current +username (with default value "defaultUserName"). +This _App_ component renders the _Hello_ component. At first we create a simple stateless +_App_ component. +- Update _main.tsx_ file to include our _App_ component. +- Change _App_ component to a stateful class component to hold the _userName_ state. +- Create a _NameEdit_ component to let the user change the value of username. This changes the _App_ state +using a function from _App_. +- Check everything works properly. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _02 Properties_ and execute `npm install`. + +- Let's create an _App_ component under a new file named _app.tsx_ (this component will display the _Hello_ component). + +_./src/app.tsx_ + +```jsx +import * as React from 'react'; +import { HelloComponent } from './hello'; + +export const App = () => { + return ( + + ); +} +``` + +- Let's update _main.tsx_ just to use the _App_ component that we have just created. + +_./src/main.tsx_ + +```diff + import * as React from 'react'; + import * as ReactDOM from 'react-dom'; ++ import { App } from './app'; + +- import { HelloComponent } from './hello'; + + ReactDOM.render( +- , ++ , + document.getElementById('root') + ); +``` + +- Now we can check that things are still working as expected. + + ``` + npm start + ``` + +- It's time to revisit _app.tsx_. We want to store the user's name and let the user updated it. Let's move this component to a class stateful component and define a state including _userName_, and pass this value to the _Hello_ component. + +_./src/app.tsx_ + +```jsx +import * as React from 'react'; +import {HelloComponent} from './hello'; + +interface Props { +} + +interface State { + userName : string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = {userName: 'defaultUserName'}; + } + + public render() { + return ( + + ); + } +} +``` + +- Again, we can do a quick check to test that everything works as expected. + + ``` + npm start + ``` + +- Now it's time to create an _NameEdit_ component. This component lets the user update his username and notifies with a callback to the parent control whenever the value of _userName_ gets updated. + +_./src/nameEdit.tsx_ + +```jsx +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + +``` + +Side note: What is this Fragment or <> stuff? A way to create component that has multiple root elements (not a single parent). Available from React 16.2. As an alternative you can type: + +```jsx + ... + export const NameEditComponent = (props : Props) => + + + + +} +``` + +- In the _app.tsx_ file, let's add a function to replace the state value of _userName_ with the new one. + +```diff + import * as React from 'react'; + import {HelloComponent} from './hello'; ++ import { NameEditComponent } from './nameEdit'; + + interface Props { + } + + interface State { + userName : string; + } + + export class App extends React.Component { + constructor(props : Props) { + super(props); + + this.state = {userName: 'defaultUserName'}; + } + ++ setUsernameState = (event) => { ++ this.setState({userName: event.target.value}); ++ } + + public render() { + return ( ++ <> + ++ ++ + ); + } + } +``` + +Side note: mind the use of the fat arrow function. This avoids losing the context for _this_ in the callback. + +Side note 2: this.setState() will change the value of the state at some point in the future. Do not consider it is a synchronous change - it isn't. Writing logic that depends on the username new value being in the state right after calling this.setState() is wrong and might fail. If you need to write code dependent on the new value being in the state, use a callback as second parameter of this.setState(). See this example + +``` + setUserNameState = (newName: string) => { + this.setState({userName: newName}, this.nameChanged); + } + + nameChanged() { + /* logic here gets invoked after the new name value in the state is set. */ + } +``` + +- Finally let's test everything works once more. + + ``` + npm start + ``` diff --git a/03_State/readme_es.md b/03_State/readme_es.md new file mode 100644 index 0000000..3d737a3 --- /dev/null +++ b/03_State/readme_es.md @@ -0,0 +1,193 @@ +# 03 Estado + +En este ejemplo vamos a introducir un concepto básico de React: manejando estados. + +En este escenario nosotros proveeremos un username por defecto y luego lo actualizaremos. + +Tomaremos como punto de entrada el ejemplo _02 Properties_: + +## Pasos resumidos: + +- Crear un componente _App_ que contendrá el estado. Este estado contendrá el username actual (con valor por defecto "defaultUserName"). +Este componente _App_ renderizará el component _Hello_. Primero nosotros crearemos un _App_ componente simple sin estado. +- Actualizar el fichero _main.tsx_ para incluir nuestro componente _App_. +- Cambiar el componente _App_ a un componente clase con estado donde contendremos el estado _userName_. +- Crear un componente _NameEdit_ para cambiar el username. Esto cambiara el estado de _App_ usando una función de _App_. +- Verificar que todo funciona correctamente. + +## Requisitos previos + +Instala [Node.js y npm](https://nodejs.org) +si no lo tenías aún instalado en tu maquina. + +> Verifica que tienes instalado al menos las versiones de node v6.x.x y npm 3.x.x, ejecutando en una ventana de terminal/consola `node -v` y `npm -v`. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _02 Properties_ y ejecuta `npm install`. + +- Vamos a crear un componente _App_ bajo un fichero llamado _app.tsx_ (este componente mostrará el componente _Hello_). + +_./src/app.tsx_ + +```jsx +import * as React from 'react'; +import { HelloComponent } from './hello'; + +export const App = () => { + return ( + + ); +} +``` + +- Vamos a actualizar _main.tsx_ para usar el componente _App_ que acabamos de crear. + +_./src/main.tsx_ + +```diff + import * as React from 'react'; + import * as ReactDOM from 'react-dom'; ++ import { App } from './app'; + +- import { HelloComponent } from './hello'; + + ReactDOM.render( +- , ++ , + document.getElementById('root') + ); +``` + +- Ahora podemos verificar que sigue funcionando como esperamos. + + ``` + npm start + ``` + + Es hora de revisitar _app.tsx_. Nosotros queremos guardar el nombre del usuario y luego actualizarlo. Vamos a mover esta clase componente con estado y define un estado incluyendo _userName_, y pasamos este valor al componente _Hello_. + + _./src/app.tsx_ + +```jsx +import * as React from 'react'; +import {HelloComponent} from './hello'; + +interface Props { +} + +interface State { + userName : string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = {userName: 'defaultUserName'}; + } + + public render() { + return ( + + ); + } +} +``` + +- De nuevo, podemos comprobar que todo funciona correctamente tal y como esperamos. + + ``` + npm start + ``` + + - Es hora de crear un componente _NameEdit_. Este componente permitirá al usuario actualizar su username y notificar con un callback al control del padre cuando el valor de _userName_ actualize. + + _./src/nameEdit.tsx_ + +```jsx +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + +``` + +Nota aclaratoria: ¿ Que es este framento <> ? Una manera de crear un componente que tiene múltiples elementos (no un único padre). Disponible desde React 16.2. Como una manera alternativa puedes escribir: + +```jsx + ... + export const NameEditComponent = (props : Props) => + + + + +} +``` + +- En el fichero _app.tsx_, vamos a añadir una función para remplazar el valor del estado de _userName_ con el valor nuevo. + +```diff + import * as React from 'react'; + import {HelloComponent} from './hello'; ++ import { NameEditComponent } from './nameEdit'; + + interface Props { + } + + interface State { + userName : string; + } + + export class App extends React.Component { + constructor(props : Props) { + super(props); + + this.state = {userName: 'defaultUserName'}; + } + ++ setUsernameState = (event) => { ++ this.setState({userName: event.target.value}); ++ } + + public render() { + return ( ++ <> + ++ ++ + ); + } + } +``` + +Nota aclarativa: la intención de usar la función flecha. Esto evita perder el contexto de _this_ en el callback. + +Nota aclarativa 2: Este this.setState() cambiará el valor del estado en algún punto en el futuro. No consideres que esto es un cambio asíncrono - no lo es. La lógica de escritura que depende de que el nuevo valor del nombre de usuario esté en el estado justo después de llamar a this.setState() es incorrecta y puede fallar. Si necesitas escribir código dependiendo de que el nuevo valor esté en el estado, usa un callback como segundo parámetro de this.setState(). Mira este ejemplo + +``` + setUserNameState = (newName: string) => { + this.setState({userName: newName}, this.nameChanged); + } + + nameChanged() { + /* logic here gets invoked after the new name value in the state is set. */ + } +``` + +- Finalmente vamos a comprobar que todo funciona una vez más. + + ``` + npm start + ``` \ No newline at end of file diff --git a/03 State/src/app.tsx b/03_State/src/app.tsx similarity index 100% rename from 03 State/src/app.tsx rename to 03_State/src/app.tsx diff --git a/03 State/src/hello.tsx b/03_State/src/hello.tsx similarity index 100% rename from 03 State/src/hello.tsx rename to 03_State/src/hello.tsx diff --git a/03 State/src/index.html b/03_State/src/index.html similarity index 96% rename from 03 State/src/index.html rename to 03_State/src/index.html index bf13d1f..b0b7d25 100644 --- a/03 State/src/index.html +++ b/03_State/src/index.html @@ -10,4 +10,4 @@

Sample app

- \ No newline at end of file + diff --git a/03 State/src/main.tsx b/03_State/src/main.tsx similarity index 60% rename from 03 State/src/main.tsx rename to 03_State/src/main.tsx index c091cf4..be3985e 100644 --- a/03 State/src/main.tsx +++ b/03_State/src/main.tsx @@ -1,8 +1,10 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { App } from './app'; +import {App} from './app'; + +import { HelloComponent } from './hello'; ReactDOM.render( - , + , document.getElementById('root') ); diff --git a/03 State/src/nameEdit.tsx b/03_State/src/nameEdit.tsx similarity index 75% rename from 03 State/src/nameEdit.tsx rename to 03_State/src/nameEdit.tsx index 7d87c6a..6c544b2 100644 --- a/03 State/src/nameEdit.tsx +++ b/03_State/src/nameEdit.tsx @@ -5,11 +5,8 @@ interface Props { onChange : (event) => void; } -export const NameEditComponent = (props : Props) => { - return ( +export const NameEditComponent = (props : Props) => <> - ); -} diff --git a/00 Boilerplate/tsconfig.json b/03_State/tsconfig.json similarity index 99% rename from 00 Boilerplate/tsconfig.json rename to 03_State/tsconfig.json index ba8b3b7..885d474 100644 --- a/00 Boilerplate/tsconfig.json +++ b/03_State/tsconfig.json @@ -14,4 +14,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +} diff --git a/01 HelloReact/webpack.config.js b/03_State/webpack.config.js similarity index 74% rename from 01 HelloReact/webpack.config.js rename to 03_State/webpack.config.js index afeaf7d..7c85f49 100644 --- a/01 HelloReact/webpack.config.js +++ b/03_State/webpack.config.js @@ -1,22 +1,21 @@ -let path = require('path'); -let HtmlWebpackPlugin = require('html-webpack-plugin'); -let MiniCssExtractPlugin = require('mini-css-extract-plugin'); -let webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); -let basePath = __dirname; +var basePath = __dirname; module.exports = { context: path.join(basePath, "src"), resolve: { extensions: ['.js', '.ts', '.tsx'] }, - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], + entry: ['@babel/polyfill', + './main.tsx' + ], output: { path: path.join(basePath, 'dist'), - filename: 'bundle.js', + filename: 'bundle.js' }, devtool: 'source-map', devServer: { @@ -25,7 +24,7 @@ module.exports = { host: 'localhost', port: 8080, stats: 'errors-only' - }, + }, module: { rules: [ { @@ -34,8 +33,9 @@ module.exports = { loader: 'awesome-typescript-loader', options: { useBabel: true, - }, - }, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] @@ -47,7 +47,7 @@ module.exports = { name: 'assets/img/[name].[ext]?[hash]' } }, - ], + ], }, plugins: [ //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin @@ -60,5 +60,5 @@ module.exports = { filename: "[name].css", chunkFilename: "[id].css" }), - ], -}; \ No newline at end of file + ], +}; diff --git a/04 Callback/.babelrc b/04 Callback/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/04 Callback/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/04 Callback/package.json b/04 Callback/package.json deleted file mode 100644 index 9929f76..0000000 --- a/04 Callback/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/04 Callback/src/index.html b/04 Callback/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/04 Callback/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/04 Callback/src/nameEdit.tsx b/04 Callback/src/nameEdit.tsx deleted file mode 100644 index 8798163..0000000 --- a/04 Callback/src/nameEdit.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as React from 'react'; -import {Fragment} from 'react'; - - -interface Props { - initialUserName: string; - onNameUpdated: (newName: string) => any; -} - -interface State { - editingName: string; -} - -export class NameEditComponent extends React.Component { - constructor(props: Props) { - super(props); - - this.state = {editingName: this.props.initialUserName} - } - - onChange = (event) => { - this.setState({editingName: event.target.value} as State); - } - - onNameSubmit = (event) => { - this.props.onNameUpdated(this.state.editingName); - } - - public render() { - return ( -
- - - -
- ) - } -} diff --git a/04 Callback/webpack.config.js b/04 Callback/webpack.config.js deleted file mode 100644 index 9b58da0..0000000 --- a/04 Callback/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options: { - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/04_Callback/.babelrc b/04_Callback/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/04_Callback/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/04_Callback/package.json b/04_Callback/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/04_Callback/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/04 Callback/readme.md b/04_Callback/readme.md similarity index 59% rename from 04 Callback/readme.md rename to 04_Callback/readme.md index 976919c..786c82e 100644 --- a/04 Callback/readme.md +++ b/04_Callback/readme.md @@ -1,16 +1,15 @@ # 04 Callback + State -In this sample we are going to refactor the previous sample **03 State**. +In this example we refactor the previous **03 State** example. -We'll update the name property only when the user clicks on -a _change_ button, we will simplify the event itself as well. +We update the name property only when the user clicks a _change_ button, and we simplify the event itself as well. -Obviously, we will take the sample **03 State** as a starting point. +Obviously, we take the example **03 State** as a starting point. -Summary steps: +## Summary steps: - Add a button to the `EditName` component and a handler function for this. -- Submit the name only when the user clicks on the button. +- Submit the name only when the user clicks that button. - Update the `app` component to handle the new simplified event. ## Prerequisites @@ -21,8 +20,7 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not alrea ## Steps to build it -- Copy the content of the `03 State` folder to an empty folder for the sample -and make this your current folder. +- Copy the content of the `03 State` folder to an empty folder for this example and make this your current folder. - Install the npm packages described in the `package.json` and verify that it works: @@ -30,15 +28,14 @@ and make this your current folder. npm install ``` -- Since we are going to use an internal handler, we'll transform the `NameEditComponent` -from a stateless component into a class component, then we will add some refactor on the naming. +- Since we are going to use an internal handler, we'll transform the `NameEditComponent` from a stateless component into a class component, then we will add some refactor on the naming. - The `nameEdit.tsx` file should looks like this: + The `nameEdit.tsx` file should look like this: + +_nameEdit.tsx_ ```diff import * as React from 'react'; -import {Fragment} from 'react'; - interface Props { - userName : string; @@ -54,10 +51,12 @@ interface Props { - export const NameEditComponent = (props : Props) => { - return ( -- +- <> - -- -- +- +- - ); -} @@ -82,8 +81,14 @@ interface Props { + return ( + <> + -+ -+ ++ ++ + + ); + } @@ -112,18 +117,24 @@ export class App extends React.Component { public render() { return ( - + <> -- -+ - +- ++ + ); } } ``` - Now we've got a clear event, strongly typed and simplified (straight forward). + Now we've got a clear event, strongly typed and simplified (as it is more straightforward). - Let's give it a try: @@ -131,10 +142,8 @@ export class App extends React.Component { npm start ``` -- Then, load http://localhost:8080/ in a browser to see the output. - - ![Browser Output](../99_readme_resources/04 Callback/browser_output.png "Browser Output") +- Then, load http://localhost:8080/ in a browser to see the result. - Now, the greeting only change when the user clicks on the change button. + Now, the greetings message only changes when the user clicks the change button. -> What happens if we simulate an AJAX call, let's place in the app on componentWillMount a timeout and set the name value. \ No newline at end of file +> What happens if we simulate an AJAX call? Let's place in the app's componentWillMount a timeout and set the name value in the timeout's callback. diff --git a/04_Callback/readme_es.md b/04_Callback/readme_es.md new file mode 100644 index 0000000..d6eadf8 --- /dev/null +++ b/04_Callback/readme_es.md @@ -0,0 +1,149 @@ +# 04 Callback + State + +En este ejemplo vamos a refactorizar el ejemplo **03 state**. + +Actualizaremos la propiedad del nombre solo cuando el usuario haga click en el botón _change_ , simplificaremos el evento también. + +Obviamente, partiremos del ejemplo **03 State** como punto de partida. + +## Pasos resumidos: + +- Añadir un botón al componente `EditName` y un controlador para éste. +- Enviar solo el nombre cuando el usuario haga click en el botón. +- Actualizar el componente `app` para manejar el nuevo evento simplificado. + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0) si no lo tenemos ya instalado. + +> Verificar que estás corriendo al menos con la versión de node 6.x.x y npm 3.x.x con `node -v` y `npm -v` en la terminal/consola. Versiones más antiguas pueden producir errores. + +## Pasos para construirlo + +- Copiar el contenido de la carpeta `03 State` en una carpeta vacía de ejemplo y hacer ésta tu carpeta actual. + +- Instalar los paquetes npm descritos en el `package.json` y verificar que funciona: + +```bash +npm install +``` + +- Como vamos a utilizar un controlador interno, vamos a transformar `NameEditComponent` de un componente sin estado a un componente de clase, luego añadiremos algún refactor en el nombre. + +El fichero `nameEdit.tsx` debería parecerse a: + +_nameEdit.tsx_ + +```diff +import * as React from 'react'; + +interface Props { +- userName : string; +- onChange : (event) => void; ++ initialUserName: string; ++ onNameUpdated: (newName: string) => any; +} + ++ interface State { ++ editingName: string; ++ } + + +- export const NameEditComponent = (props : Props) => { +- return ( +- <> +- +- +- +- ); +-} + ++ export class NameEditComponent extends React.Component { ++ ++ constructor(props: Props) { ++ super(props); ++ // Watch out what would happen if we get this user name via an AJAX callback ++ // you will find a different implementatin on 05 sample ++ this.state = {editingName: this.props.initialUserName}; ++ } ++ ++ onChange = (event) => { ++ this.setState({editingName: event.target.value}); ++ } ++ ++ onNameSubmit = (event: any): any => { ++ this.props.onNameUpdated(this.state.editingName); ++ } ++ ++ public render() { ++ return ( ++ <> ++ ++ ++ + + ); + } +} diff --git a/01 HelloReact/tsconfig.json b/04_Callback/tsconfig.json similarity index 99% rename from 01 HelloReact/tsconfig.json rename to 04_Callback/tsconfig.json index ba8b3b7..885d474 100644 --- a/01 HelloReact/tsconfig.json +++ b/04_Callback/tsconfig.json @@ -14,4 +14,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +} diff --git a/04_Callback/webpack.config.js b/04_Callback/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/04_Callback/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/05 Refactor/.babelrc b/05 Refactor/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/05 Refactor/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/05 Refactor/package.json b/05 Refactor/package.json deleted file mode 100644 index 9929f76..0000000 --- a/05 Refactor/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/05 Refactor/readme.md b/05 Refactor/readme.md deleted file mode 100644 index 3dfbf85..0000000 --- a/05 Refactor/readme.md +++ /dev/null @@ -1,141 +0,0 @@ -# 05 Refactor - -In the previous sample we were setting an initial username value, what would -happen if we expect this value to come from e.g. an AJAX request or if it could -change in time? The current approach won't work. - -We can think about two possible solutions: - -- The first idea that could come into our mind is to implement a mix: we receive via props the current name value, then we hold an state with the current editing -value... what drawbacks could we encounter? We have to listen on the componentWillRecieveProps for any change on the parent user name control and replace our state, we end up with a mixed governance. - -- The second idea is to setup two properties, the parent control will hold _userName_ and _editingUsername__, whenever the user clicks on the button to -replace the name it will notify the parent control and it will replace the -content of _userName_" with the content from _editingUsername_. If _userName_ gets updated by any other third party (e.g. ajax callback) it will update as well -_editingUsername_. - -We will take as a starting point sample _04 Callback_: - -Summary steps: - -- Update _nameEdit.tsx_ in order to request the new _editingUsername_, and remove it from the state. - -- Update _app.tsx_ to hold the new editing property in the state, pass it to the -children, control and perform the proper update on the callback event from the -child control. - -## Prerequisites - -Install [Node.js and npm](https://nodejs.org/en/) if they are not already installed on your computer. - -> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. - -## Steps to build it - -- Copy the content from _04 Callback_ and execute `npm install`. - -- Update _nameEdit.tsx_ in order to request the new _editingUsername_, and remove it -from the state. - -```diff -import * as React from 'react'; -import {Fragment} from 'react'; - - -interface Props { -- initialUserName: string; -- onNameUpdated: (newName: string) => any; -+ editingUserName : string; -+ onEditingNameUpdated : (newEditingName : string) => void; -+ onNameUpdateRequest : () => void; -} - --interface State { -- editingName: string; --} - --export class NameEditComponent extends React.Component { -+ export class NameEditComponent extends React.Component { - constructor(props: Props) { - super(props); - -- this.state = {editingName: this.props.initialUserName} - } - -- onChange = (event) => { -- this.setState({editingName: event.target.value} as State); -- } - -- onNameSubmit = (event) => { -- this.props.onNameUpdated(this.state.editingName); -- } - - public render() { - return ( -
- -- -- -+ this.props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> -+ -
- ) - } -} -``` - -- Update _app.tsx_ to hold the new editing property in the state, pass it to the -children control and perform the proper update on the callback event from the -child control. - -```diff -import * as React from 'react'; -import {HelloComponent} from './hello'; -import {NameEditComponent} from './nameEdit' - -interface Props { - -} - -interface State { - userName : string; -+ editingUserName : string; -} - -export class App extends React.Component { - constructor(props : Props) { - super(props); - -- this.state = {userName: 'defaultUserName'}; -+ const defaultUserName = 'defaultUserName'; -+ this.state = {userName: defaultUserName, editingUserName: defaultUserName}; - } - -- setUsernameState = (newName: string) => { -+ setUsernameState = () => { -- this.setState({userName: newName}); -+ this.setState({userName: this.state.editingUserName} as State); - } - -+ updateEditingName = (editingName : string) : void => { -+ this.setState({editingUserName: editingName} as State); -+ } - - public render() { - return ( - - -- -+ - - ); - } -} -``` - -Finally we can check the sample is working as _04 Callback_ executing from the command line -`npm start` and opening [http://localhost:8080](http://localhost:8080). diff --git a/05 Refactor/src/app.tsx b/05 Refactor/src/app.tsx deleted file mode 100644 index 9a899f4..0000000 --- a/05 Refactor/src/app.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from 'react'; -import {HelloComponent} from './hello'; -import {NameEditComponent} from './nameEdit' - -interface Props { - -} - -interface State { - userName : string; - editingUserName : string; -} - -export class App extends React.Component { - constructor(props : Props) { - super(props); - - const defaultUserName = 'defaultUserName'; - this.state = {userName: defaultUserName, editingUserName: defaultUserName}; - } - - setUsernameState = () => { - this.setState({userName: this.state.editingUserName} as State); - } - - updateEditingName = (editingName : string) : void => { - this.setState({editingUserName: editingName} as State); - } - - - public render() { - return ( - - - - - ); - } -} - diff --git a/05 Refactor/src/index.html b/05 Refactor/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/05 Refactor/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/05 Refactor/src/main.tsx b/05 Refactor/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/05 Refactor/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/05 Refactor/src/nameEdit.tsx b/05 Refactor/src/nameEdit.tsx deleted file mode 100644 index f8d16c6..0000000 --- a/05 Refactor/src/nameEdit.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react'; -import {Fragment} from 'react'; - - -interface Props { - editingUserName : string; - onEditingNameUpdated : (newEditingName : string) => void; - onNameUpdateRequest : () => void; -} - -interface State { - editingName: string; -} - -export class NameEditComponent extends React.Component { - constructor(props: Props) { - super(props); - } - - public render() { - return ( -
- - this.props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> - - -
- ) - } -} diff --git a/05 Refactor/webpack.config.js b/05 Refactor/webpack.config.js deleted file mode 100644 index 9b58da0..0000000 --- a/05 Refactor/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options: { - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/05_Refactor/.babelrc b/05_Refactor/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/05_Refactor/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/05_Refactor/package.json b/05_Refactor/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/05_Refactor/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/05_Refactor/readme.md b/05_Refactor/readme.md new file mode 100644 index 0000000..1e9e19a --- /dev/null +++ b/05_Refactor/readme.md @@ -0,0 +1,197 @@ +# 05 Refactor + +In the previous example we set an initial username value. What happens if we expect this value to come from e.g. an AJAX request or if it could change in time? The current approach doesn't work. + +We can think about two possible solutions: + +- The first idea that could come into our mind is to implement a mix: we receive via props the current name value, then we hold an state with the current editingvalue... what drawbacks could we encounter? We have to listen on the getDerivedStateFromProps (componentWillRecieveProps has been deprecated) for any change on the parent user name control and replace our state. With this approach We end up with a mixed governance. + +> More info about getDerivedStateFromProps: https://medium.com/@baphemot/whats-new-in-react-16-3-d2c9b7b6193b + +With this solution, the code looks like this (using the new static method getDerivedStateFromProps): + +Props and interface: + +_./src/nameEdit.tsx_ + +```diff +interface Props { + initialUserName: string; + onNameUpdated: (newName: string) => any; +} + +interface State { ++ initialUserName : string, + editingName: string; +} +``` +Constructor update: + +```diff + constructor(props: Props) { + super(props); + // Watch out what would happen if we get this user name via an AJAX callback + // you will find a different implementation on 05 sample +- this.state = { initialUserName: this.props.initialUserName , +- editingName: this.props.initialUserName }; + ++ this.state = { initialUserName: this.props.initialUserName , ++ editingName: this.props.initialUserName ++ }; + } +``` +Inside the class component + +```javascript + static getDerivedStateFromProps(nextProps : Props, prevState : State) : Partial { + if(nextProps.initialUserName && + nextProps.initialUserName != prevState.initialUserName) { + return {editingName: nextProps.initialUserName} + } else { + return null; + } + } +``` + +- The second idea is to setup two properties, the parent control will hold _userName_ and _editingUsername__. Whenever the user clicks the button to replace the name, it will notify the parent control and it will replace the content of _userName_ with the content from _editingUsername_. If _userName_ gets updated by any other third party (e.g. AJAX callback) it will update as well _editingUsername_. + +We take as a starting point sample _04 Callback_: + +## Summary steps: + +- Update _nameEdit.tsx_ in order to request the new _editingUsername_, and remove it from the state. +- Update _app.tsx_ to hold the new editing property in the state, pass it to the children, control and perform the proper update on the callback event from the child control. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _04 Callback_ and execute `npm install`. + +- Update _nameEdit.tsx_ in order to request the new _editingUsername_, and remove it from the state. + +_nameEdit.tsx_ + +```diff +import * as React from 'react'; + +interface Props { +- initialUserName: string; +- onNameUpdated: (newName: string) => any; ++ editingUserName : string; ++ onEditingNameUpdated : (newEditingName : string) => void; ++ onNameUpdateRequest : () => void; +} + +-interface State { +- editingName: string; +-} + +-export class NameEditComponent extends React.Component { ++ export class NameEditComponent extends React.Component { + constructor(props: Props) { + super(props); + +- this.state = {editingName: this.props.initialUserName} + } + +- onChange = (event) => { +- this.setState({editingName: event.target.value}); +- } + +- onNameSubmit = (event) => { +- this.props.onNameUpdated(this.state.editingName); +- } + ++ onChange = (e: React.ChangeEvent) => { ++ this.props.onEditingNameUpdated((e.target as HTMLInputElement).value); ++ } + + + public render() { + return ( +
+ +- +- ++ ++ +
+ ) + } +} +``` + +- Update _app.tsx_ to hold the new editing property in the state, pass it to the children controls and perform the proper update on the callback event from the child control. + +_./src/app.tsx_ + +```diff +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit' + +interface Props { + +} + +interface State { + userName : string; ++ editingUserName : string; +} + +export class App extends React.Component { + constructor(props : Props) { + super(props); + +- this.state = {userName: 'defaultUserName'}; ++ const defaultUserName = 'defaultUserName'; ++ this.state = {userName: defaultUserName, editingUserName: defaultUserName}; + } + +- setUsernameState = (newName: string) => { ++ setUsernameState = () => { +- this.setState({userName: newName}); ++ this.setState({userName: this.state.editingUserName}); + } + ++ updateEditingName = (editingName : string) : void => { ++ this.setState({editingUserName: editingName}); ++ } + + public render() { + return ( + <> + +- ++ + + ); + } +} +``` + +Finally we can check the example is working as in _04 Callback_ by executing from the command line `npm start` and opening [http://localhost:8080](http://localhost:8080). diff --git a/05_Refactor/readme_es.md b/05_Refactor/readme_es.md new file mode 100644 index 0000000..ec9b331 --- /dev/null +++ b/05_Refactor/readme_es.md @@ -0,0 +1,195 @@ +# 05 Refactorizar + +En el ejemplo anterior estabamos estableciendo un valor username inicial, ¿que ocurriría si esperásemos que este valor viniera, por ejemplo, de una petición AJAX o si pudiera variar en el tiempo? Lo que ocurriría es que la aproximación actual no funcionaría. + +Podríamos pensar en dos posibles soluciones: + +- La primera idea que podría venirnos a la mente sería implementar una mezcla: recibimos el valor actual de name via props, entonces mantenemos un estado con el valor editable actual... ¿Que desventajas nos encontraríamos? Tendríamos que escuchar el getDerivedStateFromProps (componentWillRecieveProps está obsoleto) para cualquier cambio en el control de nombre de usuario del padre y sustituir nuestro estado, acabaríamos con un control compartido. + +> Más información sobre getDerivedStateFromProps: https://medium.com/@baphemot/whats-new-in-react-16-3-d2c9b7b6193b + +Veamos como quedaría (usando el nuevo método estático getDerivedStateFromProps): + +Props e interfaz: + +_./src/nameEdit.tsx_ + +```diff +interface Props { + initialUserName: string; + onNameUpdated: (newName: string) => any; +} + +interface State { ++ initialUserName : string, + editingName: string; +} +``` +Actualización del constructor: + +```diff + constructor(props: Props) { + super(props); + // Comprueba que pasaría si obtenemos este nombre de usuario a través de un callback AJAX + // encontrarás una implementación diferente en el ejemplo 05 +- this.state = { initialUserName: this.props.initialUserName , +- editingName: this.props.initialUserName }; + ++ this.state = { initialUserName: this.props.initialUserName , ++ editingName: this.props.initialUserName ++ }; + } +``` +Dentro del componente de clase + +```javascript + static getDerivedStateFromProps(nextProps : Props, prevState : State) : Partial { + if(nextProps.initialUserName && + nextProps.initialUserName != prevState.initialUserName) { + return {editingName: nextProps.initialUserName} + } else { + return null; + } + } +``` + +- La segunda idea es preparar dos propiedades, el control padre contendrá _userName_ y _editingUsername__. Cuando el usuario hace click en el botón para sustituir el nombre se notifica al control padre y reemplazará el contenido de _userName_ con el contenido de _editingUsername_. Si _userName_ es actualizado por cualquier otra tercera parte (por ejemplo, un callback AJAX) también se actualizará _editingUsername_. + +Tomaremos como punto de partida el ejemplo _04 Callback_: + +## Pasos resumidos: + +- Actualizar _nameEdit.tsx_ para que solicite el nuevo _editingUsername_, y eliminarlo del estado. +- Actualizar _app.tsx_ para contener la nueva propiedad de edición en el estado, pasarla al hijo, controlar y realizar la actualización apropiada en el evento callback del control hijo. + +## Prerequisitos + +Instalar [Node.js y npm](https://nodejs.org/es/) si no lo tenemos ya instalado. + +> Verificar que estás ejecutando al menos con la versión 6.x.x de node y la versión 3.x.x de npm ejecutando `node -v` y `npm -v` en la ventana de terminal/consola. Versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copiar el contenido de _04 Callback_ y ejecutar `npm install`. + +- Actualizar _nameEdit.tsx_ para que solicite el nuevo _editingUsername_, y eliminarlo del estado. + +_nameEdit.tsx_ + +```diff +import * as React from 'react'; + +interface Props { +- initialUserName: string; +- onNameUpdated: (newName: string) => any; ++ editingUserName : string; ++ onEditingNameUpdated : (newEditingName : string) => void; ++ onNameUpdateRequest : () => void; +} + +-interface State { +- editingName: string; +-} + +-export class NameEditComponent extends React.Component { ++ export class NameEditComponent extends React.Component { + constructor(props: Props) { + super(props); + +- this.state = {editingName: this.props.initialUserName} + } + +- onChange = (event) => { +- this.setState({editingName: event.target.value}); +- } + +- onNameSubmit = (event) => { +- this.props.onNameUpdated(this.state.editingName); +- } + ++ onChange = (e: React.ChangeEvent) => { ++ this.props.onEditingNameUpdated((e.target as HTMLInputElement).value); ++ } + + + public render() { + return ( +
+ +- +- ++ ++ +
+ ) + } +} +``` + +- Actualizar _app.tsx_ para contener la nueva propiedad de edición en el estado, pasarla al hijo, controlar y realizar la actualización apropiada en el evento callback del control hijo. + +```diff +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit' + +interface Props { + +} + +interface State { + userName : string; ++ editingUserName : string; +} + +export class App extends React.Component { + constructor(props : Props) { + super(props); + +- this.state = {userName: 'defaultUserName'}; ++ const defaultUserName = 'defaultUserName'; ++ this.state = {userName: defaultUserName, editingUserName: defaultUserName}; + } + +- setUsernameState = (newName: string) => { ++ setUsernameState = () => { +- this.setState({userName: newName}); ++ this.setState({userName: this.state.editingUserName}); + } + ++ updateEditingName = (editingName : string) : void => { ++ this.setState({editingUserName: editingName}); ++ } + + public render() { + return ( + <> + +- ++ + + ); + } +} +``` + +Finalmente podemos comprobar que el ejemplo funciona como el _04 Callback_ ejecutando un `npm start` desde la consola de comandos y abriendo [http://localhost:8080](http://localhost:8080). diff --git a/05_Refactor/src/app.tsx b/05_Refactor/src/app.tsx new file mode 100644 index 0000000..7bad2c9 --- /dev/null +++ b/05_Refactor/src/app.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; + +interface Props { +} + +interface State { + userName: string; + editingUserName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + const defaultUserName = 'defaultUserName'; + this.state = { userName: defaultUserName, editingUserName: defaultUserName }; + } + + setUsernameState = () => { + this.setState({ userName: this.state.editingUserName }); + } + + updateEditingName = (editingName: string): void => { + this.setState({ editingUserName: editingName }); + } + + + public render() { + return ( + <> + + + + + ); + } +} \ No newline at end of file diff --git a/07 Enable/src/hello.tsx b/05_Refactor/src/hello.tsx similarity index 100% rename from 07 Enable/src/hello.tsx rename to 05_Refactor/src/hello.tsx diff --git a/05_Refactor/src/index.html b/05_Refactor/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/05_Refactor/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/05_Refactor/src/main.tsx b/05_Refactor/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/05_Refactor/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/05_Refactor/src/nameEdit.tsx b/05_Refactor/src/nameEdit.tsx new file mode 100644 index 0000000..306bf8d --- /dev/null +++ b/05_Refactor/src/nameEdit.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; + +interface Props { + editingUserName: string; + onEditingNameUpdated: (newEditingName: string) => void; + onNameUpdateRequest: () => void; +} + + +export class NameEditComponent extends React.Component { + + constructor(props: Props) { + super(props); + } + + onChange = (e: React.ChangeEvent) => { + this.props.onEditingNameUpdated((e.target as HTMLInputElement).value); + } + + + public render() { + return ( + <> + + + + + ); + } +} diff --git a/02 Properties/tsconfig.json b/05_Refactor/tsconfig.json similarity index 99% rename from 02 Properties/tsconfig.json rename to 05_Refactor/tsconfig.json index ba8b3b7..885d474 100644 --- a/02 Properties/tsconfig.json +++ b/05_Refactor/tsconfig.json @@ -14,4 +14,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +} diff --git a/05_Refactor/webpack.config.js b/05_Refactor/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/05_Refactor/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/06 MoveBackToStateless/.babelrc b/06 MoveBackToStateless/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/06 MoveBackToStateless/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/06 MoveBackToStateless/package.json b/06 MoveBackToStateless/package.json deleted file mode 100644 index 9929f76..0000000 --- a/06 MoveBackToStateless/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/06 MoveBackToStateless/readme.md b/06 MoveBackToStateless/readme.md deleted file mode 100644 index 7222b38..0000000 --- a/06 MoveBackToStateless/readme.md +++ /dev/null @@ -1,46 +0,0 @@ -# 06 MoveBackToStateless - -In example 05 we learned how to remove state from a child control just to have clear governance of state. - -It's time to make some cleanup, let's simplify _nameEdit_ component and move it as a stateless component. - -We will take a startup point sample _05 MoveBacktOStateless_. - -Summary steps: - -- Update _nameEdit.tsx_, port it to stateless component and add the methods inline. - - -## Prerequisites - -Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are not already installed on your computer. - -> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. - -## Steps to build it - -- Copy the content from _05 Refactor_ and execute `npm install`. - -- Update _nameEdit.tsx_, port it to stateless component and add the methods inline. It should look like: - - ```jsx -import * as React from 'react'; -import {Fragment} from 'react'; - - -interface Props { - editingUserName : string; - onEditingNameUpdated : (newEditingName : string) => void; - onNameUpdateRequest : () => void; -} - - -export const NameEditComponent = (props : Props) => -
- - props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> - - -
- ``` diff --git a/06 MoveBackToStateless/src/app.tsx b/06 MoveBackToStateless/src/app.tsx deleted file mode 100644 index a32e1a4..0000000 --- a/06 MoveBackToStateless/src/app.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import {HelloComponent} from './hello'; -import {NameEditComponent} from './nameEdit'; - -interface Props { - -} - -interface State { - userName : string; - editingUserName : string; -} - -export class App extends React.Component { - constructor(props: Props) { - super(props); - - const defaultUserName = "defaultUserName"; - this.state = {userName: defaultUserName, editingUserName: defaultUserName}; - } - - setUsernameState() { - this.setState({userName: this.state.editingUserName} as State); - } - - updateEditingName(editingName : string) { - this.setState({editingUserName: editingName} as State); - } - - public render() { - return ( -
- - -
- ); - } -} diff --git a/06 MoveBackToStateless/src/index.html b/06 MoveBackToStateless/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/06 MoveBackToStateless/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/06 MoveBackToStateless/src/main.tsx b/06 MoveBackToStateless/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/06 MoveBackToStateless/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/06 MoveBackToStateless/src/nameEdit.tsx b/06 MoveBackToStateless/src/nameEdit.tsx deleted file mode 100644 index e698e63..0000000 --- a/06 MoveBackToStateless/src/nameEdit.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; - -interface Props { - editingUserName : string; - onEditingNameUpdated : (newEditingName : string) => any; - onNameUpdateRequest : () => void; -} - -export const NameEditComponent = (props: Props) => { - return ( -
- - props.onEditingNameUpdated(event.target.value)}/> - -
- ); -} diff --git a/06 MoveBackToStateless/tsconfig.json b/06 MoveBackToStateless/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/06 MoveBackToStateless/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/06 MoveBackToStateless/webpack.config.js b/06 MoveBackToStateless/webpack.config.js deleted file mode 100644 index 5ab56ca..0000000 --- a/06 MoveBackToStateless/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options:{ - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/06_MoveBackToStateless/.babelrc b/06_MoveBackToStateless/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/06_MoveBackToStateless/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/06_MoveBackToStateless/package.json b/06_MoveBackToStateless/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/06_MoveBackToStateless/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/06_MoveBackToStateless/readme.md b/06_MoveBackToStateless/readme.md new file mode 100644 index 0000000..28a4335 --- /dev/null +++ b/06_MoveBackToStateless/readme.md @@ -0,0 +1,53 @@ +# 06 MoveBackToStateless + +In example 05 we learned how to remove state from a child control just to have clear governance of state. + +It's time to make some cleanup, let's simplify _[nameEdit.tsx](./src/nameEdit.tsx)_ component and move it as a stateless component. + +Let's take example _[05 Refactor](./../05%20Refactor)_ as reference. + +## Summary steps: + +- Update _[nameEdit.tsx](./src/nameEdit.tsx)_, transform it to a stateless component and inline some methods. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _[05 Refactor](./../05%20Refactor)_ and execute `npm install`. + +- Update _[nameEdit.tsx](./src/nameEdit.tsx)_, transform it to stateless component and inline some methods. It should look like this: + + ```jsx +import * as React from 'react'; + +interface Props { + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
+ ``` +Side note: when refactoring this code, we have replaced ```this.props``` by ```props```. This is because ```NameEditComponent``` is now a function, not a class. If you keep ```this.props```, it fails in runtime because ```this``` is now undefined. + +- Now we can run the example, and we get the same results. + +```bash +npm start +``` diff --git a/06_MoveBackToStateless/readme_es.md b/06_MoveBackToStateless/readme_es.md new file mode 100644 index 0000000..410ffc3 --- /dev/null +++ b/06_MoveBackToStateless/readme_es.md @@ -0,0 +1,54 @@ +# 06 MoveBackToStateless + +En el ejemplo 05 aprendimos como eliminar el estado de un control secundario para tener una gestión limpia del estado. + +Es hora de hacer limpieza, simplificando el componente _[nameEdit.tsx](./src/nameEdit.tsx)_ convirtiéndolo en un componente sin estado. + +Tomaremos como punto de partida el ejemplo _[05 Refactor](./../05%20Refactor)_. + +## Pasos resumidos: + +- Actualizar _[nameEdit.tsx](./src/nameEdit.tsx)_, convirtiéndolo en un componente sin estado y añadir los métodos inline. + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si aún no los tienes instalados en tu equipo. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en un terminal/consola. Versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _[05 Refactor](./../05%20Refactor)_ y ejecuta `npm install`. + +- Actualiza _[nameEdit.tsx](./src/nameEdit.tsx)_, convirtiendo en un componente sin estados y añadiendo los métodos inline. Debería verse como: + + ```jsx +import * as React from 'react'; + +interface Props { + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
+ ``` + + Nota aclarativa: cuando refactorizamos este código, nosotros hemos remplazado ```this.props``` por ```props```. Esto es porque ```NameEditComponent``` es ahora una función, no una clase. Si tu guardas ```this.props```, falla en tiempo de ejecución porqué ```this``` es ahora undefined + +- Ahora podemos arrancar el ejemplo y obtendremos los mismos resultados. + +```bash +npm start +``` \ No newline at end of file diff --git a/06_MoveBackToStateless/src/app.tsx b/06_MoveBackToStateless/src/app.tsx new file mode 100644 index 0000000..7bad2c9 --- /dev/null +++ b/06_MoveBackToStateless/src/app.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; + +interface Props { +} + +interface State { + userName: string; + editingUserName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + const defaultUserName = 'defaultUserName'; + this.state = { userName: defaultUserName, editingUserName: defaultUserName }; + } + + setUsernameState = () => { + this.setState({ userName: this.state.editingUserName }); + } + + updateEditingName = (editingName: string): void => { + this.setState({ editingUserName: editingName }); + } + + + public render() { + return ( + <> + + + + + ); + } +} \ No newline at end of file diff --git a/10 Sidebar/src/hello.tsx b/06_MoveBackToStateless/src/hello.tsx similarity index 100% rename from 10 Sidebar/src/hello.tsx rename to 06_MoveBackToStateless/src/hello.tsx diff --git a/06_MoveBackToStateless/src/index.html b/06_MoveBackToStateless/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/06_MoveBackToStateless/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/06_MoveBackToStateless/src/main.tsx b/06_MoveBackToStateless/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/06_MoveBackToStateless/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/06_MoveBackToStateless/src/nameEdit.tsx b/06_MoveBackToStateless/src/nameEdit.tsx new file mode 100644 index 0000000..1ee5fd4 --- /dev/null +++ b/06_MoveBackToStateless/src/nameEdit.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import {Fragment} from 'react'; + + +interface Props { + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
diff --git a/03 State/tsconfig.json b/06_MoveBackToStateless/tsconfig.json similarity index 99% rename from 03 State/tsconfig.json rename to 06_MoveBackToStateless/tsconfig.json index ba8b3b7..885d474 100644 --- a/03 State/tsconfig.json +++ b/06_MoveBackToStateless/tsconfig.json @@ -14,4 +14,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +} diff --git a/06_MoveBackToStateless/webpack.config.js b/06_MoveBackToStateless/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/06_MoveBackToStateless/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/07 Enable/.babelrc b/07 Enable/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/07 Enable/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/07 Enable/package.json b/07 Enable/package.json deleted file mode 100644 index 9929f76..0000000 --- a/07 Enable/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/07 Enable/readme.md b/07 Enable/readme.md deleted file mode 100644 index 78ff7c9..0000000 --- a/07 Enable/readme.md +++ /dev/null @@ -1,96 +0,0 @@ -# 07 Enable - - -Let's continue with the update name sample, this time we want to disable the -"update" button when the input is empty or when the value hasn't changed. - -We will take a startup point sample _06 MoveBacktOStateless_. - -Summary steps: - -- Add a condition to disable - - -## Prerequisites - -Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are not already installed on your computer. - -> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. - -## Steps to build it - -- Copy the content from _06 MoveBacktoStateless_. - -- Let's start by adding a condition to disable the field whenever is empty. Replace only the input tag in _src/nameEdit.tsx_ with the following code: - -_./src/nameEditComponent.tsx_ - -```diff -
- - props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> - -- -+ -
-``` - - -- Now comes the tricky part, detect when the name hasn't changed.
-First we will add a new property called _userName_ with type `string` in _src/nameEdit.tsx_. This one will hold the last accepted userName. - -_./src/nameEdit.tsx_ - - ```diff - interface Props { -+ userName : string; - editingUserName : string; - onEditingNameUpdated : (newEditingName : string) => any; - onNameUpdateRequest : () => void; - } - ``` - -- We will add to the enable condition one more test, checking if name has changed. -Replace again only the input tag in _src/nameEdit.tsx_ with the following code: - -```diff - -``` - -- Now we have to feed this property from the parent control (Add `userName={this.state.userName}` to the NameEditComponent in _src/app.tsx_). The `NameEditComponent` should be like: - -_./src/app.tsx_ - -```diff - public render() { - return ( - - - - - ); - } -``` - -- Let's give a try - - ``` -npm start - ``` - - > As an excercise, what if we want to do this more generic? we could have a generic property - called enable that could be true or false. diff --git a/07 Enable/src/app.tsx b/07 Enable/src/app.tsx deleted file mode 100644 index 8cae0ef..0000000 --- a/07 Enable/src/app.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import * as React from 'react'; -import {HelloComponent} from './hello'; -import {NameEditComponent} from './nameEdit' - -interface Props { - -} - -interface State { - userName : string; - editingUserName : string; -} - -export class App extends React.Component { - constructor(props : Props) { - super(props); - - const defaultUserName = 'defaultUserName'; - this.state = {userName: defaultUserName, editingUserName: defaultUserName}; - } - - setUsernameState = () => { - this.setState({userName: this.state.editingUserName} as State); - } - - updateEditingName = (editingName : string) : void => { - this.setState({editingUserName: editingName} as State); - } - - - public render() { - return ( - - - - - ); - } -} - diff --git a/07 Enable/src/index.html b/07 Enable/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/07 Enable/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/07 Enable/src/main.tsx b/07 Enable/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/07 Enable/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/07 Enable/src/nameEdit.tsx b/07 Enable/src/nameEdit.tsx deleted file mode 100644 index 98c06ae..0000000 --- a/07 Enable/src/nameEdit.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; -import {Fragment} from 'react'; - - -interface Props { - userName : string; - editingUserName : string; - onEditingNameUpdated : (newEditingName : string) => void; - onNameUpdateRequest : () => void; -} - - -export const NameEditComponent = (props : Props) => -
- - props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> - - -
- - diff --git a/07 Enable/tsconfig.json b/07 Enable/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/07 Enable/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/07 Enable/webpack.config.js b/07 Enable/webpack.config.js deleted file mode 100644 index 5ab56ca..0000000 --- a/07 Enable/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options:{ - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/07_Enable/.babelrc b/07_Enable/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/07_Enable/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/07_Enable/package.json b/07_Enable/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/07_Enable/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/07_Enable/readme.md b/07_Enable/readme.md new file mode 100644 index 0000000..8d911c1 --- /dev/null +++ b/07_Enable/readme.md @@ -0,0 +1,153 @@ +# 07 Enable + +Let's continue with the update name sample, this time we want to disable the +"update" button when the input is empty or when the value hasn't changed. + +We will take a startup point sample _[06 MoveBackToStateless/](./../06%20MoveBackToStateless/)_. + +## Summary steps: + +- Add a condition to disable + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _[06 MoveBackToStateless/](./../06%20MoveBackToStateless/)_. + +- Let's start by adding a condition to disable the field whenever is empty. Replace only the input tag in _[./src/nameEdit.tsx](./src/nameEdit.tsx)_ with the following code: + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ +```diff +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + +- ++ +
+``` + +- Now comes the tricky part, detect when the name hasn't changed.
+First we will add a new property called _userName_ with type `string` in _[./src/nameEdit.tsx](./src/nameEdit.tsx)_. This one will hold the last accepted userName. + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ + +```diff + interface Props { ++ userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => any; + onNameUpdateRequest : () => void; + } + ``` + +- We will add to the enable condition one more test, checking if name has changed. +Replace again only the input tag in _[./src/nameEdit.tsx](./src/nameEdit.tsx)_ with the following code: + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ +```diff + +``` + +- Now we have to feed this property from the parent control (Add `userName={this.state.userName}` to the NameEditComponent in _[./src/app.tsx](./src/app.tsx)_). The `NameEditComponent` should be like: + +_[./src/app.tsx](./src/app.tsx)_ +```diff + public render() { + return ( + <> + + + + ); + } +``` + +- Let's give a try + +``` +npm start +``` + +> As an excercise, what if we want to do this more generic? we could have a generic property called enable that could be true or false. + +To do this, we will modify [./src/app.tsx](./src/app.tsx) adding the variable `disable` to the `` component. This variable is **Boolean**, so you need conditions to evaluate it. + +_[./src/app.tsx](./src/app.tsx)_ +```diff + public render() { + return ( + <> + + + + ); + } +``` + +Within the component we define the **props** **disable** as Boolean, together with its conditions that will evaluate it. + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ +```diff +interface Props { +++ disable: boolean; + userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
+``` diff --git a/07_Enable/readme_es.md b/07_Enable/readme_es.md new file mode 100644 index 0000000..ab43f4a --- /dev/null +++ b/07_Enable/readme_es.md @@ -0,0 +1,153 @@ +# 07 Habilitar + +Continuemos con el ejemplo de nombre de actualización, esta vez queremos desactivar el +botón "actualizar" cuando la entrada está vacía o cuando el valor no ha cambiado. + +Tomaremos una muestra del punto de inicio _[06 MoveBackToStateless/](./../06%20MoveBackToStateless/)_. + +## Pasos resumidos: + +- Agregar una condición para deshabilitar + +## Prerrequisitos + +Instala [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o más reciente) si aún no está instalados en tu maquina. + +> Verifica que está ejecutando al menos node v6.x.x y npm 3.x.x ejecutando `node -v` y` npm -v` en una ventana de terminal/ consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _[06 MoveBackToStateless/](./../06%20MoveBackToStateless/)_. + +- Comencemos agregando una condición para deshabilitar el campo siempre que esté vacío. Reemplaza solo la etiqueta de entrada en _[./src/nameEdit.tsx](./src/nameEdit.tsx)_ con el siguiente código: + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ +```diff +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + +- ++ +
+``` + +- Ahora viene la parte difícil, detectar cuando el nombre no ha cambiado.
+Primero agregaremos una nueva propiedad llamada _userName_ con el tipo `string` en _[./src/nameEdit.tsx](./src/nameEdit.tsx)_. Este tendrá el último nombre de usuario aceptado. + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ + +```diff + interface Props { ++ userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => any; + onNameUpdateRequest : () => void; + } + ``` + +- Añadiremos a la condición de habilitación una prueba más, verificando si el nombre ha cambiado. +Reemplace nuevamente solo la etiqueta de entrada en _[./src/nameEdit.tsx](./src/nameEdit.tsx)_ con el siguiente código: + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ +```diff + +``` + +- Ahora tenemos que alimentar esta propiedad desde el control principal (Añade `userName={this.state.userName}` al NameEditComponent en _[./src/app.tsx](./src/app.tsx)_). El `NameEditComponent` debería ser como: + +_[./src/app.tsx](./src/app.tsx)_ +```diff + public render() { + return ( + <> + + + + ); + } +``` + +- Démosle una oportunidad + +``` +npm start +``` + +> Como ejercicio, ¿y si queremos hacer esto más genérico? podríamos tener una propiedad genérica llamada enable que podría ser verdadera o falsa. + +Para hacer esto, modificaremos [./src/app.tsx](./src/app.tsx) agregando la variable `disable` al componente` `. Esta variable es **Booleana**, por lo que necesita condiciones para evaluarla. + +_[./src/app.tsx](./src/app.tsx)_ +```diff + public render() { + return ( + <> + + + + ); + } +``` + +Dentro del componente definimos **props** **disable** como Boolean, junto con sus condiciones que lo evaluarán. + +_[./src/nameEdit.tsx](./src/nameEdit.tsx)_ +```diff +interface Props { +++ disable: boolean; + userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
+``` \ No newline at end of file diff --git a/07_Enable/src/app.tsx b/07_Enable/src/app.tsx new file mode 100644 index 0000000..942af8c --- /dev/null +++ b/07_Enable/src/app.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; + +interface Props { +} + +interface State { + userName: string; + editingUserName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + const defaultUserName = 'defaultUserName'; + this.state = { userName: defaultUserName, editingUserName: defaultUserName }; + } + + setUsernameState = () => { + this.setState({ userName: this.state.editingUserName }); + } + + updateEditingName = (editingName: string): void => { + this.setState({ editingUserName: editingName }); + } + + + public render() { + return ( + <> + + + + ); + } +} \ No newline at end of file diff --git a/04 Callback/src/hello.tsx b/07_Enable/src/hello.tsx similarity index 50% rename from 04 Callback/src/hello.tsx rename to 07_Enable/src/hello.tsx index 1a66de7..5636921 100644 --- a/04 Callback/src/hello.tsx +++ b/07_Enable/src/hello.tsx @@ -1,11 +1,7 @@ import * as React from 'react'; -interface Props { - userName: string; -} - -export const HelloComponent = (props: Props) => { +export const HelloComponent = (props: {userName : string}) => { return (

Hello user: {props.userName} !

); -}; +} diff --git a/07_Enable/src/index.html b/07_Enable/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/07_Enable/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/07_Enable/src/main.tsx b/07_Enable/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/07_Enable/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/07_Enable/src/nameEdit.tsx b/07_Enable/src/nameEdit.tsx new file mode 100644 index 0000000..f7f59fb --- /dev/null +++ b/07_Enable/src/nameEdit.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import {Fragment} from 'react'; + + +interface Props { + userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
diff --git a/07_Enable/tsconfig.json b/07_Enable/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/07_Enable/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/07_Enable/webpack.config.js b/07_Enable/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/07_Enable/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/08 Colorpicker/.babelrc b/08 Colorpicker/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/08 Colorpicker/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/08 Colorpicker/package.json b/08 Colorpicker/package.json deleted file mode 100644 index 9929f76..0000000 --- a/08 Colorpicker/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/08 Colorpicker/src/colorpicker.tsx b/08 Colorpicker/src/colorpicker.tsx deleted file mode 100644 index c8c9b59..0000000 --- a/08 Colorpicker/src/colorpicker.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from 'react'; -import {Color} from './color' - -interface Props { - color : Color; - onColorUpdated : (color : Color) => void; -} - -export const ColorPicker = (props : Props) => { - return ( -
- props.onColorUpdated( - {red: event.target.value, green: props.color.green, blue: props.color.blue} - )} - /> - {props.color.red} -
- props.onColorUpdated( - { - red: props.color.red, - green: event.target.value, - blue: props.color.blue - } - )} - /> - {props.color.green} -
- props.onColorUpdated( - { - red: props.color.red, - green: props.color.green, - blue: event.target.value - } - )} - /> - {props.color.blue} -
-
- ); -} diff --git a/08 Colorpicker/src/index.html b/08 Colorpicker/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/08 Colorpicker/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/08 Colorpicker/tsconfig.json b/08 Colorpicker/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/08 Colorpicker/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/08 Colorpicker/webpack.config.js b/08 Colorpicker/webpack.config.js deleted file mode 100644 index 5ab56ca..0000000 --- a/08 Colorpicker/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options:{ - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/08_Colorpicker/.babelrc b/08_Colorpicker/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/08_Colorpicker/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/08_Colorpicker/package.json b/08_Colorpicker/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/08_Colorpicker/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/08 Colorpicker/readme.md b/08_Colorpicker/readme.md similarity index 67% rename from 08 Colorpicker/readme.md rename to 08_Colorpicker/readme.md index 57c2c5c..b7ff652 100644 --- a/08 Colorpicker/readme.md +++ b/08_Colorpicker/readme.md @@ -1,15 +1,13 @@ # 08 Colorpicker +We take _01 HelloReact_ as reference. +> This example is based on the following [egghead jsbin](https://jsbin.com/qiwoxax/4/edit?html,js,output), but adding some variations. -We will take a startup point sample _01 HelloReact_: - ->This sample is based on the following [egghead jsbin](https://jsbin.com/qiwoxax/4/edit?html,js,output), but adding some variations. - -Summary steps: +## Summary steps: - Rename _hello.tsx_ file to _colorpicker.tsx_. -- Define the properties and state. +- Define properties and state. - Create the UI. @@ -25,6 +23,8 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are - Let's define a proper color structure (create a _color.ts_ file). +_./src/color.ts_ + ```javascript export interface Color { red : number; @@ -47,14 +47,14 @@ export const ColorPicker = () => { } ``` -- Let's create an indermediate _app.tsx_ file like we did in some previous samples: +- Let's create an indermediate _app.tsx_ file as we did in some of the previous examples: _./src/app.tsx_ ```jsx import * as React from 'react'; -import {Color} from './color'; -import {ColorPicker} from './colorpicker'; +import { Color } from './color'; +import { ColorPicker } from './colorpicker'; interface State { color : Color; @@ -81,7 +81,7 @@ export class App extends React.Component<{}, State> { } ``` -- We need to update _main.tsx_ to indicate the change +- We need to update _main.tsx_ to adjust it to the change: _./src/main.tsx_ @@ -89,22 +89,21 @@ _./src/main.tsx_ import * as React from 'react'; import * as ReactDOM from 'react-dom'; - import { HelloComponent } from './hello'; -+ import {App} from './app'; ++ import { App } from './app'; ReactDOM.render( -- -+ , +- ++ , document.getElementById('root')); ``` -- We are going to change as well the content of the file let's define a color and callback -as a property to setup the color (_colorpicker.tsx_). +- We are going to change as well the content of the file. Let's define a color and a callback (as a property) to set the color (_colorpicker.tsx_). _./src/colorpicker.tsx_ ```diff import * as React from 'react'; -+ import {Color} from './color' ++ import { Color } from './color' + interface Props { + color : Color; @@ -120,6 +119,8 @@ export const ColorPicker = () => { - Let's start by defining only one slider to control the red component of a given color (_colorpicker.tsx_). +_./src/colorpicker.tsx_ + ```diff - export const ColorPicker = () => { + export const ColorPicker = (props : Props) => { @@ -131,7 +132,8 @@ export const ColorPicker = () => { + max="255" + value={props.color.red} + onChange={(event) => props.onColorUpdated( -+ {red: +event.target.value, green: props.color.green, blue: props.color.blue} ++ {red: +event.target.value, green: ++ props.color.green, blue: props.color.blue} + )} + /> + {props.color.red} @@ -140,12 +142,14 @@ export const ColorPicker = () => { } ``` -- Now it's time to update _app.tsx_ to interact with the components props. +- Now it's time to update _app.tsx_ to interact with the component's props. + +_./src/app.tsx_ ```diff import * as React from 'react'; - import {Color} from './color'; - import {ColorPicker} from './colorpicker'; + import { Color } from './color'; + import { ColorPicker } from './colorpicker'; interface State { color : Color; @@ -165,9 +169,18 @@ export const ColorPicker = () => { public render() { return (
-+ Color: [red: {this.state.color.red}, green: {this.state.color.green}, blue: {this.state.color.blue}] ++ ++ Color: [ ++ red: {this.state.color.red}, ++ green: {this.state.color.green}, ++ blue: {this.state.color.blue} ++ ] ++ - -+ ++
); } @@ -175,7 +188,7 @@ export const ColorPicker = () => { ``` -- Let's give a try and check that we got the basics working +- Let's give a try and check that we got the basics working. ``` npm start @@ -183,7 +196,9 @@ export const ColorPicker = () => { - Let's complete the component by adding sliders for the green and blue options: -> Note: this will look a bit ugly, in the next sample we will refactor this to a cleaner solution +> Note: this will look a bit ugly, in the next example we will refactor this to a cleaner solution. + +_./src/colopicker.tsx_ ```diff export const ColorPicker = (props : Props) => { @@ -210,7 +225,7 @@ export const ColorPicker = () => { + onChange={(event : any) => props.onColorUpdated( + { + red: props.color.red, -+ green: event.target.value, ++ green: +event.target.value, + blue: props.color.blue + } + )} @@ -225,7 +240,7 @@ export const ColorPicker = () => { + { + red: props.color.red, + green: props.color.green, -+ blue: event.target.value ++ blue: +event.target.value + } + )} + /> @@ -236,19 +251,20 @@ export const ColorPicker = () => { } ``` -- Let's make this a bit more visual, it would be a good idea to display a rectangle -filled with the selected color. Let's create a ColorDisplayer component (_colordisplayer.tsx_). +- Let's make this a bit more visual. It would be a good idea to display a rectangle filled with the selected color. Let's create a ColorDisplayer component (_colordisplayer.tsx_). + +_./src/colordisplayer.tsx_ ```jsx import * as React from 'react'; - import {Color} from './color' + import { Color } from './color' interface Props { color : Color; } export const ColorDisplayer = (props : Props) => { - const divStyle = { + const divStyle : React.CSSProperties = { // React.CSSProperties gives editing-time visual feedback on the CSS you are typing. width: '11rem', height: '7rem', backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})` @@ -265,9 +281,9 @@ filled with the selected color. Let's create a ColorDisplayer component (_colord ```diff import * as React from 'react'; -import {Color} from './color'; -import {ColorPicker} from './colorpicker'; -+ import {ColorDisplayer} from './colordisplayer'; +import { Color } from './color'; +import { ColorPicker } from './colorpicker'; ++ import { ColorDisplayer } from './colordisplayer'; interface State { color : Color; @@ -287,16 +303,20 @@ export class App extends React.Component<{}, State> { public render() { return (
-+ - Color: [red: {this.state.color.red}, green: {this.state.color.green}, blue: {this.state.color.blue}] - ++ + + Color: [red: {this.state.color.red}, green: {this.state.color.green}, blue: {this.state.color.blue}] + +
); } } ``` -- Let's give a try and check the results +- Let's give a try and check the results. ``` npm start diff --git a/08_Colorpicker/readme_es.md b/08_Colorpicker/readme_es.md new file mode 100644 index 0000000..76c3cae --- /dev/null +++ b/08_Colorpicker/readme_es.md @@ -0,0 +1,323 @@ +# 08 Colorpicker + +Tomaremos como punto de partida el ejemplo _01 HelloReact_: + +> Este ejemplo está basado en el siguiente: [egghead jsbin](https://jsbin.com/qiwoxax/4/edit?html,js,output), pero añade algunas variaciones + +## Pasos resumidos: + +- Renombrar el archivo _hello.tsx_ a _colorpicker.tsx_. +- Definir las propiedades y el estado. +- Crear la UI. + + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 ó más reciente) si aún no está instalada en la máquina. + +> Verificar que tienes node al menos v6.x.x y npm 3.x.x ejecutando `node -v` y `npm -v` en un terminal ó console de windows. Versiones viejas pueden provocar errores. + +## Pasos a seguir. + +- Copiar el contenido de _01 HelloReact_ y ejecutar `npm install`. + +- Vamos a definir una estructura apropiada (crea un archivo _color.ts_). + +_./src/color.ts_ + +```javascript +export interface Color { + red : number; + green : number; + blue : number; +} +``` + +- Renombramos _hello.tsx_ a _colorpicker.tsx_. + +- Renombramos también el nombre del componente. + +```jsx +import * as React from 'react'; + +export const ColorPicker = () => { + return ( +

Hello component !

+ ); +} +``` + +- Creamos un archivo intermedio _app.tsx_ como hicimos en ejemplos previos: + +_./src/app.tsx_ + +```jsx +import * as React from 'react'; +import { Color } from './color'; +import { ColorPicker } from './colorpicker'; + +interface State { + color : Color; +} + +export class App extends React.Component<{}, State> { + constructor(props) { + super(props); + + this.state = {color: {red: 90, green: 50, blue: 70}}; + } + + setColorState = (newColor : Color) => { + this.setState({color: newColor}); + } + + public render() { + return ( +
+ +
+ ); + } +} +``` + +- Necesitamos actualizar _main.tsx_ para indicar los cambios + +_./src/main.tsx_ + +```diff + import * as React from 'react'; + import * as ReactDOM from 'react-dom'; +- import { HelloComponent } from './hello'; ++ import { App } from './app'; + + ReactDOM.render( +- ++ , + document.getElementById('root')); +``` + +- Vamos a cambiar también el contenido del fichero, definimos un color y un callback como propiedad para establecer el color (_colorpicker.tsx_). + +_./src/colorpicker.tsx_ + +```diff +import * as React from 'react'; ++ import { Color } from './color' + ++ interface Props { ++ color : Color; ++ onColorUpdated : (color : Color) => void; ++ } + +export const ColorPicker = () => { + return ( +

Hello component !

+ ); +} +``` + +- Vamos a comenzar solo definiendo un control para el componente rojo de un color dado. (_colorpicker.tsx_). + +_./src/colorpicker.tsx_ + +```diff +- export const ColorPicker = () => { ++ export const ColorPicker = (props : Props) => { + return ( +-

Hello component !

++
++ props.onColorUpdated( ++ {red: +event.target.value, ++ green: props.color.green, blue: props.color.blue} ++ )} ++ /> ++ {props.color.red} ++
+ ); + } +``` + +- Ahora actualizamos el archivo _app.tsx_ para interactuar con las propiedades del componente. + +_./src/app.tsx_ + +```diff + import * as React from 'react'; + import { Color } from './color'; + import { ColorPicker } from './colorpicker'; + + interface State { + color : Color; + } + + export class App extends React.Component<{}, State> { + constructor(props) { + super(props); + + this.state = {color: {red: 90, green: 50, blue: 70}}; + } + ++ setColorState = (newColor : Color) => { ++ this.setState({color: newColor}); ++ } + + public render() { + return ( +
++ ++ Color: [ ++ red: {this.state.color.red}, ++ green: {this.state.color.green}, ++ blue: {this.state.color.blue} ++ ] ++ +- ++ +
+ ); + } + } + +``` + +- Ahora probamos que tenemos el funcionamiento básico correcto. + +``` + npm start +``` + +- Completamos el componente añadiendo sliders para las opciones de color verde y azul: + +> Nota: esto parecerá un poco feo, en el siguiente ejemplo vamos a refactorizar y lo haremos una solución más limpia. + +_./src/colopicker.tsx_ + +```diff + export const ColorPicker = (props : Props) => { + return ( +
+ props.onColorUpdated( + { + red: event.target.value, + green: props.color.green, + blue: props.color.blue + } + )} + /> + {props.color.red} ++
++ props.onColorUpdated( ++ { ++ red: props.color.red, ++ green: +event.target.value, ++ blue: props.color.blue ++ } ++ )} ++ /> ++ {props.color.green} ++
++ props.onColorUpdated( ++ { ++ red: props.color.red, ++ green: props.color.green, ++ blue: +event.target.value ++ } ++ )} ++ /> ++ {props.color.blue} ++
+
+ ); + } +``` + +- Haremos esto un poco más atractivo visualmente, sería una buena idea mostrar un rectángulo relleno con el color seleccionado. Crearemos un componente ColorDisplayer (_colordisplayer.tsx_). + +_./src/colordisplayer.tsx_ + +```jsx + import * as React from 'react'; + import { Color } from './color' + + interface Props { + color : Color; + } + + export const ColorDisplayer = (props : Props) => { + const divStyle : React.CSSProperties = { // React.CSSProperties gives editing-time visual feedback on the CSS you are typing. + width: '11rem', + height: '7rem', + backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})` + }; + + return ( +
+
+ ); + } +``` + +- Ahora vamos a usarlo dentro de nuestro componente App (_app.tsx_). + +```diff +import * as React from 'react'; +import {Color} from './color'; +import { ColorPicker } from './colorpicker'; ++ import { ColorDisplayer } from './colordisplayer'; + +interface State { + color : Color; +} + +export class App extends React.Component<{}, State> { + constructor(props) { + super(props); + + this.state = {color: {red: 90, green: 50, blue: 70}}; + } + + setColorState(newColor : Color) { + this.setState({color: newColor}); + } + + public render() { + return ( +
++ + + Color: [red: {this.state.color.red}, green: {this.state.color.green}, blue: {this.state.color.blue}] + + +
+ ); + } +} +``` + +- Ahora a probarlo y ver los resultados!!! + + ``` +npm start +``` diff --git a/08 Colorpicker/src/app.tsx b/08_Colorpicker/src/app.tsx similarity index 95% rename from 08 Colorpicker/src/app.tsx rename to 08_Colorpicker/src/app.tsx index eb60c3f..ab7fde2 100644 --- a/08 Colorpicker/src/app.tsx +++ b/08_Colorpicker/src/app.tsx @@ -23,8 +23,8 @@ export class App extends React.Component<{}, State> {
Color: [red: {this.state.color.red}, green: {this.state.color.green}, blue: {this.state.color.blue}] - +
); } -} +} \ No newline at end of file diff --git a/08 Colorpicker/src/color.ts b/08_Colorpicker/src/color.ts similarity index 97% rename from 08 Colorpicker/src/color.ts rename to 08_Colorpicker/src/color.ts index 38e925c..a9e0aab 100644 --- a/08 Colorpicker/src/color.ts +++ b/08_Colorpicker/src/color.ts @@ -2,4 +2,4 @@ export interface Color { red : number; green : number; blue : number; -} +} \ No newline at end of file diff --git a/08 Colorpicker/src/colordisplayer.tsx b/08_Colorpicker/src/colordisplayer.tsx similarity index 72% rename from 08 Colorpicker/src/colordisplayer.tsx rename to 08_Colorpicker/src/colordisplayer.tsx index 4421291..1a4cb1c 100644 --- a/08 Colorpicker/src/colordisplayer.tsx +++ b/08_Colorpicker/src/colordisplayer.tsx @@ -6,12 +6,14 @@ interface Props { } export const ColorDisplayer = (props : Props) => { - var divStyle = { + + const divStyle : React.CSSProperties = { // React.CSSProperties gives editing-time visual feedback on the CSS you are typing. width: '11rem', height: '7rem', backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})` }; + return (
diff --git a/08_Colorpicker/src/colorpicker.tsx b/08_Colorpicker/src/colorpicker.tsx new file mode 100644 index 0000000..aa2e0e1 --- /dev/null +++ b/08_Colorpicker/src/colorpicker.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { Color } from './color' + +interface Props { + color: Color; + onColorUpdated: (color: Color) => void; +} + +export const ColorPicker = (props: Props) => { + return ( +
+ props.onColorUpdated( + { red: +event.target.value, green: props.color.green, blue: props.color.blue } + )} + /> + {props.color.red} +
+ props.onColorUpdated( + { + red: props.color.red, + green: event.target.value, + blue: props.color.blue + } + )} + /> + {props.color.green} +
+ props.onColorUpdated( + { + red: props.color.red, + green: props.color.green, + blue: event.target.value + } + )} + /> + {props.color.blue} +
+
+ ); +} \ No newline at end of file diff --git a/08_Colorpicker/src/index.html b/08_Colorpicker/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/08_Colorpicker/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/08 Colorpicker/src/main.tsx b/08_Colorpicker/src/main.tsx similarity index 93% rename from 08 Colorpicker/src/main.tsx rename to 08_Colorpicker/src/main.tsx index d887a92..d75c95c 100644 --- a/08 Colorpicker/src/main.tsx +++ b/08_Colorpicker/src/main.tsx @@ -4,6 +4,6 @@ import {App} from './app'; ReactDOM.render( - , + , document.getElementById('root') ); diff --git a/08_Colorpicker/src/nameEdit.tsx b/08_Colorpicker/src/nameEdit.tsx new file mode 100644 index 0000000..f7f59fb --- /dev/null +++ b/08_Colorpicker/src/nameEdit.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import {Fragment} from 'react'; + + +interface Props { + userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
diff --git a/08_Colorpicker/tsconfig.json b/08_Colorpicker/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/08_Colorpicker/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/08_Colorpicker/webpack.config.js b/08_Colorpicker/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/08_Colorpicker/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/09 ColorpRefactor/.babelrc b/09 ColorpRefactor/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/09 ColorpRefactor/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/09 ColorpRefactor/package.json b/09 ColorpRefactor/package.json deleted file mode 100644 index 9929f76..0000000 --- a/09 ColorpRefactor/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/09 ColorpRefactor/src/colorpicker.tsx b/09 ColorpRefactor/src/colorpicker.tsx deleted file mode 100644 index c681c82..0000000 --- a/09 ColorpRefactor/src/colorpicker.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { Color } from './color'; -import { ColorSliderComponent } from './colorslider'; - -interface Props { - color: Color; - onColorUpdated: (color: Color) => void; -} - -const updateColor = (props: Props, colorId : keyof Color) => (value) => { - props.onColorUpdated({ - ...props.color, - [colorId]: value - }); -}; - - -export const ColorPicker = (props: Props) => { - return ( -
- - {props.color.red} -
- -
- -
-
- ); -} diff --git a/09 ColorpRefactor/src/index.html b/09 ColorpRefactor/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/09 ColorpRefactor/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/09 ColorpRefactor/src/main.tsx b/09 ColorpRefactor/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/09 ColorpRefactor/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/09 ColorpRefactor/tsconfig.json b/09 ColorpRefactor/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/09 ColorpRefactor/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/09 ColorpRefactor/webpack.config.js b/09 ColorpRefactor/webpack.config.js deleted file mode 100644 index 5ab56ca..0000000 --- a/09 ColorpRefactor/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options:{ - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/09_ColorpRefactor/.babelrc b/09_ColorpRefactor/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/09_ColorpRefactor/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/09_ColorpRefactor/package.json b/09_ColorpRefactor/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/09_ColorpRefactor/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/09 ColorpRefactor/readme.md b/09_ColorpRefactor/readme.md similarity index 76% rename from 09 ColorpRefactor/readme.md rename to 09_ColorpRefactor/readme.md index 5b2687b..9e55221 100644 --- a/09 ColorpRefactor/readme.md +++ b/09_ColorpRefactor/readme.md @@ -1,17 +1,14 @@ # 09 Colorpicker Refactor -In this sample we are going to review the colorpicker component we have created -and simplify it, right now we have three slider controls with many details -that make our HTML hard to read, let's componentize this scenario. +In this example we are going to review the colorpicker component we have created and simplify it. Right now we have three slider controls with many details that make our HTML hard to read. Let's componentize this scenario. -We will take a startup point sample _08 Colorpicker_: +We take _08 Colorpicker_ as reference. -Summary steps: +## Summary steps: -- Create a simple color slide component. +- Create a simple color slider component. - Replace the color slider inputs with the new slider. -- Check result. - +- Check the result. ## Prerequisites @@ -25,9 +22,11 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are - Let's define a ColorSliderComponent component (_colorslider.tsx_). +_./src/colorslider.tsx_ + ```jsx import * as React from 'react'; -import {Color} from './color'; +import { Color } from './color'; interface Props { value : number; @@ -42,7 +41,7 @@ export const ColorSliderComponent = (props : Props) => { min="0" max="255" value={props.value} - onChange={(event : any) => props.onValueUpdated(event.target.value)} + onChange={(event) => props.onValueUpdated(event.target.value)} /> {props.value} @@ -54,8 +53,8 @@ export const ColorSliderComponent = (props : Props) => { ```diff import * as React from 'react'; -import {Color} from './color'; -+ import {ColorSliderComponent} from './colorslider'; +import { Color } from './color'; ++ import { ColorSliderComponent } from './colorslider'; interface Props { color : Color; @@ -65,21 +64,11 @@ interface Props { export const ColorPicker = (props : Props) => { return (
- props.onColorUpdated( - {red: event.target.value, green: props.color.green, blue: props.color.blue} - )} - /> - {props.color.red} -
- props.onColorUpdated( +- value={props.color.red} +- onChange={(event) => props.onColorUpdated( - { - red: props.color.red, - green: event.target.value, @@ -152,28 +141,30 @@ export const ColorPicker = (props : Props) => { } ``` -- Let's give a try and check that everything is still working as expected. +- Let's give it a try and check it works as expected. ``` npm start ``` -- We have still room for improvement, why not having a single handler for all colors? if we currified the colorupdated handler we can ! +- We have still room for improvement. What about using a single handler for all colors? If we currify the colorupdated handler, then we can! + +_./src/colorpicker.tsx_ ```diff import * as React from 'react'; import { Color } from './color' -import {ColorSliderComponent} from './colorslider'; +import { ColorSliderComponent } from './colorslider'; interface Props { color: Color; onColorUpdated: (color: Color) => void; } -+ const updateColor = (props : Props, colorId : keyOf Color) => (value) => { ++ const updateColor = (props : Props, colorId : keyof Color) => (value) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. + props.onColorUpdated({ -+ ...props.color, -+ [colorId]: value ++ ...props.color, // this creates a clone of the current props.color object... ++ [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. + }); + }; @@ -220,3 +211,9 @@ export const ColorPicker = (props: Props) => { ); } ``` + +- Let's give it a try: + +``` +npm start +``` diff --git a/09_ColorpRefactor/readme_es.md b/09_ColorpRefactor/readme_es.md new file mode 100644 index 0000000..b385d9d --- /dev/null +++ b/09_ColorpRefactor/readme_es.md @@ -0,0 +1,221 @@ +# 09 Refactorización del Colorpicker +En este ejemplo revisaremos el componente colorpicker que creamos previamente, y lo simplificaremos. +Ahora tenemos 3 controles sliders con muchos detalles que hacen nuestro HTML difícil de leer. +Haremos un escenario más orientado al componente. + +Tomaremos como punto de partida el ejemplo _08 Colorpicker_: + +## Pasos resumidos: + +- Crear un componente deslizable para un color simple. +- Remplazar los slides inputs actuales con el nuevo componente creado. +- Revisar el resultado. + + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o más reciente) si no están ya instalados. + +> Verificar que estás ejecutando al menos la v6.x.x de node y npm 3.x.x ejecutando el siguiente comando `node -v` y `npm -v` en una terminal/console de window. Versiones antiguas pueden producir errores. + +## Pasos a seguir: + +- Copiar el contenido desde _08 ColorPicker_ y ejecutar `npm install`. + +- Definiremos un componente ColorSliderComponent (_colorslider.tsx_). + +_./src/colorslider.tsx_ + +```jsx +import * as React from 'react'; +import { Color } from './color'; + +interface Props { + value : number; + onValueUpdated : (newValue : number) => void; +} + +export const ColorSliderComponent = (props : Props) => { + + return ( +
+ props.onValueUpdated(event.target.value)} + /> + {props.value} +
+ ); +} +``` + +- Refactorizamos nuestro _colorpicker.tsx_. + +```diff +import * as React from 'react'; +import { Color } from './color'; ++ import { ColorSliderComponent } from './colorslider'; + +interface Props { + color : Color; + onColorUpdated : (color : Color) => void; +} + +export const ColorPicker = (props : Props) => { + return ( +
+- props.onColorUpdated( +- { +- red: props.color.red, +- green: event.target.value, +- blue: props.color.blue +- } +- )} +- /> +- {props.color.green} ++ props.onColorUpdated( ++ { ++ red: value, ++ green: props.color.green, ++ blue: props.color.blue ++ }) ++ } ++ /> +
+- props.onColorUpdated( +- { +- red: props.color.red, +- green: event.target.value, +- blue: props.color.blue +- } +- )} +- /> +- {props.color.green} ++ props.onColorUpdated( ++ { ++ red: props.color.red, ++ green: value, ++ blue: props.color.blue ++ }) ++ } ++ /> +
+- props.onColorUpdated( +- { +- red: props.color.red, +- green: props.color.green, +- blue: event.target.value +- } +- )} +- /> +- {props.color.blue} ++ props.onColorUpdated( ++ { ++ red: props.color.red, ++ green: props.color.green, ++ blue: value ++ }) ++ } ++ /> +
+
+ ); +} +``` + +- Probemos y verifiquemos que todo funciona según lo esperado. + + ``` + npm start + ``` + +- Todavía tenemos mejoras que hacer, ¿porque no tener un solo manejador para todos los colores? Si currificamos el colorupdated handler, podemos! + +_./src/colorpicker.tsx_ + +```diff +import * as React from 'react'; +import { Color } from './color' +import { ColorSliderComponent } from './colorslider'; + +interface Props { + color: Color; + onColorUpdated: (color: Color) => void; +} + ++ const updateColor = (props : Props, colorId : keyof Color) => (value) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. ++ props.onColorUpdated({ ++ ...props.color, // this creates a clone of the current props.color object... ++ [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. ++ }); ++ }; + +export const ColorPicker = (props: Props) => { + return ( +
+ props.onColorUpdated( +- { +- red: value, +- green: props.color.green, +- blue: props.color.blue +- }) +- } + /> +
+ props.onColorUpdated( +- { +- red: props.color.red, +- green: value, +- blue: props.color.blue +- }) +- } + /> +
+ props.onColorUpdated( +- { +- red: props.color.red, +- green: props.color.green, +- blue: value +- }) +- } + /> +
+
+ ); +} +``` + +- Démosle una oprtunidad al ejemplo: + +``` +npm start +``` diff --git a/09 ColorpRefactor/src/app.tsx b/09_ColorpRefactor/src/app.tsx similarity index 77% rename from 09 ColorpRefactor/src/app.tsx rename to 09_ColorpRefactor/src/app.tsx index 1a3de6a..ab7fde2 100644 --- a/09 ColorpRefactor/src/app.tsx +++ b/09_ColorpRefactor/src/app.tsx @@ -14,17 +14,17 @@ export class App extends React.Component<{}, State> { this.state = {color: {red: 90, green: 50, blue: 70}}; } - setColorState(newColor : Color) { + setColorState = (newColor : Color) => { this.setState({color: newColor}); } public render() { - return ( -
- + return ( +
+ Color: [red: {this.state.color.red}, green: {this.state.color.green}, blue: {this.state.color.blue}] - -
- ); - } -} + +
+ ); + } +} \ No newline at end of file diff --git a/09 ColorpRefactor/src/color.ts b/09_ColorpRefactor/src/color.ts similarity index 96% rename from 09 ColorpRefactor/src/color.ts rename to 09_ColorpRefactor/src/color.ts index 0e9c39e..a9e0aab 100644 --- a/09 ColorpRefactor/src/color.ts +++ b/09_ColorpRefactor/src/color.ts @@ -1,6 +1,5 @@ - export interface Color { red : number; green : number; blue : number; -} +} \ No newline at end of file diff --git a/09 ColorpRefactor/src/colordisplayer.tsx b/09_ColorpRefactor/src/colordisplayer.tsx similarity index 56% rename from 09 ColorpRefactor/src/colordisplayer.tsx rename to 09_ColorpRefactor/src/colordisplayer.tsx index 571f846..1a4cb1c 100644 --- a/09 ColorpRefactor/src/colordisplayer.tsx +++ b/09_ColorpRefactor/src/colordisplayer.tsx @@ -6,17 +6,16 @@ interface Props { } export const ColorDisplayer = (props : Props) => { - // `rgb(${props.color.red},${props.color.green}, ${props.color.blue}) })` - // 'rgb(' + props.color.red + ', 40, 80)' - var divStyle = { - width: '120px', - height: '80px', + + const divStyle : React.CSSProperties = { // React.CSSProperties gives editing-time visual feedback on the CSS you are typing. + width: '11rem', + height: '7rem', backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})` }; return ( -
+
); } diff --git a/09_ColorpRefactor/src/colorpicker.tsx b/09_ColorpRefactor/src/colorpicker.tsx new file mode 100644 index 0000000..f27ffdb --- /dev/null +++ b/09_ColorpRefactor/src/colorpicker.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import { Color } from './color' +import { ColorSliderComponent } from './colorslider'; + +interface Props { + color: Color; + onColorUpdated: (color: Color) => void; +} + +const updateColor = (props: Props, colorId: keyof Color) => (value) => { // keyof Color ensures only 'red', 'blue' or 'green' can be passed in. + props.onColorUpdated({ + ...props.color, // this creates a clone of the current props.color object... + [colorId]: value // ... which gets one of its properties (colorId) immediately replaced by a new value. + }); +}; + + +export const ColorPicker = (props: Props) => { + return ( +
+ +
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/09 ColorpRefactor/src/colorslider.tsx b/09_ColorpRefactor/src/colorslider.tsx similarity index 100% rename from 09 ColorpRefactor/src/colorslider.tsx rename to 09_ColorpRefactor/src/colorslider.tsx diff --git a/09_ColorpRefactor/src/index.html b/09_ColorpRefactor/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/09_ColorpRefactor/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/04 Callback/src/main.tsx b/09_ColorpRefactor/src/main.tsx similarity index 76% rename from 04 Callback/src/main.tsx rename to 09_ColorpRefactor/src/main.tsx index 1815f3b..d75c95c 100644 --- a/04 Callback/src/main.tsx +++ b/09_ColorpRefactor/src/main.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; +import {App} from './app'; -import { App } from './app'; ReactDOM.render( - , + , document.getElementById('root') ); diff --git a/09_ColorpRefactor/src/nameEdit.tsx b/09_ColorpRefactor/src/nameEdit.tsx new file mode 100644 index 0000000..f7f59fb --- /dev/null +++ b/09_ColorpRefactor/src/nameEdit.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import {Fragment} from 'react'; + + +interface Props { + userName : string; + editingUserName : string; + onEditingNameUpdated : (newEditingName : string) => void; + onNameUpdateRequest : () => void; +} + +export const NameEditComponent = (props : Props) => +
+ + props.onEditingNameUpdated((e.target as HTMLInputElement).value)} /> + + +
diff --git a/09_ColorpRefactor/tsconfig.json b/09_ColorpRefactor/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/09_ColorpRefactor/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/09_ColorpRefactor/webpack.config.js b/09_ColorpRefactor/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/09_ColorpRefactor/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/10 Sidebar/.babelrc b/10 Sidebar/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/10 Sidebar/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/10 Sidebar/package.json b/10 Sidebar/package.json deleted file mode 100644 index 1e36b7e..0000000 --- a/10 Sidebar/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "^16.2.0", - "react-dom": "^16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "^16.0.36", - "@types/react-dom": "^16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/10 Sidebar/src/app.tsx b/10 Sidebar/src/app.tsx deleted file mode 100644 index 06b4de9..0000000 --- a/10 Sidebar/src/app.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; -import {HelloComponent} from './hello'; -import {NameEditComponent} from './nameEdit'; -import {SidebarComponent} from './sidebar'; - -interface Props { - -} - -interface State { - userName : string; - isSidebarVisible : boolean; -} - -export class App extends React.Component { - constructor(props: Props) { - super(props); - - this.state = {userName: "defaultUserName", isSidebarVisible: false}; - } - - setUsernameState(event) { - this.setState({userName: event.target.value} as State); - } - - toggleSidebarVisibility = () => { - const newVisibleState = !this.state.isSidebarVisible; - - this.setState({isSidebarVisible: newVisibleState} as State); - } - - public render() { - return ( -
- -

Test content

-
- - -
- -
-
- ); - } -} diff --git a/10 Sidebar/src/index.html b/10 Sidebar/src/index.html deleted file mode 100644 index 36b5a9b..0000000 --- a/10 Sidebar/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/10 Sidebar/src/main.tsx b/10 Sidebar/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/10 Sidebar/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/10 Sidebar/src/mystyles.ts b/10 Sidebar/src/mystyles.ts deleted file mode 100644 index 43793fd..0000000 --- a/10 Sidebar/src/mystyles.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function calculateDivWidth(isVisible : boolean) { - const widthpx = (isVisible) ? '250px' : '0px'; - const style = {width: widthpx} - - return style; -} diff --git a/10 Sidebar/src/nameEdit.tsx b/10 Sidebar/src/nameEdit.tsx deleted file mode 100644 index 4f1a5fe..0000000 --- a/10 Sidebar/src/nameEdit.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from 'react'; - -export const NameEditComponent = (props: {userName : string, onChange : (event : any) => any}) => { - return ( -
- - -
- ); -} diff --git a/10 Sidebar/src/sidebar.tsx b/10 Sidebar/src/sidebar.tsx deleted file mode 100644 index 147d03e..0000000 --- a/10 Sidebar/src/sidebar.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react'; - -const classNames = require('./sidebar.css'); - -interface Props { - isVisible: boolean; -} - -const divStyle = (props: React.CSSProperties) => ({ - width: (props.isVisible) ? '23rem' : '0rem' -}); - - -export const SidebarComponent : React.StatelessComponent = (props) => { - return ( -
- {props.children} -
- ); -} diff --git a/10 Sidebar/tsconfig.json b/10 Sidebar/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/10 Sidebar/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/10 Sidebar/webpack.config.js b/10 Sidebar/webpack.config.js deleted file mode 100644 index df58a53..0000000 --- a/10 Sidebar/webpack.config.js +++ /dev/null @@ -1,105 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx', '.css'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options:{ - useBabel: true, - }, - }, - // Use CSS modules for custom stylesheets - { - test: /\.css$/, - exclude: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: [ - { - loader: 'css-loader', - options: { - modules: true, - localIdentName: '[name]__[local]___[hash:base64:5]', - camelCase: true, - }, - }, - ] - }), - }, - // Do not use CSS modules in node_modules folder - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/10_Sidebar/.babelrc b/10_Sidebar/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/10_Sidebar/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/10_Sidebar/package.json b/10_Sidebar/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/10_Sidebar/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/10 Sidebar/readme.md b/10_Sidebar/readme.md similarity index 68% rename from 10 Sidebar/readme.md rename to 10_Sidebar/readme.md index a39b385..a5a985e 100644 --- a/10 Sidebar/readme.md +++ b/10_Sidebar/readme.md @@ -4,7 +4,7 @@ In this sample we are going to implement a single sidebar. We will take a startup point sample _03 State_: -Summary steps: +## Summary steps: - Add some styles. - Include the new css into the webpack loop. @@ -23,7 +23,7 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are - Copy the content from _03 State_ and execute `npm install`. -- Create a file called src/sidebar.css and add the following styles (http://www.w3schools.com/howto/howto_js_sidenav.asp): +- Create a file called _src/sidebar.css_ and add the following styles (http://www.w3schools.com/howto/howto_js_sidenav.asp): _./src/sidebar.css_ @@ -80,23 +80,20 @@ _./webpack.config.js_ - We will only use CSS Modules for custom app stylesheets. We will not use CSS Modules for other CSS files, like Bootstrap (folder node_modules). +_./webpack.config.js_ + ```diff { test: /\.css$/, + include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + // Use CSS modules for custom stylesheets + { + test: /\.css$/, -+ loader: ExtractTextPlugin.extract({ -+ fallback: 'style-loader', -+ use: [ ++ exclude: /node_modules/, ++ use: [ ++ MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { @@ -106,7 +103,6 @@ _./webpack.config.js_ + }, + }, + ] -+ }), + }, + // Do not use CSS modules in node_modules folder @@ -116,21 +112,20 @@ _./webpack.config.js_ - We are going to create now a sidebar component, _src/sidebar.tsx_. Right now we will create just a rectangle and we will interact with the animation. +_./src/sidebar.tsx_ + ```jsx import * as React from 'react'; const classNames = require('./sidebar.css'); -export const SidebarComponent = () => { - return ( +export const SidebarComponent = () =>
Basic side bar, first steps
- ); -} ``` -- We are going to add a known id to to body section of _src/index.html_ page +- We are going to add a known id to the body section of _src/index.html_ page _./src/index.html_ @@ -139,23 +134,32 @@ _./src/index.html_ + ``` -- Let's place the component adding into the app.tsx: +- Let's place the component adding it into the `app.tsx`: -```jsx - import {SidebarComponent} from './sidebar'; +_./src/app.tsx_ + +```diff + import * as React from 'react'; + import { HelloComponent } from './hello'; + import { NameEditComponent } from './nameEdit'; ++ import { SidebarComponent } from './sidebar'; ``` +_./src/app.tsx_ + ```diff return ( -
+ <> + - -
+ + ); ``` -- Now is time to run the app, just to check we haven't broken anything (but you will see no results). +- Now it is time to run the app, just to check we haven't broken anything (but you will see no results). ``` npm start @@ -175,18 +179,16 @@ const classNames = require('./sidebar.css'); + isVisible: boolean; + } -- export const SidebarComponent = () => { -+ export const SidebarComponent = (props: Props) => { - return ( +- export const SidebarComponent = () => ++ export const SidebarComponent = (props: Props) =>
- Basic side bar, first steps + Basic sidebar, first steps
- ); -} ``` -- Now let's add some logic to show / display the sidebar in case the flag gets -updated +- Now let's add some logic to show / hide the sidebar in case the flag gets updated + +_./src/sidebar.tsx_ ```diff import * as React from 'react'; @@ -201,14 +203,13 @@ interface Props { + width: (props.isVisible) ? '23rem' : '0rem' + }); -export const SidebarComponent = (props: Props) => { - return ( +export const SidebarComponent = (props: Props) => -
-+
- Basic side bar, first steps ++
+ Basic sidebar, first steps
- ); -} ``` - Now at app level (in file _app.tsx_) we can add a new member to the state (a boolean flag) and a button to turn it @@ -227,7 +228,10 @@ export class App extends React.Component<{}, State> { super(props); - this.state = {userName: 'defaultUserName'}; -+ this.state = {userName: "defaultUserName", isSidebarVisible: false}; ++ this.state = { ++ userName: "defaultUserName", ++ isSidebarVisible: false ++ }; } setUsernameState(event) { @@ -238,46 +242,60 @@ export class App extends React.Component<{}, State> { + toggleSidebarVisibility = () => { + const newVisibleState = !this.state.isSidebarVisible; + -+ this.setState({isSidebarVisible: newVisibleState} as State); ++ this.setState({isSidebarVisible: newVisibleState}); + } public render() { return ( -
+ <> - + - -+
+ ++
+ +
- -
+ ); } } ``` +- At this point we will need to stop the app and start it again to see the changes working: + +``` +npm start +``` + - If we run our sample, we can see how the sidebar is shown / hidden. -- So far so good, but what happens if we want to make this sidebar a reusable component, we could -just show the frame but the content should be dynamic. +- So far so good, but what happens if we want to make this sidebar a reusable component? We could just show the frame but the content should be dynamic. - Let's start by adding some content when instantiating the sidebar (_app.tsx_). +_./src/app.tsx_ + ```diff - -+

Test content

-
+- ++ ++

Cool Scfi movies

++ ++
``` - Now in the _sidebar.tsx_ let's dump this content by using {this.props.children} +_./src/sidebar.tsx_ + ```diff import * as React from 'react'; @@ -291,22 +309,19 @@ const divStyle = (props: React.CSSProperties) => ({ width: (props.isVisible) ? '250px' : '0px' }); -- export const SidebarComponent = (props: Props) => { -+ export const SidebarComponent : React.StatelessComponent = (props) => { +- export const SidebarComponent = (props: Props) => ++ export const SidebarComponent : React.StatelessComponent = (props) => - return (
- Basic side bar, first steps + {props.children}
- ); -} ``` -> This code can be enhanced, excercise: try to extra the width calculation to a separate function (isolated form the SidebarComponent). +> This code can be enhanced, exercise: try to extract the width calculation to a separate function (isolated from the SidebarComponent). - Let's try the sample -```cmd +``` npm start ``` diff --git a/10_Sidebar/readme_es.md b/10_Sidebar/readme_es.md new file mode 100644 index 0000000..7451f99 --- /dev/null +++ b/10_Sidebar/readme_es.md @@ -0,0 +1,325 @@ +# 10 Sidebar + +En este ejemplo vamos a implementar una única barra lateral. + +Tomaremos como punto de partida el ejemplo _03 State_: + +## Resumen de pasos: + +- Añadir algunos estilos. +- Incluir el nuevo css en webpack. +- Crear un componente de barra lateral. +- Añadir algo de contenido a la barra lateral. +- Añadir un botón para abrir / cerrar la barra lateral. + + +## Prerequisitos + +Instalar [Node.js y npm](https://nodejs.org/es/) (v6.0.0 o más reciente) si no lo tenemos ya instalado en nuestra máquina. + +> Verificar que estás ejecutando al menos con la versión 6.x.x de node y la versión 3.x.x de npm ejecutando `node -v` y `npm -v` en la ventana de terminal/consola. Versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copiar el contenido de _03 State_ y ejecutar `npm install`. + +- Crear un fichero llamado _src/sidebar.css_ y añadir los siguientes estilos (http://www.w3schools.com/howto/howto_js_sidenav.asp): + +_./src/sidebar.css_ + +```css + /* The side navigation menu */ + .sidenav { + height: 100%; /* 100% Full-height */ + width: 0; /* 0 width - change this with JavaScript */ + position: fixed; /* Stay in place */ + z-index: 1; /* Stay on top */ + top: 0; + left: 0; + background-color: #808080; /* Gray*/ + overflow-x: hidden; /* Disable horizontal scroll */ + padding-top: 60px; /* Place content 60px from the top */ + transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */ + } + + + /* Position and style the close button (top right corner) */ + .sidenav .closebtn { + position: absolute; + top: 0; + right: 25px; + font-size: 36px; + margin-left: 50px; + } + + /* Style page content - use this if you want to push the page content to the right when you open the side navigation */ + #main { + transition: margin-left .5s; + padding: 20px; + } + + /* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */ + @media screen and (max-height: 450px) { + .sidenav {padding-top: 15px;} + .sidenav a {font-size: 18px;} + } +``` + +- Vamos a utilizar CSS Modules, así que configurémoslo. + +_./webpack.config.js_ + +```diff + module.exports = { + context: path.join(basePath, "src"), + resolve: { +- extensions: ['.js', '.ts', '.tsx'] ++ extensions: ['.js', '.ts', '.tsx', '.css'] + }, +``` + +- Solo utilizaremos CSS Modules para la hoja de estilos de la app. No utilizaremos CSS Modules para otros ficheros CSS, como Bootstrap (carpeta node_modules). + +_./webpack.config.js_ + +```diff + { + test: /\.css$/, ++ include: /node_modules/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, ++ // Use CSS modules for custom stylesheets ++ { ++ test: /\.css$/, ++ exclude: /node_modules/, ++ use: [ ++ MiniCssExtractPlugin.loader, ++ { ++ loader: 'css-loader', ++ options: { ++ modules: true, ++ localIdentName: '[name]__[local]___[hash:base64:5]', ++ camelCase: true, ++ }, ++ }, ++ ] ++ }, ++ // Do not use CSS modules in node_modules folder + +``` + + +- Vamos a crear un nuevo componente sidebar, _src/sidebar.tsx_. En este punto crearemos solamente un rectangulo e interactuaremos con la animación. + +_./src/sidebar.tsx_ + +```jsx +import * as React from 'react'; + +const classNames = require('./sidebar.css'); + +export const SidebarComponent = () => +
+ Basic side bar, first steps +
+``` + +- Vamos a añadir un id conocido a la sección body de la página _src/index.html_ + +_./src/index.html_ + +```diff +- ++ +``` + +- Situemos el componente añadiendolo dentro de `app.tsx`: + +_./src/app.tsx_ + +```diff + import * as React from 'react'; + import { HelloComponent } from './hello'; + import { NameEditComponent } from './nameEdit'; ++ import { SidebarComponent } from './sidebar'; +``` + +_./src/app.tsx_ + +```diff + return ( + <> ++ + + + + ); +``` + +- Ahora es el momento de ejecutar la aplicación, simplemente para comprobar que no se ha roto nada (pero no verás ningún resultado). + +``` + npm start +``` + +- Comencemos con la parte interesante de la implementación, añadamos un flag para mostrar/ocultar la barra lateral _sidebar.tsx_. + +_./src/sidebar.tsx_ + +```diff +import * as React from 'react'; + +const classNames = require('./sidebar.css'); + ++ interface Props { ++ isVisible: boolean; ++ } + +- export const SidebarComponent = () => ++ export const SidebarComponent = (props: Props) => +
+ Basic sidebar, first steps +
+``` + +- Añadamos algo de lógica para mostrar/ocultar la barra lateral en caso de que se actualice el valor del flag + +_./src/sidebar.tsx_ + +```diff +import * as React from 'react'; + +const classNames = require('./sidebar.css'); + +interface Props { + isVisible: boolean; +}; + ++ const divStyle = (props: Props): React.CSSProperties => ({ ++ width: (props.isVisible) ? '23rem' : '0rem' ++ }); + +export const SidebarComponent = (props: Props) => +-
++
+ Basic sidebar, first steps +
+``` + +- Ahora a nivel de app (en el fichero _app.tsx_) podemos añadir un nuevo miembro al estado (un flag booleano) y un botón para activarlo o desactivarlo. + +```diff + interface State { + userName : string; ++ isSidebarVisible : boolean; + } +``` + +```diff +export class App extends React.Component<{}, State> { + constructor(props) { + super(props); + +- this.state = {userName: 'defaultUserName'}; ++ this.state = { ++ userName: "defaultUserName", ++ isSidebarVisible: false ++ }; + } + + setUsernameState(event) { + // If the state gets more complex we should use object.assign + this.setState({userName: event.target.value}); + } + ++ toggleSidebarVisibility = () => { ++ const newVisibleState = !this.state.isSidebarVisible; ++ ++ this.setState({isSidebarVisible: newVisibleState} as State); ++ } + + + public render() { + return ( + <> +- ++ + + ++
++ ++
+ + ); + } +} +``` + +- Llegados a este punto necesitaremos parar y volver a arrancar la aplicación para ver los cambios funcionando: + +``` +npm start +``` + +- Si ejecutamos nuestro ejemplo, podemos ver como nuestra barra lateral se muestra/oculta. + +- Hasta aqui todo bien, pero ¿que pasa si queremos hacer que nuestra barra lateral sea un componente reusable? Podriamos simplemente mostrar nuestra caja pero el contenido debería ser dinámico. + +- Comencemos añadiendo algo de contenido cuando nuestra barra lateral es instanciada (_app.tsx_). + +_./src/app.tsx_ + +```diff +- ++ ++

Cool Scfi movies

++ ++
+``` + +- Ahora vamos a volcar algo de contenido en _sidebar.tsx_ usando {this.props.children} + +_./src/sidebar.tsx_ + +```diff +import * as React from 'react'; + +const classNames = require('./sidebar.css'); + +interface Props { + isVisible: boolean; +}; + +const divStyle = (props: React.CSSProperties) => ({ + width: (props.isVisible) ? '250px' : '0px' +}); + +- export const SidebarComponent = (props: Props) => ++ export const SidebarComponent : React.StatelessComponent = (props) => + +
+- Basic side bar, first steps ++ {props.children} +
+``` + +> Este código puede mejorarse, ejercicio: intenta extraer el cálculo de width a una función separada (aislada de SidebarComponent). + +- Probemos el ejemplo + +``` +npm start +``` diff --git a/10_Sidebar/src/app.tsx b/10_Sidebar/src/app.tsx new file mode 100644 index 0000000..ad70fdb --- /dev/null +++ b/10_Sidebar/src/app.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; +import { SidebarComponent } from './sidebar'; + +interface Props { +} + +interface State { + userName: string; + isSidebarVisible: boolean; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: "defaultUserName", isSidebarVisible: false }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + toggleSidebarVisibility = () => { + const newVisibleState = !this.state.isSidebarVisible; + + this.setState({ isSidebarVisible: newVisibleState } as State); + } + + public render() { + return ( + <> + +

Cool Scfi movies

+ +
+ + + +
+ +
+ + ); + } +} \ No newline at end of file diff --git a/05 Refactor/src/hello.tsx b/10_Sidebar/src/hello.tsx similarity index 50% rename from 05 Refactor/src/hello.tsx rename to 10_Sidebar/src/hello.tsx index cdb2a70..5636921 100644 --- a/05 Refactor/src/hello.tsx +++ b/10_Sidebar/src/hello.tsx @@ -1,10 +1,6 @@ import * as React from 'react'; -interface Props { - userName : string; -} - -export const HelloComponent = (props : Props) => { +export const HelloComponent = (props: {userName : string}) => { return (

Hello user: {props.userName} !

); diff --git a/10_Sidebar/src/index.html b/10_Sidebar/src/index.html new file mode 100644 index 0000000..0ebb720 --- /dev/null +++ b/10_Sidebar/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/10_Sidebar/src/main.tsx b/10_Sidebar/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/10_Sidebar/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/10_Sidebar/src/nameEdit.tsx b/10_Sidebar/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/10_Sidebar/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/10 Sidebar/src/sidebar.css b/10_Sidebar/src/sidebar.css similarity index 95% rename from 10 Sidebar/src/sidebar.css rename to 10_Sidebar/src/sidebar.css index fb7201b..d6e0d52 100644 --- a/10 Sidebar/src/sidebar.css +++ b/10_Sidebar/src/sidebar.css @@ -1,5 +1,5 @@ -/* The side navigation menu */ -.sidenav { + /* The side navigation menu */ + .sidenav { height: 100%; /* 100% Full-height */ width: 0; /* 0 width - change this with JavaScript */ position: fixed; /* Stay in place */ diff --git a/10_Sidebar/src/sidebar.tsx b/10_Sidebar/src/sidebar.tsx new file mode 100644 index 0000000..5e74a12 --- /dev/null +++ b/10_Sidebar/src/sidebar.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +const classNames = require('./sidebar.css'); + +interface Props { + isVisible: boolean; +} + +const divStyle = (props: Props): React.CSSProperties => ({ + width: (props.isVisible) ? '23rem' : '0rem' +}); + + +export const SidebarComponent: React.StatelessComponent = (props) => +
+ {props.children} +
diff --git a/10_Sidebar/tsconfig.json b/10_Sidebar/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/10_Sidebar/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/10_Sidebar/webpack.config.js b/10_Sidebar/webpack.config.js new file mode 100644 index 0000000..f9fd461 --- /dev/null +++ b/10_Sidebar/webpack.config.js @@ -0,0 +1,81 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx', '.css'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + include: /node_modules/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + // Use CSS modules for custom stylesheets + { + test: /\.css$/, + exclude: /node_modules/, + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', + camelCase: true, + }, + }, + ] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/11 TableMock/.babelrc b/11 TableMock/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/11 TableMock/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/11 TableMock/package.json b/11 TableMock/package.json deleted file mode 100644 index 971917d..0000000 --- a/11 TableMock/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/11 TableMock/src/app.tsx b/11 TableMock/src/app.tsx deleted file mode 100644 index b5b5f25..0000000 --- a/11 TableMock/src/app.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import {MembersTableComponent} from './membersTable'; - -interface State { - userName : string; -} - -export class App extends React.Component<{}, State> { - public render() { - return ( -
- -
- ); - } -} diff --git a/11 TableMock/src/index.html b/11 TableMock/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/11 TableMock/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/11 TableMock/src/main.tsx b/11 TableMock/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/11 TableMock/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/11 TableMock/src/membersTable.tsx b/11 TableMock/src/membersTable.tsx deleted file mode 100644 index e05451d..0000000 --- a/11 TableMock/src/membersTable.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from 'react'; -import {MemberEntity} from './model/member'; -import {memberAPI} from './api/memberAPI'; -import {MemberRowComponent} from './memberRow'; - -interface Props extends React.Props { -} - -// We define members as a state (the compoment holding this will be a container -// component) -interface State { - members : Array -} - -// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX -export class MembersTableComponent extends React.Component { - - constructor(props : Props){ - super(props); - // set initial state - this.state = {members: []}; - } - - - // Standard react lifecycle function: - // https://facebook.github.io/react/docs/component-specs.html - public componentWillMount() { - this.setState({members: memberAPI.getAllMembers()}) - } - - public render() { - - return ( -
-

Members Page

- - - - - - - - - - { - this.state.members.map((member : MemberEntity) => - - ) - } - -
- Avatar - - Id - - Name -
-
- ); - } -} diff --git a/11 TableMock/webpack.config.js b/11 TableMock/webpack.config.js deleted file mode 100644 index 0c9c465..0000000 --- a/11 TableMock/webpack.config.js +++ /dev/null @@ -1,85 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options: { - useBabel: true, - } - }, - { - test: /\.css$/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/11_TableMock/.babelrc b/11_TableMock/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/11_TableMock/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/11_TableMock/package.json b/11_TableMock/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/11_TableMock/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/11 TableMock/readme.md b/11_TableMock/readme.md similarity index 86% rename from 11 TableMock/readme.md rename to 11_TableMock/readme.md index 24bb85c..b65c53f 100644 --- a/11 TableMock/readme.md +++ b/11_TableMock/readme.md @@ -6,12 +6,12 @@ We will start by just creating some mock data. We will take a startup point sample _03 State_: -Summary steps: +## Summary steps: - Define a model entity (we will call it _member_). -- Define a fake api (to take thing simple we will just make it synchronous) +- Define a fake api (to take thing simple we will just make it synchronous). - We will row component, we will call it _memberRow_. -- Create a table component, we will call it _memberTable_ and make use of _memberRow +- Create a table component, we will call it _memberTable_ and make use of _memberRow. ## Prerequisites @@ -24,6 +24,10 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not alrea - Copy the content from _03 State_ and execute _npm install_. +```cmd +npm install +``` + - Let's define a model entity in _src/model/member.ts_: _./src/model/member.ts_ @@ -49,7 +53,7 @@ _./src/api/memberMockData.ts_ ```javascript import {MemberEntity} from '../model/member'; -var MembersMockData : MemberEntity[] = +const MembersMockData : MemberEntity[] = [ { id: 1457912, @@ -63,7 +67,7 @@ var MembersMockData : MemberEntity[] = } ]; - export default MembersMockData; +export default MembersMockData; ``` - Define a fake api (to take thing simple we will just make it synchronous) in _src/api/memberAPI.ts_: @@ -114,7 +118,8 @@ export const MemberRow = (props: {member : MemberEntity}) => ``` -We can't use max-widh in the param style in. We must write 'maxWidth' in the react components. + +> We can't use max-widh in the param style in. We must write 'maxWidth' in the react components. - Then we are going to implement a component that will display a list of members (and will make use of rows), _membersTable.tsx_: @@ -133,7 +138,7 @@ interface Props { // We define members as a state (the compoment holding this will be a container // component) interface State { - members : Array + members : MemberEntity[] } // Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX @@ -145,10 +150,9 @@ export class MembersTableComponent extends React.Component { this.state = {members: []}; } - // Standard react lifecycle function: // https://facebook.github.io/react/docs/component-specs.html - public componentWillMount() { + public componentDidMount() { this.setState({members: memberAPI.getAllMembers()}) } @@ -183,14 +187,15 @@ export class MembersTableComponent extends React.Component { ); } } - ``` - Let's update an app.tsx -```javascript +```diff import * as React from 'react'; -import {MembersTableComponent} from './membersTable'; +- import { HelloComponent } from './hello'; +- import { NameEditComponent } from './nameEdit'; ++ import {MembersTableComponent} from './membersTable'; interface State { userName : string; @@ -199,9 +204,17 @@ interface State { export class App extends React.Component<{}, State> { public render() { return ( -
- -
+ <> ++ +- +- + + ); } } diff --git a/11_TableMock/readme_es.md b/11_TableMock/readme_es.md new file mode 100644 index 0000000..c1e1fe3 --- /dev/null +++ b/11_TableMock/readme_es.md @@ -0,0 +1,224 @@ +# 11 TableMock + +Vamos a renderizar una tabla y usar un componente hijo para mostrar cada fila. + +Comemzamos por crear algunos datos falsos. + +Tomaremos como punto de entrada la muestra _03 State_: + +## Resumen de pasos: + +- Definimos un modelo de entidad (la llamaremos _member_). +- Definimos una api falsa (para hacerlo simple lo haremos síncrono). +- Crearemos un componente fila, que llamaremos _memberRow_. +- Crearemos un componente tabla, la llamaremos _memberTable_ y hacemos que use _memberRow. + +## Prerequesitos + +Instala [Node.js and npm](https://nodejs.org/en/) (v6.6.0) si no lo tienes aún instalado en tu máquina. + +> Verificar que estás ejecutando al menos con la versión 6.x.x de node y la versión 3.x.x de npm ejecutando `node -v` y `npm -v` en la ventana de terminal/consola. Versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contendio de _03 State_ y ejecuta _npm install_. + +```cmd +npm install +``` + +- Definamos una entidad modelo en _src/model/member.ts_: + +```javascript +export interface MemberEntity { + id: number; + login: string; + avatar_url: string; +} + +export const createEmptyMember = () : MemberEntity => ({ + id: -1, + login: "", + avatar_url: "" +}); +``` + +- Vamos a crear algunos datos falsos en _src/api/memberMockData.ts_: + +_./src/api/memberMockData.ts_ + +```javascript +import {MemberEntity} from '../model/member'; + +var MembersMockData : MemberEntity[] = + [ + { + id: 1457912, + login: "brauliodiez", + avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3" + }, + { + id: 4374977, + login: "Nasdan", + avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3" + } + ]; + +export default MembersMockData; +``` + +- Definimos una api falsa (para hacerlo simple vamos a hacerlo síncrono) en _src/api/memberAPI.ts_: + + _src/api/memberAPI.ts_ + + ```javascript +import {MemberEntity} from '../model/member'; +import MembersMockData from './memberMockData'; + +// Sync mock data API, inspired from: +// https://gist.github.com/coryhouse/fd6232f95f9d601158e4 +class MemberAPI { + //This would be performed on the server in a real app. Just stubbing in. + private _clone (item) { + return JSON.parse(JSON.stringify(item)); //return cloned copy so that the item is passed by value instead of by reference + }; + + // Just return a copy of the mock data + getAllMembers() : Array { + return this._clone(MembersMockData); + } +} + +export const memberAPI = new MemberAPI(); +``` + +- Ahora es momento de saltar dentro de una parte interesante, vamos a borrar _hello.tsx_ y _nameEdit.tsx_. + +- Vamos a crear un componente sin estado que mostrara una simple _memberRow.tsx_ fila. + +_src/memberRow.tsx_ + +```javascript +import * as React from 'react'; +import {MemberEntity} from './model/member'; + +export const MemberRow = (props: {member : MemberEntity}) => + + + + + + {props.member.id} + + + {props.member.login} + + +``` + +> No podemos usar max-widh en el parametro style. Tenemos que escribir 'maxWidth' en los componentes de react. + +- Luego vamos a implementar un componente que mostrará una lista de miembros (y usaremos las filas), _membersTable.tsx_: + +_./src/membersTable.tsx_ + +```javascript +import * as React from 'react'; +import {MemberEntity} from './model/member'; +import {memberAPI} from './api/memberAPI'; +import {MemberRow} from './memberRow'; + +interface Props { +} + +// We define members as a state (the compoment holding this will be a container +// component) +interface State { + members : MemberEntity[] +} + +// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX +export class MembersTableComponent extends React.Component { + + constructor(props : Props){ + super(props); + // set initial state + this.state = {members: []}; + } + + // Standard react lifecycle function: + // https://facebook.github.io/react/docs/component-specs.html + public componentDidMount() { + this.setState({members: memberAPI.getAllMembers()}) + } + + public render() { + + return ( +
+

Members Page

+ + + + + + + + + + { + this.state.members.map((member : MemberEntity) => + + ) + } + +
+ Avatar + + Id + + Name +
+
+ ); + } +} +``` + +- Vamos a actualizar app.tsx + +```diff +import * as React from 'react'; +- import { HelloComponent } from './hello'; +- import { NameEditComponent } from './nameEdit'; ++ import {MembersTableComponent} from './membersTable'; + +interface State { + userName : string; +} + +export class App extends React.Component<{}, State> { + public render() { + return ( + <> ++ +- +- + + + ); + } +} + +``` + +Ejecutemos la muestra + +``` +npm start +``` diff --git a/11 TableMock/src/api/memberAPI.ts b/11_TableMock/src/api/memberAPI.ts similarity index 91% rename from 11 TableMock/src/api/memberAPI.ts rename to 11_TableMock/src/api/memberAPI.ts index 165d0e6..5a062b3 100644 --- a/11 TableMock/src/api/memberAPI.ts +++ b/11_TableMock/src/api/memberAPI.ts @@ -1,5 +1,5 @@ import {MemberEntity} from '../model/member'; -import MembersMockData from './memberMockData'; +import MembersMockData from '../model/memberMockData'; // Sync mock data API, inspired from: // https://gist.github.com/coryhouse/fd6232f95f9d601158e4 diff --git a/11_TableMock/src/app.tsx b/11_TableMock/src/app.tsx new file mode 100644 index 0000000..74a4736 --- /dev/null +++ b/11_TableMock/src/app.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import {MembersTableComponent} from './membersTable'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + + ); + } +} \ No newline at end of file diff --git a/11_TableMock/src/hello.tsx b/11_TableMock/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/11_TableMock/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

Hello user: {props.userName} !

+ ); +} diff --git a/11_TableMock/src/index.html b/11_TableMock/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/11_TableMock/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/11_TableMock/src/main.tsx b/11_TableMock/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/11_TableMock/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/11 TableMock/src/memberRow.tsx b/11_TableMock/src/memberRow.tsx similarity index 75% rename from 11 TableMock/src/memberRow.tsx rename to 11_TableMock/src/memberRow.tsx index 0b3d56e..9e56039 100644 --- a/11 TableMock/src/memberRow.tsx +++ b/11_TableMock/src/memberRow.tsx @@ -1,15 +1,10 @@ import * as React from 'react'; import {MemberEntity} from './model/member'; - - - -export const MemberRowComponent = (props: {member : MemberEntity}) => { - - return ( +export const MemberRow = (props: {member : MemberEntity}) => - + {props.member.id} @@ -18,5 +13,3 @@ export const MemberRowComponent = (props: {member : MemberEntity}) => { {props.member.login} - ); -} diff --git a/12 TableHttp/src/membersTable.tsx b/11_TableMock/src/membersTable.tsx similarity index 87% rename from 12 TableHttp/src/membersTable.tsx rename to 11_TableMock/src/membersTable.tsx index 7b22e1c..7d84b8f 100644 --- a/12 TableHttp/src/membersTable.tsx +++ b/11_TableMock/src/membersTable.tsx @@ -13,7 +13,7 @@ interface State { } // Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX -export class MembersTable extends React.Component { +export class MembersTableComponent extends React.Component { constructor(props : Props){ super(props); @@ -21,13 +21,10 @@ export class MembersTable extends React.Component { this.state = {members: []}; } - // Standard react lifecycle function: // https://facebook.github.io/react/docs/component-specs.html - public componentWillMount() { - memberAPI.getAllMembers().then((members) => - this.setState({members: members}) - ); + public componentDidMount() { + this.setState({members: memberAPI.getAllMembers()}) } public render() { diff --git a/11 TableMock/src/model/member.ts b/11_TableMock/src/model/member.ts similarity index 100% rename from 11 TableMock/src/model/member.ts rename to 11_TableMock/src/model/member.ts diff --git a/11 TableMock/src/api/memberMockData.ts b/11_TableMock/src/model/memberMockData.ts similarity index 79% rename from 11 TableMock/src/api/memberMockData.ts rename to 11_TableMock/src/model/memberMockData.ts index 8b6f014..8a2846a 100644 --- a/11 TableMock/src/api/memberMockData.ts +++ b/11_TableMock/src/model/memberMockData.ts @@ -1,4 +1,4 @@ -import {MemberEntity} from '../model/member'; +import {MemberEntity} from './member'; var MembersMockData : MemberEntity[] = [ @@ -14,4 +14,4 @@ var MembersMockData : MemberEntity[] = } ]; - export default MembersMockData; +export default MembersMockData; diff --git a/11_TableMock/src/nameEdit.tsx b/11_TableMock/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/11_TableMock/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/11_TableMock/tsconfig.json b/11_TableMock/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/11_TableMock/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/11_TableMock/webpack.config.js b/11_TableMock/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/11_TableMock/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/12 TableHttp/.babelrc b/12 TableHttp/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/12 TableHttp/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/12 TableHttp/package.json b/12 TableHttp/package.json deleted file mode 100644 index dd635ae..0000000 --- a/12 TableHttp/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "awesome-typescript-loader": "^3.4.1", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/12 TableHttp/src/app.tsx b/12 TableHttp/src/app.tsx deleted file mode 100644 index ee2ad75..0000000 --- a/12 TableHttp/src/app.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import {MembersTable} from './membersTable'; - -interface State { - userName : string; -} - -export class App extends React.Component<{}, State> { - public render() { - return ( -
- -
- ); - } -} diff --git a/12 TableHttp/src/index.html b/12 TableHttp/src/index.html deleted file mode 100644 index 4b32a83..0000000 --- a/12 TableHttp/src/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - -

Sample app

-
-
- - diff --git a/12 TableHttp/src/main.tsx b/12 TableHttp/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/12 TableHttp/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/12 TableHttp/tsconfig.json b/12 TableHttp/tsconfig.json deleted file mode 100644 index 403fa3d..0000000 --- a/12 TableHttp/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "moduleResolution": "node", - "module": "es6", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} diff --git a/12 TableHttp/webpack.config.js b/12 TableHttp/webpack.config.js deleted file mode 100644 index 5fdde21..0000000 --- a/12 TableHttp/webpack.config.js +++ /dev/null @@ -1,88 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - use: - { - loader: 'awesome-typescript-loader', - options: { - useBabel: true - } - } - }, - { - test: /\.css$/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/12_TableHttp/.babelrc b/12_TableHttp/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/12_TableHttp/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/12_TableHttp/package.json b/12_TableHttp/package.json new file mode 100644 index 0000000..6321a58 --- /dev/null +++ b/12_TableHttp/package.json @@ -0,0 +1,39 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "core-js": "^2.5.7", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9", + "whatwg-fetch": "^3.0.0" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/12 TableHttp/readme.md b/12_TableHttp/readme.md similarity index 64% rename from 12 TableHttp/readme.md rename to 12_TableHttp/readme.md index 03e6ade..ca0d4a2 100644 --- a/12 TableHttp/readme.md +++ b/12_TableHttp/readme.md @@ -5,7 +5,7 @@ mock data by real one. We will take a startup point sample _11 TableMock_: -Summary steps: +## Summary steps: - Configure transpilation and add extra transpile step babel >> es5. - Update API in order to work with promises and fetch data from Github API. @@ -26,83 +26,31 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are npm install ``` -- Let's update our tsconfig.json - -_.tsconfig.json_ - -```diff -{ - "compilerOptions": { -+ "target": "es6", -+ "moduleResolution": "node", -+ "module": "es6", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} -``` - -- Let's install babel: - -```cmd -npm install babel-core babel-preset-env --save-dev -``` - -- Let's add a _.babelrc_ configuration +- Let's remove the file _mermberMockData.ts_ in _src/api_ directory. -_./.babelrc_ +- Just to provide support to old browsers let's install the following libraries: -``` -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} +```javascript +npm install whatwg-fetch --save-dev ``` -- Let's update our webpackconfig configuration (awesome typescript loader). +- In _webpack.config.js_ let's add the new _whatwg-fetch_ polyfill as entry point: -_./webpack.config.js_ +_webpack.config.js_ ```diff - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, -- loader: 'awesome-typescript-loader', -+ use: -+ { -+ loader: 'awesome-typescript-loader', -+ options: { -+ useBabel: true -+ } -+ } - }, +entry: ['@babel/polyfill', ++ 'whatwg-fetch', + './main.tsx' +], ``` -- Let's remove the file _mermberMockData.ts_ in _src/api_ directory. - - Let's replace _memberAPI_ load members with the fetch / promise one: _./src/api/memberAPI.ts_ ```javascript -import {MemberEntity} from '../model/member'; -import {} from 'core-js'; -import {} from 'whatwg-fetch'; +import { MemberEntity } from '../model/member'; // Sync mock data API, inspired from: // https://gist.github.com/coryhouse/fd6232f95f9d601158e4 @@ -147,20 +95,73 @@ class MemberAPI { } export const memberAPI = new MemberAPI(); + +``` +- Add a new component _memberHead_ to create the table's header: + +_./src/memberHead.tsx_ + +```javascript +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + ``` - Now it's time to update our _membersTable_ component.
- Let's consume the new promise base method to retrieve the users: -_./src/memberTable.tsx_ +_./src/membersTable.tsx_ + +- Import the new component: + +```diff + ++ import { MemberHead } from './memberHead'; -```jsx +``` + +- Modify the render function: + +```diff +- +- +- +- Avatar +- +- +- Id +- +- +- Name +- +- +- ++ ++ ++ +``` + +- Let's consume the new promise base method to retrieve the users: + +```diff // Standard react lifecycle function: // https://facebook.github.io/react/docs/component-specs.html -public componentWillMount() { - memberAPI.getAllMembers().then((members) => - this.setState({members: members}) - ); +public componentDidMount() { +- this.setState({members: memberAPI.getAllMembers()}) ++ memberAPI.getAllMembers().then((members) => ++ this.setState({members: members}) ++ ); } ``` diff --git a/12_TableHttp/readme_es.md b/12_TableHttp/readme_es.md new file mode 100644 index 0000000..b883e04 --- /dev/null +++ b/12_TableHttp/readme_es.md @@ -0,0 +1,161 @@ +# 12 Table Http + +Sigamos con nuestro ejemplo de la tabla, vamos a cambiar datos falsos por unos reales. + +Cogeremos como punto inicial el ejemplo _11 TableMock_: + +## Pasos resumidos: + +- Configurar transpilación y añadir una transpilación extra babel >> es5. +- Actualizar la API para trabajar con promesas y obtener datos de la API de Github. +- Actualizar _tableComponent_ para mostrar los datos. + + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o más nuevo) si no están ya instalados. + +> Verificar que tienes al menos corriendo la versión de node v6.x.x y npm 3.x.x ejecutando `node -v` y `npm -v` en la terminal de Windows. Versiones más antiguas pueden producir errores. + +## Pasos para construirlo + +- Copiar el contenido de _11 TableMock_ y ejecutar: + + ``` + npm install + ``` + +- Vamos a eliminar el fichero _mermberMockData.ts_ del directorio _src/api_ . + +- Para tener soporte en navegadores antiguos vamos a instalar la siguiente librería: + +```javascript +npm install whatwg-fetch --save-dev +``` + +- Vamos a reemplazar _memberAPI_ cargando los miembros con promesas : + +_./src/api/memberAPI.ts_ + +```javascript +import { MemberEntity } from '../model/member'; +import {} from 'whatwg-fetch'; + +// Sync mock data API, inspired from: +// https://gist.github.com/coryhouse/fd6232f95f9d601158e4 +class MemberAPI { + + // Just return a copy of the mock data + getAllMembers() : Promise { + const gitHubMembersUrl : string = 'https://api.github.com/orgs/lemoncode/members'; + + return fetch(gitHubMembersUrl) + .then((response) => this.checkStatus(response)) + .then((response) => this.parseJSON(response)) + .then((data) => this.resolveMembers(data)); + } + + private checkStatus(response : Response) : Promise { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response); + } else { + let error = new Error(response.statusText); + throw error; + } + } + + private parseJSON(response : Response) : any { + return response.json(); + } + + private resolveMembers (data : any) : Promise { + const members = data.map((gitHubMember) => { + var member : MemberEntity = { + id: gitHubMember.id, + login: gitHubMember.login, + avatar_url: gitHubMember.avatar_url, + }; + + return member; + }); + + return Promise.resolve(members); + } +} + +export const memberAPI = new MemberAPI(); + +``` +- Añadimos un nuevo componente _memberHead_ para crear la cabecera de la tabla: + +_./src/memberHead.tsx_ + +```javascript +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + +``` + +- Ahora vamos a actualizar nuestro componente _membersTable_ .
+ +_./src/memberTable.tsx_ + +- Importamos el nuevo componente: + +```diff + ++ import { MemberHead } from './memberHead'; + +``` + +- Modificamos la funcion render: + +```diff +- +- +- +- Avatar +- +- +- Id +- +- +- Name +- +- +- ++ ++ ++ +``` + +- Vamos a consumir el nuevo método de promesas para recuperar a los usuarios: + +```diff +// Standard react lifecycle function: +// https://facebook.github.io/react/docs/component-specs.html +public componentDidMount() { +- this.setState({members: memberAPI.getAllMembers()}) ++ memberAPI.getAllMembers().then((members) => ++ this.setState({members: members}) ++ ); +} +``` + +- Vamos a probar y verificar los resultados + +``` +npm start +``` diff --git a/12 TableHttp/src/api/memberAPI.ts b/12_TableHttp/src/api/memberAPI.ts similarity index 92% rename from 12 TableHttp/src/api/memberAPI.ts rename to 12_TableHttp/src/api/memberAPI.ts index 7f61a94..1551ace 100644 --- a/12 TableHttp/src/api/memberAPI.ts +++ b/12_TableHttp/src/api/memberAPI.ts @@ -1,6 +1,4 @@ import {MemberEntity} from '../model/member'; -import {} from 'core-js'; -import {} from 'whatwg-fetch'; // Sync mock data API, inspired from: // https://gist.github.com/coryhouse/fd6232f95f9d601158e4 @@ -44,4 +42,4 @@ class MemberAPI { } } -export const memberAPI = new MemberAPI(); \ No newline at end of file +export const memberAPI = new MemberAPI(); diff --git a/12_TableHttp/src/app.tsx b/12_TableHttp/src/app.tsx new file mode 100644 index 0000000..74a4736 --- /dev/null +++ b/12_TableHttp/src/app.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import {MembersTableComponent} from './membersTable'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + + ); + } +} \ No newline at end of file diff --git a/12_TableHttp/src/hello.tsx b/12_TableHttp/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/12_TableHttp/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

Hello user: {props.userName} !

+ ); +} diff --git a/12_TableHttp/src/index.html b/12_TableHttp/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/12_TableHttp/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/12_TableHttp/src/main.tsx b/12_TableHttp/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/12_TableHttp/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/12_TableHttp/src/memberHead.tsx b/12_TableHttp/src/memberHead.tsx new file mode 100644 index 0000000..1d8a947 --- /dev/null +++ b/12_TableHttp/src/memberHead.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + diff --git a/12 TableHttp/src/memberRow.tsx b/12_TableHttp/src/memberRow.tsx similarity index 59% rename from 12 TableHttp/src/memberRow.tsx rename to 12_TableHttp/src/memberRow.tsx index 0307fdb..9e56039 100644 --- a/12 TableHttp/src/memberRow.tsx +++ b/12_TableHttp/src/memberRow.tsx @@ -1,17 +1,10 @@ import * as React from 'react'; import {MemberEntity} from './model/member'; - -interface Props { - member : MemberEntity; -} - -export const MemberRow = (props: Props) => { - - return ( +export const MemberRow = (props: {member : MemberEntity}) => - + {props.member.id} @@ -20,5 +13,3 @@ export const MemberRow = (props: Props) => { {props.member.login} - ); -} diff --git a/12_TableHttp/src/membersTable.tsx b/12_TableHttp/src/membersTable.tsx new file mode 100644 index 0000000..fce8e40 --- /dev/null +++ b/12_TableHttp/src/membersTable.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { MemberEntity } from './model/member'; +import { memberAPI } from './api/memberAPI'; +import { MemberRow } from './memberRow'; +import { MemberHead } from './memberHead'; +import {} from 'core-js'; + +interface Props { +} + +// We define members as a state (the compoment holding this will be a container +// component) +interface State { + members: Array +} + +// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX +export class MembersTableComponent extends React.Component { + + constructor(props: Props) { + super(props); + // set initial state + this.state = { members: [] }; + } + + // Standard react lifecycle function: + // https://facebook.github.io/react/docs/component-specs.html + public componentDidMount() { + memberAPI.getAllMembers().then((members) => + this.setState({ members: members }) + ); + } + + public render() { + + return ( +
+

Members Page

+ + + + + + { + this.state.members.map((member: MemberEntity) => + + ) + } + +
+
+ ); + } +} diff --git a/12 TableHttp/src/model/member.ts b/12_TableHttp/src/model/member.ts similarity index 100% rename from 12 TableHttp/src/model/member.ts rename to 12_TableHttp/src/model/member.ts diff --git a/12_TableHttp/src/model/memberMockData.ts b/12_TableHttp/src/model/memberMockData.ts new file mode 100644 index 0000000..8a2846a --- /dev/null +++ b/12_TableHttp/src/model/memberMockData.ts @@ -0,0 +1,17 @@ +import {MemberEntity} from './member'; + +var MembersMockData : MemberEntity[] = + [ + { + id: 1457912, + login: "brauliodiez", + avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3" + }, + { + id: 4374977, + login: "Nasdan", + avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3" + } + ]; + +export default MembersMockData; diff --git a/12_TableHttp/src/nameEdit.tsx b/12_TableHttp/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/12_TableHttp/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/12_TableHttp/tsconfig.json b/12_TableHttp/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/12_TableHttp/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/12_TableHttp/webpack.config.js b/12_TableHttp/webpack.config.js new file mode 100644 index 0000000..862fd32 --- /dev/null +++ b/12_TableHttp/webpack.config.js @@ -0,0 +1,65 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + 'whatwg-fetch', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/12_TableHttp_Axios/.babelrc b/12_TableHttp_Axios/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/12_TableHttp_Axios/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/12_TableHttp_Axios/package.json b/12_TableHttp_Axios/package.json new file mode 100644 index 0000000..4a914d7 --- /dev/null +++ b/12_TableHttp_Axios/package.json @@ -0,0 +1,39 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "core-js": "^2.5.7", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "axios": "^0.18.0", + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/12_TableHttp_Axios/readme.md b/12_TableHttp_Axios/readme.md new file mode 100644 index 0000000..c6f473c --- /dev/null +++ b/12_TableHttp_Axios/readme.md @@ -0,0 +1,141 @@ +# 12 Table Http with Axios + +Let's move forward with the table sample, this time we are going to replace the +mock data by real one and we will make calls with the Axios package. + +We will take a startup point sample _11 TableMock_: + +## Summary steps: + +- Configure transpilation and add extra transpile step babel >> es5. +- Update API in order to work with [Axios](https://github.com/axios/axios) and fetch data from Github API. +- Update the _tableComponent_ in order to show this data. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _11 TableMock_ and execute: + +``` +npm install +``` + +- We install the Axios package: + +``` + npm install --save axios +``` + +- Let's remove the file _memberMockData.ts_ in _src/api_ directory. + +- Let's replace _memberAPI_ load members with the fetch / promise one: + +_./src/api/memberAPI.ts_ + +```javascript +import Axios, { AxiosResponse } from 'axios'; +import { MemberEntity } from '../model/member'; + +const gitHubURL = 'https://api.github.com'; +const gitHubMembersUrl = `${gitHubURL}/orgs/lemoncode/members`; + +const getAllMembers = (): Promise => { + const promise: Promise = new Promise((resolve, reject) => { + try { + Axios.get(gitHubMembersUrl) + .then(response => resolve(mapMemberListApiToModel(response))); + } catch (ex) { + reject(ex); + } + }); + + return promise; +}; + +const mapMemberListApiToModel = ({ data }: AxiosResponse) => + data.map(gitHubMember => gitHubMember); + +export const memberAPI = { + getAllMembers, +}; + +``` + +- Add a new component _memberHead_ to create the table's header: + +_./src/memberHead.tsx_ + +```javascript +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + +``` + +- Now it's time to update our _membersTable_ component. + +_./src/membersTable.tsx_ + +- Import the new component: + +```diff + ++ import { MemberHead } from './memberHead'; + +``` + +- Modify the render function: + +```diff +- +- +- +- Avatar +- +- +- Id +- +- +- Name +- +- +- ++ ++ ++ +``` + +- Let's consume the new promise base method to retrieve the users: + +```diff +// Standard react lifecycle function: +// https://facebook.github.io/react/docs/component-specs.html +public componentDidMount() { +- this.setState({members: memberAPI.getAllMembers()}) ++ memberAPI.getAllMembers().then((members) => ++ this.setState({members: members}) ++ ); +} +``` + +- Let's give a try and check the results + +``` +npm start +``` diff --git a/12_TableHttp_Axios/readme_es.md b/12_TableHttp_Axios/readme_es.md new file mode 100644 index 0000000..9c7aea4 --- /dev/null +++ b/12_TableHttp_Axios/readme_es.md @@ -0,0 +1,141 @@ +# 12 Table Http con Axios + +Sigamos con nuestro ejemplo de la tabla, vamos a cambiar datos falsos por unos reales y realizaremos las llamadas con la librería Axios. + +Cogeremos como punto inicial el ejemplo _11 TableMock_: + +## Pasos resumidos: + +- Configurar transpilación y añadir una transpilación extra babel >> es5. +- Actualizar la API para trabajar con [Axios](https://github.com/axios/axios) y obtener datos de la API de Github. +- Actualizar _tableComponent_ para mostrar los datos. + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o más nuevo) si no están ya instalados. + +> Verificar que tienes al menos corriendo la versión de node v6.x.x y npm 3.x.x ejecutando `node -v` y `npm -v` en la terminal de Windows. Versiones más antiguas pueden producir errores. + +## Pasos para construirlo + +- Copiar el contenido de _11 TableMock_ y ejecutar: + +``` +npm install +``` + +- Instalamos la librería Axios: + +``` + npm install --save axios +``` + +- Vamos a eliminar el fichero _memberMockData.ts_ del directorio _src/api_ . + +- Vamos a reemplazar _memberAPI_ cargando los miembros con promesas: + +_./src/api/memberAPI.ts_ + +```javascript +import Axios, { AxiosResponse } from 'axios'; +import { MemberEntity } from '../model/member'; + +const gitHubURL = 'https://api.github.com'; +const gitHubMembersUrl = `${gitHubURL}/orgs/lemoncode/members`; + +const getAllMembers = (): Promise => { + const promise: Promise = new Promise((resolve, reject) => { + try { + Axios.get(gitHubMembersUrl) + .then(response => resolve(mapMemberListApiToModel(response))); + } catch (ex) { + reject(ex); + } + }); + + return promise; +}; + +const mapMemberListApiToModel = ({ data }: AxiosResponse) => + data.map(gitHubMember => gitHubMember); + +export const memberAPI = { + getAllMembers, +}; + + +``` + +- Añadimos un nuevo componente _memberHead_ para crear la cabecera de la tabla: + +_./src/memberHead.tsx_ + +```javascript +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + +``` + +- Ahora vamos a actualizar nuestro componente _membersTable_. + +_./src/memberTable.tsx_ + +- Importamos el nuevo componente: + +```diff + ++ import { MemberHead } from './memberHead'; + +``` + +- Modificamos la función render: + +```diff +- +- +- +- Avatar +- +- +- Id +- +- +- Name +- +- +- ++ ++ ++ +``` + +- Vamos a consumir el nuevo método de promesas para recuperar a los usuarios: + +```diff +// Standard react lifecycle function: +// https://facebook.github.io/react/docs/component-specs.html +public componentDidMount() { +- this.setState({members: memberAPI.getAllMembers()}) ++ memberAPI.getAllMembers().then((members) => ++ this.setState({members: members}) ++ ); +} +``` + +- Vamos a probar y verificar los resultados + +``` +npm start +``` diff --git a/12_TableHttp_Axios/src/api/memberAPI.ts b/12_TableHttp_Axios/src/api/memberAPI.ts new file mode 100644 index 0000000..b860d7f --- /dev/null +++ b/12_TableHttp_Axios/src/api/memberAPI.ts @@ -0,0 +1,25 @@ +import Axios, { AxiosResponse } from 'axios'; +import { MemberEntity } from '../model/member'; + +const gitHubURL = 'https://api.github.com'; +const gitHubMembersUrl = `${gitHubURL}/orgs/lemoncode/members`; + +const getAllMembers = (): Promise => { + const promise: Promise = new Promise((resolve, reject) => { + try { + Axios.get(gitHubMembersUrl) + .then(response => resolve(mapMemberListApiToModel(response))); + } catch (ex) { + reject(ex); + } + }); + + return promise; +}; + +const mapMemberListApiToModel = ({ data }: AxiosResponse) => + data.map(gitHubMember => gitHubMember); + +export const memberAPI = { + getAllMembers, +}; diff --git a/12_TableHttp_Axios/src/app.tsx b/12_TableHttp_Axios/src/app.tsx new file mode 100644 index 0000000..74a4736 --- /dev/null +++ b/12_TableHttp_Axios/src/app.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import {MembersTableComponent} from './membersTable'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + + ); + } +} \ No newline at end of file diff --git a/12_TableHttp_Axios/src/hello.tsx b/12_TableHttp_Axios/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/12_TableHttp_Axios/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

Hello user: {props.userName} !

+ ); +} diff --git a/12_TableHttp_Axios/src/index.html b/12_TableHttp_Axios/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/12_TableHttp_Axios/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/12_TableHttp_Axios/src/main.tsx b/12_TableHttp_Axios/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/12_TableHttp_Axios/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/12_TableHttp_Axios/src/memberHead.tsx b/12_TableHttp_Axios/src/memberHead.tsx new file mode 100644 index 0000000..1d8a947 --- /dev/null +++ b/12_TableHttp_Axios/src/memberHead.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + diff --git a/12_TableHttp_Axios/src/memberRow.tsx b/12_TableHttp_Axios/src/memberRow.tsx new file mode 100644 index 0000000..9e56039 --- /dev/null +++ b/12_TableHttp_Axios/src/memberRow.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import {MemberEntity} from './model/member'; + +export const MemberRow = (props: {member : MemberEntity}) => + + + + + + {props.member.id} + + + {props.member.login} + + diff --git a/12_TableHttp_Axios/src/membersTable.tsx b/12_TableHttp_Axios/src/membersTable.tsx new file mode 100644 index 0000000..dbe99e0 --- /dev/null +++ b/12_TableHttp_Axios/src/membersTable.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { MemberEntity } from './model/member'; +import { memberAPI } from './api/memberAPI'; +import { MemberRow } from './memberRow'; +import { MemberHead } from './memberHead'; +import {} from 'core-js'; + +interface Props { +} + +// We define members as a state (the compoment holding this will be a container +// component) +interface State { + members: Array +} + +// Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX +export class MembersTableComponent extends React.Component { + + constructor(props: Props) { + super(props); + // set initial state + this.state = { members: [] }; + } + + // Standard react lifecycle function: + // https://facebook.github.io/react/docs/component-specs.html + public componentDidMount() { + memberAPI.getAllMembers().then((members) => + this.setState({ members }) + ); + } + + public render() { + + return ( +
+

Members Page

+ + + + + + { + this.state.members.map((member: MemberEntity) => + + ) + } + +
+
+ ); + } +} diff --git a/12_TableHttp_Axios/src/model/member.ts b/12_TableHttp_Axios/src/model/member.ts new file mode 100644 index 0000000..8977f30 --- /dev/null +++ b/12_TableHttp_Axios/src/model/member.ts @@ -0,0 +1,11 @@ +export interface MemberEntity { + id: number; + login: string; + avatar_url: string; +} + +export const createEmptyMember = () : MemberEntity => ({ + id: -1, + login: "", + avatar_url: "" +}); diff --git a/12_TableHttp_Axios/src/model/memberMockData.ts b/12_TableHttp_Axios/src/model/memberMockData.ts new file mode 100644 index 0000000..8a2846a --- /dev/null +++ b/12_TableHttp_Axios/src/model/memberMockData.ts @@ -0,0 +1,17 @@ +import {MemberEntity} from './member'; + +var MembersMockData : MemberEntity[] = + [ + { + id: 1457912, + login: "brauliodiez", + avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3" + }, + { + id: 4374977, + login: "Nasdan", + avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3" + } + ]; + +export default MembersMockData; diff --git a/12_TableHttp_Axios/src/nameEdit.tsx b/12_TableHttp_Axios/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/12_TableHttp_Axios/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/12_TableHttp_Axios/tsconfig.json b/12_TableHttp_Axios/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/12_TableHttp_Axios/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/12_TableHttp_Axios/webpack.config.js b/12_TableHttp_Axios/webpack.config.js new file mode 100644 index 0000000..f665ad4 --- /dev/null +++ b/12_TableHttp_Axios/webpack.config.js @@ -0,0 +1,62 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, 'src'), + resolve: { + extensions: ['.js', '.ts', '.tsx'], + }, + entry: ['@babel/polyfill', './main.tsx'], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js', + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only', + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + babelCore: '@babel/core', // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]', + }, + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: '[name].css', + chunkFilename: '[id].css', + }), + ], +}; diff --git a/13 ShouldUpdate/.babelrc b/13 ShouldUpdate/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/13 ShouldUpdate/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/13 ShouldUpdate/package.json b/13 ShouldUpdate/package.json deleted file mode 100644 index 9929f76..0000000 --- a/13 ShouldUpdate/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "Callback.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server --inline", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0" - }, - "devDependencies": { - "awesome-typescript-loader": "^3.4.1", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/13 ShouldUpdate/src/face.tsx b/13 ShouldUpdate/src/face.tsx deleted file mode 100644 index efaad85..0000000 --- a/13 ShouldUpdate/src/face.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as React from 'react'; - -interface Props { - level : number; -} - -export class FaceComponent extends React.Component { - - setSatisfactionClass(level : number) { - if(level < 100) { - return "very-dissatisfied" - } - - if(level < 200) { - return "somewhat-dissatisfied" - } - - if(level < 300) { - return "neither" - } - - if(level < 400) { - return "somewhat-satisfied" - } - - return "very-satisfied" - } - - shouldComponentUpdate(nextProps : Props, nextState) - { - const rangeChange = [100, 200, 300, 400]; - - let index = 0; - let isRangeChange = false; - - while(!isRangeChange && index < rangeChange.length) { - isRangeChange = (this.props.level < rangeChange[index] && nextProps.level >= rangeChange[index]) - || - (this.props.level > rangeChange[index] && nextProps.level <= rangeChange[index]) - ; - - index++; - } - - return isRangeChange; - } - - render() { - return ( -
- ); - } -} diff --git a/13 ShouldUpdate/src/index.html b/13 ShouldUpdate/src/index.html deleted file mode 100644 index ffbfcc1..0000000 --- a/13 ShouldUpdate/src/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Sample 03: State management - - -

Sample 03: State management

-
- - diff --git a/13 ShouldUpdate/src/main.tsx b/13 ShouldUpdate/src/main.tsx deleted file mode 100644 index 58b045e..0000000 --- a/13 ShouldUpdate/src/main.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import {App} from './app'; - -ReactDOM.render( - - , document.getElementById('root')); diff --git a/13 ShouldUpdate/tsconfig.json b/13 ShouldUpdate/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/13 ShouldUpdate/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/13 ShouldUpdate/webpack.config.js b/13 ShouldUpdate/webpack.config.js deleted file mode 100644 index 9b5c177..0000000 --- a/13 ShouldUpdate/webpack.config.js +++ /dev/null @@ -1,91 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css', - './content/site.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options:{ - useBabel: true, - }, - }, - { - test: /\.css$/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - { - test: /\.(png|jpg)$/, - exclude: /node_modules/, - loader: 'url-loader?limit=10000' - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/13_ShouldUpdate/.babelrc b/13_ShouldUpdate/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/13_ShouldUpdate/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/13_ShouldUpdate/package.json b/13_ShouldUpdate/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/13_ShouldUpdate/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/13 ShouldUpdate/readme.md b/13_ShouldUpdate/readme.md similarity index 68% rename from 13 ShouldUpdate/readme.md rename to 13_ShouldUpdate/readme.md index a7d01c6..cb9572d 100644 --- a/13 ShouldUpdate/readme.md +++ b/13_ShouldUpdate/readme.md @@ -9,14 +9,13 @@ the value jumps into the next or previous range. We will take a startup point sample _03 State_: -Summary steps: +## Summary steps: - Remove _hello_ and _nameEdit_ components (app cleanup). - Copy under dir _content_ the four png's that contain the simleys. - Create under dir _content_ a _site.css_ file and define stlyes for the smileys. - Create a smily component. -- Add to app state a currenValue entry, pass it to the control plus add an slider -to configure it. +- Add to app state a currenValue entry, pass it to the control plus, add an slider to configure it. - Let's add an optimization... componentshouldupdate. ## Prerequisites @@ -33,7 +32,7 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are npm install ``` -- Remove _ +- Let's make a cleanup on _app.tsx_ _./src/app.tsx_ @@ -99,32 +98,21 @@ _./src/content/site.css_ } ``` +- You can copy this images into the _content_ folde, url where you can download them: https://github.com/Lemoncode/react-by-sample/tree/master/13%20ShouldUpdate/src/content + - In _webpack.config.js_ let's add the new _css_ file as entry point: _webpack.config.js_ ```diff entry: [ + '@babel/polyfill', './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css', + './content/site.css' ], ``` -- We need to add as well a loader to handle images in _webpackconfig.js_ (add it if it is not defined on webpack config yet): - -_webpack.config.js_ - -```javascript -{ - test: /\.(png|jpg)$/, - exclude: /node_modules/, - loader: 'url-loader?limit=10000' -}, -``` - -- Let's create a simple _faceComponent_ under _src_, we will start by just adding -something hardcoded in file _src/face.tsx_: +- Let's create a simple _faceComponent_ under _src_, we will start by just adding something hardcoded in file _src/face.tsx_: _./src/face.tsx_ @@ -144,7 +132,7 @@ _./src/app.tsx_ ```diff import * as React from 'react'; -+ import {FaceComponent} from './face'; ++ import { FaceComponent } from './face'; interface Props { } @@ -173,8 +161,9 @@ export class App extends React.Component { npm start ``` -- Now it's time to link the property with the proper faces, let's create a style function -for that in _face.tsx_ +- Now it's time to link the property with the proper faces, let's create a style function for that in _face.tsx_ + +_./src/face.tsx_ ```diff import * as React from 'react'; @@ -211,37 +200,41 @@ export const FaceComponent = (props : {level : number}) => { - In _app.tsx_ let's add a state variable to hold the current satisfaction level plus an slider to let the user update it. -```jsx +_./src/app.tsx_ + +```diff import * as React from 'react'; -import {FaceComponent} from './face' +import { FaceComponent } from './face' interface Props { } -interface State { - satisfactionLevel : number; -} ++ interface State { ++ satisfactionLevel : number; ++ } export class App extends React.Component { constructor(props: Props) { super(props); - this.state = {satisfactionLevel: 300}; ++ this.state = {satisfactionLevel: 300}; } public render() { return (
- this.setState({satisfactionLevel :event.target.value} as State)} - /> -
- {this.state.satisfactionLevel} -
- ++ this.setState( ++ {satisfactionLevel:+event.target.value})} ++ /> ++
++ {this.state.satisfactionLevel} ++
+- ++
); } @@ -255,67 +248,42 @@ export class App extends React.Component { ``` - Let's add a rendering optimization, we should only trigger the render whenever -the level just changes the satisfaction range, we need to move the component to -state component: +the level just changes the satisfaction range, we need to move the component to state component: -```jsx -import * as React from 'react'; - -interface Props { - level : number; -} - -export class FaceComponent extends React.Component { - - setSatisfactionClass(level : number) { - if(level < 100) { - return "very-dissatisfied" - } - - if(level < 200) { - return "somewhat-dissatisfied" - } - - if(level < 300) { - return "neither" - } - - if(level < 400) { - return "somewhat-satisfied" - } - - return "very-satisfied" - } +_./src/face.tsx_ - shouldComponentUpdate(nextProps : Props, nextState) - { - const rangeChange = [100, 200, 300, 400]; +```diff +import * as React from 'react'; - let index = 0; - let isRangeChange = false; ++ interface Props { ++ level : number; ++ } - while(!isRangeChange && index < rangeChange.length) { - isRangeChange = (this.props.level < rangeChange[index] && nextProps.level >= rangeChange[index]) - || - (this.props.level > rangeChange[index] && nextProps.level <= rangeChange[index]) - ; ++ const isRangeChange = (oldValue : number, newValue : number) => { ++ const oldValueClass = setSatisfactionClass(oldValue); ++ const newValueClass = setSatisfactionClass(newValue); ++ ++ return oldValueClass !== newValueClass; ++ } - index++; - } ++ export class FaceComponent extends React.Component { +- export const FaceComponent = (props: { level: number }) => { - return isRangeChange; - } ++ shouldComponentUpdate(nextProps : Props, nextState) ++ { ++ return isRangeChange(this.props.level, nextProps.level); ++ } - render() { ++ render() { return ( -
+-
++
); - } ++ } } ``` -> Excercise there's an easier way to implement the same algorithm in the -should component update. +> Excercise there's an easier way to implement the same algorithm in the should component update. - Now if we place a breakpoint in the faceComponent render method we can see that render is only triggered when you change from a satisfaction range (e.g. 99 to 100). diff --git a/13_ShouldUpdate/readme_es.md b/13_ShouldUpdate/readme_es.md new file mode 100644 index 0000000..7b3fbae --- /dev/null +++ b/13_ShouldUpdate/readme_es.md @@ -0,0 +1,285 @@ +## 13 ShouldUpdate + +En esta muestra mejoraremos el rendimiento con 'shouldComponentUpdate'. + +Vamos a implementar un widget de satisfacción de cliente, basado en una cara sonriente, aceptará un rango de valores (de 0 a 500), y la cara tiene un rango de valores de 0..100, 100..200, 200..300, 300..400, 400..500. Nosotros solo dispararemos la opción de renderizado cuando el valor salte dentro del rango anterior o posterior. + +Tomaremos como punto de entrada la muestra _03 State_: + +## Pasos resumidos: + +- Eliminar los componentes _hello_ y _nameEdit_ (limpiar app). +- Copiar dentro del directorio _content_ los cuatro png que contienen los emoticonos. +- Crear dentro del directorio _content_ un fichero _site.css_ y definir estilos para los emoticonos. +- Crear un componente emoticono. +- Añadir a app un estaddo currenValue, pasando esto a un control mas, añadir un slider para configurarlo. +- Añadir una optimización... componentshouldupdate. + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o más nuevo) si no están ya instalados. + +> Verificar que tienes al menos corriendo la versión de node v6.x.x y npm 3.x.x ejecutando `node -v` y `npm -v` en la terminal de Windows. Versiones más antiguas pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _03 State_ y ejecútalo: + +``` +npm install +``` + +- Hagamos una limpieza en _app.tsx_ + +_./src/app.tsx_ + +```jsx +import * as React from 'react'; + +interface Props { +} + +interface State { +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + } + + public render() { + return ( +
+
+ ); + } +} +``` + +- Creemos un fichero _src/content_ y copia los cinco emoticones (puedes copiarlo de la implementación en github). + +- Vamos a crear un fichero css: _src/content/site.css_ y añade los estilos a los emoticonos: + +_./src/content/site.css_ + +```css +.very-dissatisfied { + width:100%; + height:80px; + background:url('./one.png') no-repeat;; +} + +.somewhat-dissatisfied { + width:100%; + height:80px; + background:url('./two.png') no-repeat; +} + +.neither { + width:100%; + height:80px; + background:url('./three.png') no-repeat; +} + +.somewhat-satisfied { + width:100%; + height:80px; + background:url('./four.png') no-repeat; +} + +.very-satisfied { + width:100%; + height:80px; + background:url('./five.png') no-repeat; +} +``` + +- Puedes copiar estas imágenes dentro de la carpeta _content_ la url donde puedes descargartelos: https://github.com/Lemoncode/react-by-sample/tree/master/13%20ShouldUpdate/src/content + +- En _webpack.config.js_ añadamos un nuevo fichero _css_ como punto de entrada: + +_webpack.config.js_ + +```diff +entry: [ + '@babel/polyfill', + './main.tsx', ++ './content/site.css' +], +``` + +- Vamos a crear un simple _faceComponent_ bajo _src_, nosotros empezaremos por añadir algunos codificados en el fichero _src/face.tsx_: + +_./src/face.tsx_ + +```jsx +import * as React from 'react'; + +export const FaceComponent = (props : {level : number}) => { + return ( +
+ ); +} +``` + +Hagamos una prueba rápida en _app.tsx_ + +_./src/app.tsx_ + +```diff +import * as React from 'react'; ++ import { FaceComponent } from './face'; + +interface Props { +} + +interface State { +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + } + + public render() { + return ( +
++ +
+ ); + } +} +``` + +- Hagamos un punto de guardado y ejecutemos la muestra: comprobemos que está funcionando como esperamos. + +``` +npm start +``` + +- Ahora es hora de unir la propiedad con la expresión adecuada, vamos a crear un función que de estilo en _face.tsx_ + +_./src/face.tsx_ + +```diff +import * as React from 'react'; + ++ const setSatisfactionClass = (level : number) => { ++ if(level < 100) { ++ return "very-dissatisfied" ++ } ++ ++ if(level < 200) { ++ return "somewhat-dissatisfied" ++ } ++ ++ if(level < 300) { ++ return "neither" ++ } ++ ++ if(level < 400) { ++ return "somewhat-satisfied" ++ } ++ ++ return "very-satisfied" ++} + +export const FaceComponent = (props : {level : number}) => { + return ( +-
++
+ ); +} +``` + +- En _app.tsx_ añadamos un estado que mantenga el grado de satisfacción actual más un control deslizante para que el usuario lo actualize. + +_./src/app.tsx_ + +```diff +import * as React from 'react'; +import { FaceComponent } from './face' + +interface Props { +} + ++ interface State { ++ satisfactionLevel : number; ++ } + +export class App extends React.Component { + constructor(props: Props) { + super(props); + ++ this.state = {satisfactionLevel: 300}; + } + + public render() { + return ( +
++ this.setState( ++ {satisfactionLevel:event.target.value})} ++ /> ++
++ {this.state.satisfactionLevel} ++
+- ++ +
+ ); + } +} +``` + +- Vamos a ejecutar la muestra + + ``` + npm start + ``` + + - Añadamos una optimización de renderización, nosotros deberíamos solo lanzar el renderizado cuando el nivel de satisfacción cambie, necesitamos mover el componente a un componente estado: + + _./src/face.tsx_ + +```diff +import * as React from 'react'; + ++ interface Props { ++ level : number; ++ } + ++ const isRangeChange = (oldValue : number, newValue : number) => { ++ const oldValueClass = setSatisfactionClass(oldValue); ++ const newValueClass = setSatisfactionClass(newValue); ++ ++ return oldValueClass !== newValueClass; ++ } + ++ export class FaceComponent extends React.Component { +- export const FaceComponent = (props: { level: number }) => { + ++ shouldComponentUpdate(nextProps : Props, nextState) ++ { ++ return isRangeChange(this.props.level, nextProps.level); ++ } + ++ render() { + return ( +-
++
+ ); ++ } +} +``` + +> Hay una manera más fácil de implementar el mismo algoritmo en el componente debería actualizarse. + +- Ahora si nosotros colocamos un punto de interrupción en el método de renderizado de faceComponent podemos ver que el renderizado solo se lanza cuando cambia a un rango positivo (ejemplo de 99 a 100). + +``` +npm start +``` \ No newline at end of file diff --git a/13 ShouldUpdate/src/app.tsx b/13_ShouldUpdate/src/app.tsx similarity index 95% rename from 13 ShouldUpdate/src/app.tsx rename to 13_ShouldUpdate/src/app.tsx index 60590c5..7902266 100644 --- a/13 ShouldUpdate/src/app.tsx +++ b/13_ShouldUpdate/src/app.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {FaceComponent} from './face' +import {FaceComponent} from './face'; interface Props { } diff --git a/13 ShouldUpdate/src/content/five.png b/13_ShouldUpdate/src/content/five.png similarity index 100% rename from 13 ShouldUpdate/src/content/five.png rename to 13_ShouldUpdate/src/content/five.png diff --git a/13 ShouldUpdate/src/content/four.png b/13_ShouldUpdate/src/content/four.png similarity index 100% rename from 13 ShouldUpdate/src/content/four.png rename to 13_ShouldUpdate/src/content/four.png diff --git a/13 ShouldUpdate/src/content/one.png b/13_ShouldUpdate/src/content/one.png similarity index 100% rename from 13 ShouldUpdate/src/content/one.png rename to 13_ShouldUpdate/src/content/one.png diff --git a/13 ShouldUpdate/src/content/site.css b/13_ShouldUpdate/src/content/site.css similarity index 100% rename from 13 ShouldUpdate/src/content/site.css rename to 13_ShouldUpdate/src/content/site.css diff --git a/13 ShouldUpdate/src/content/three.png b/13_ShouldUpdate/src/content/three.png similarity index 100% rename from 13 ShouldUpdate/src/content/three.png rename to 13_ShouldUpdate/src/content/three.png diff --git a/13 ShouldUpdate/src/content/two.png b/13_ShouldUpdate/src/content/two.png similarity index 100% rename from 13 ShouldUpdate/src/content/two.png rename to 13_ShouldUpdate/src/content/two.png diff --git a/13_ShouldUpdate/src/face.tsx b/13_ShouldUpdate/src/face.tsx new file mode 100644 index 0000000..16e181e --- /dev/null +++ b/13_ShouldUpdate/src/face.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; + +const setSatisfactionClass = (level: number) => { + if (level < 100) { + return "very-dissatisfied" + } + + if (level < 200) { + return "somewhat-dissatisfied" + } + + if (level < 300) { + return "neither" + } + + if (level < 400) { + return "somewhat-satisfied" + } + + return "very-satisfied" +} + +const isRangeChange = (oldValue: number, newValue: number) => { + const oldValueClass = setSatisfactionClass(oldValue); + const newValueClass = setSatisfactionClass(newValue); + + return oldValueClass !== newValueClass; +} + +interface Props { + level: number; +} + +export class FaceComponent extends React.Component { + shouldComponentUpdate(nextProps: Props, nextState) { + return isRangeChange(this.props.level, nextProps.level); + } + + render() { + return ( +
+ ); + } +} diff --git a/13_ShouldUpdate/src/hello.tsx b/13_ShouldUpdate/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/13_ShouldUpdate/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

Hello user: {props.userName} !

+ ); +} diff --git a/13_ShouldUpdate/src/index.html b/13_ShouldUpdate/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/13_ShouldUpdate/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/13_ShouldUpdate/src/main.tsx b/13_ShouldUpdate/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/13_ShouldUpdate/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/13_ShouldUpdate/src/nameEdit.tsx b/13_ShouldUpdate/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/13_ShouldUpdate/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/13_ShouldUpdate/tsconfig.json b/13_ShouldUpdate/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/13_ShouldUpdate/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/13_ShouldUpdate/webpack.config.js b/13_ShouldUpdate/webpack.config.js new file mode 100644 index 0000000..f103c2c --- /dev/null +++ b/13_ShouldUpdate/webpack.config.js @@ -0,0 +1,65 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx', + './content/site.css', + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/14 ReactRouter/.babelrc b/14 ReactRouter/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/14 ReactRouter/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/14 ReactRouter/package.json b/14 ReactRouter/package.json deleted file mode 100644 index 0614095..0000000 --- a/14 ReactRouter/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "State basics.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0", - "react-router-dom": "^4.2.2" - }, - "devDependencies": { - "@types/history": "^4.6.2", - "@types/react-router-dom": "^4.2.3", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "awesome-typescript-loader": "^3.4.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/14 ReactRouter/src/index.html b/14 ReactRouter/src/index.html deleted file mode 100644 index d972c34..0000000 --- a/14 ReactRouter/src/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Sample 14: ReactRouter - - -

Sample 14: ReactRouter

-
- - diff --git a/14 ReactRouter/src/main.tsx b/14 ReactRouter/src/main.tsx deleted file mode 100644 index b99dbbb..0000000 --- a/14 ReactRouter/src/main.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { HashRouter, Switch, Route } from 'react-router-dom'; -import { PageA } from './pageA'; -import { PageB } from './pageB'; - -ReactDOM.render( - - - - - - - , document.getElementById('root') -); diff --git a/14 ReactRouter/tsconfig.json b/14 ReactRouter/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/14 ReactRouter/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/14 ReactRouter/webpack.config.js b/14 ReactRouter/webpack.config.js deleted file mode 100644 index 9b58da0..0000000 --- a/14 ReactRouter/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options: { - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/14_ReactRouter/.babelrc b/14_ReactRouter/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/14_ReactRouter/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/14_ReactRouter/package.json b/14_ReactRouter/package.json new file mode 100644 index 0000000..7ee65fc --- /dev/null +++ b/14_ReactRouter/package.json @@ -0,0 +1,39 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-router-dom": "^4.3.1" + } +} diff --git a/14 ReactRouter/readme.md b/14_ReactRouter/readme.md similarity index 67% rename from 14 ReactRouter/readme.md rename to 14_ReactRouter/readme.md index 9a7be6a..00b410f 100644 --- a/14 ReactRouter/readme.md +++ b/14_ReactRouter/readme.md @@ -2,20 +2,18 @@ In this sample we will start using React-Router (SPA navigation). -In this case we will provide a default `userName` but let the user update -it. +In this case we will provide a default `userName` but let the user update it. +We will take a startup point sample _[03 State](./../03%20State)_: -We will take a startup point sample _03 State_: - -Summary steps: +## Summary steps: - Let's make first some cleanup: remove _hello.tsx_ and _nameEdit.tsx_ -- Let's create two components _PageA_ and _PageB_ +- Let's create two components _[PageA.tsx](./src/pageA.tsx)_ and _[PageB.tsx](./src/pageB.tsx)_ - Let's install the dependencies to _react-router-dom_ and typescript definitions for this. - Let's define the routing. -- Let's define a navigation from _PageA_ to _PageB_. -- Let's define a navigation from _PageB_ to _PageA_. +- Let's define a navigation from _[PageA.tsx](./src/pageA.tsx)_ to _[PageB.tsx](./src/pageB.tsx)_. +- Let's define a navigation from _[PageB.tsx](./src/pageB.tsx)_ to _[PageA.tsx](./src/pageA.tsx)_. ## Prerequisites @@ -25,7 +23,7 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are ## Steps to build it -- Copy the content from _03 State_ and execute: +- Copy the content from _[03 State](./../03%20State)_ and execute: ``` npm install @@ -35,54 +33,49 @@ Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0 or newer) if they are - Let's create a component called _PageA_ as _src/pageA.tsx_: -### ./src/pageA.tsx +_./src/pageA.tsx_ ```jsx import * as React from "react" -export const PageA = () => { - return ( +export const PageA = () =>

Hello from page A

- ) -} ``` -### ./src/pageB.tsx - - Let's create a component called _PageB_ as _src/pageB.tsx_: +_./src/pageB.tsx_ + ```jsx import * as React from "react" -export const PageB = () => { - return ( +export const PageB = () =>

Hello from page B

- ) -} ``` - Let's install the dependencies [`react-router-dom`](https://github.com/ReactTraining/react-router) and typescript definitions for this. ```bash npm install react-router-dom --save -npm install @types/react-router-dom --save-dev +npm install @types/react-router-dom --save-dev ``` - Let's define the routing in _main.tsx_: -### ./src/main.tsx +_./src/main.tsx_ ```diff import * as React from 'react'; import * as ReactDOM from 'react-dom'; -- import {App} from './app'; +- import { App } from './app'; +- import { HelloComponent } from './hello'; + import { HashRouter, Switch, Route } from 'react-router-dom'; -+ import {PageA} from './pageA'; -+ import {PageB} from './pageB'; ++ import { PageA } from './pageA'; ++ import { PageB } from './pageB'; ReactDOM.render( - @@ -91,8 +84,8 @@ ReactDOM.render( + + + -+ - , document.getElementById('root') ++ , +document.getElementById('root') ); ``` @@ -103,42 +96,36 @@ ReactDOM.render( npm start ``` -- Let's define a navigation from PageA to PageB (_src/pageA.tsx_). +- Let's define a navigation from _[PageA.tsx](./src/pageA.tsx)_ to _[PageB.tsx](./src/pageB.tsx)_. -### ./src/pageA.tsx +_./src/pageA.tsx_ ```diff import * as React from "react" + import { Link } from 'react-router-dom'; -export const PageA = () => { - return ( +export const PageA = () =>

Hello from page A

+
+ Navigate to Page B
- ) -} - ``` -- Let's define a navigation from PageB to PageA (_pageA.tsx_) +- Let's define a navigation from _[PageB.tsx](./src/pageB.tsx)_ to _[PageA.tsx](./src/pageA.tsx)_ + +_./src/pageB.tsx_ ```diff import * as React from "react" + import { Link } from 'react-router-dom'; -export const PageB = () => { - return ( +export const PageB = () =>

Hello from page B

+
+ Navigate to Page A
- ) -} - ``` diff --git a/14_ReactRouter/readme_es.md b/14_ReactRouter/readme_es.md new file mode 100644 index 0000000..4725b7c --- /dev/null +++ b/14_ReactRouter/readme_es.md @@ -0,0 +1,134 @@ +# 14 ReactRouter + +En este ejemplo comenzaremos a usar React-Router (navegación SPA). + +En este caso proporcionaremos un `userName` por defecto pero dejaremos actualizarlo al usuario. + +Tomaremos como punto de partida el ejemplo _[03 State](./../03%20State)_ + +## Resumen de pasos: + +- Primero vamos a hacer un poco de limpieza: eliminamos _hello.tsx_ y _nameEdit.tsx_ +- Vamos a crear dos componentes: _[PageA.tsx](./src/pageA.tsx)_ y _[PageB.tsx](./src/pageB.tsx)_ +- Vamos a instalar las dependencias a _react-router-dom_ y sus definiciones para typescript. +- Vamos a definir el enrutado. +- Vamos a definir la navegación de _[PageA.tsx](./src/pageA.tsx)_ a _[PageB.tsx](./src/pageB.tsx)_ +- Vamos a definir la navegación de _[PageB.tsx](./src/pageB.tsx)_ a _[PageA.tsx](./src/pageA.tsx)_ + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en un terminal/consola. Versiones anteriores pueden producir errores. + +## Pasos para construirlo + +Copia el contenido de _[03 State](./../03%20State)_ y ejecuta: + + ``` + npm install + ``` + +- Vamos a hacer algo de limpieza (eliminar los archivos _src/hello.tsx_ y _src/nameEdit.tsx_). + +- Vamos a crear un componente llamado _PageA_ como _src/pageA.tsx_: + +_./src/pageA.tsx_ + +```jsx +import * as React from "react" + +export const PageA = () => +
+

Hello from page A

+
+``` + +- Vamos a crear un componente llamado _PageB_ como _src/pageB.tsx_: + +_./src/pageB.tsx_ + +```jsx +import * as React from "react" + +export const PageB = () => +
+

Hello from page B

+
+``` + +- Vamos a instalar las dependencias [`react-router-dom`](https://github.com/ReactTraining/react-router) y sus definiciones para typescript. + +```bash +npm install react-router-dom --save +npm install @types/react-router-dom --save-dev +``` + +- Vamos a definir el enrutado en _main.tsx_: + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +- import { App } from './app'; +- import { HelloComponent } from './hello'; ++ import { HashRouter, Switch, Route } from 'react-router-dom'; ++ import { PageA } from './pageA'; ++ import { PageB } from './pageB'; + +ReactDOM.render( +- ++ ++ ++ ++ ++ ++ , +document.getElementById('root') +); +``` + +- Es hora de verificar que estamos siguiendo el camino correcto: + +```bash +npm start +``` + +- Vamos a definir la navegación de _[PageA.tsx](./src/pageA.tsx)_ a _[PageB.tsx](./src/pageB.tsx)_. + +_./src/pageA.tsx_ + +```diff +import * as React from "react" ++ import { Link } from 'react-router-dom'; + +export const PageA = () => +
+

Hello from page A

++
++ Navigate to Page B +
+``` + +- Vamos a definir la navegación de _[PageB.tsx](./src/pageB.tsx)_ a _[PageA.tsx](./src/pageA.tsx)_ + +_./src/pageB.tsx_ + +```diff +import * as React from "react" ++ import { Link } from 'react-router-dom'; + +export const PageB = () => +
+

Hello from page B

++
++ Navigate to Page A +
+``` + +- Ejecutamos la aplicación y comprobamos que la navegación funciona correctamente. + +```bash +npm start +``` \ No newline at end of file diff --git a/14_ReactRouter/src/app.tsx b/14_ReactRouter/src/app.tsx new file mode 100644 index 0000000..055eced --- /dev/null +++ b/14_ReactRouter/src/app.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + + + ); + } +} \ No newline at end of file diff --git a/14_ReactRouter/src/hello.tsx b/14_ReactRouter/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/14_ReactRouter/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

Hello user: {props.userName} !

+ ); +} diff --git a/14_ReactRouter/src/index.html b/14_ReactRouter/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/14_ReactRouter/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
+

Sample app

+
+
+ + diff --git a/14_ReactRouter/src/main.tsx b/14_ReactRouter/src/main.tsx new file mode 100644 index 0000000..3701e3d --- /dev/null +++ b/14_ReactRouter/src/main.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import {PageA} from './pageA'; +import {PageB} from './pageB'; + +ReactDOM.render( + + + + + + + ,document.getElementById('root') +); diff --git a/14_ReactRouter/src/nameEdit.tsx b/14_ReactRouter/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/14_ReactRouter/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/14 ReactRouter/src/pageA.tsx b/14_ReactRouter/src/pageA.tsx similarity index 80% rename from 14 ReactRouter/src/pageA.tsx rename to 14_ReactRouter/src/pageA.tsx index e415fdd..10b6b62 100644 --- a/14 ReactRouter/src/pageA.tsx +++ b/14_ReactRouter/src/pageA.tsx @@ -1,12 +1,9 @@ import * as React from "react" import { Link } from 'react-router-dom'; -export const PageA = () => { - return ( +export const PageA = () =>

Hello from page A


Navigate to Page B
- ) -} diff --git a/14 ReactRouter/src/pageB.tsx b/14_ReactRouter/src/pageB.tsx similarity index 80% rename from 14 ReactRouter/src/pageB.tsx rename to 14_ReactRouter/src/pageB.tsx index ece3ee7..241c19d 100644 --- a/14 ReactRouter/src/pageB.tsx +++ b/14_ReactRouter/src/pageB.tsx @@ -1,12 +1,9 @@ import * as React from "react" import { Link } from 'react-router-dom'; -export const PageB = () => { - return ( +export const PageB = () =>

Hello from page B


Navigate to Page A
- ) -} diff --git a/14_ReactRouter/tsconfig.json b/14_ReactRouter/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/14_ReactRouter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/14_ReactRouter/webpack.config.js b/14_ReactRouter/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/14_ReactRouter/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/15 LoginForm/.babelrc b/15 LoginForm/.babelrc deleted file mode 100644 index 911d8c1..0000000 --- a/15 LoginForm/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false - } - ] - ] -} \ No newline at end of file diff --git a/15 LoginForm/package.json b/15 LoginForm/package.json deleted file mode 100644 index 0614095..0000000 --- a/15 LoginForm/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "helloworld-react", - "version": "1.0.0", - "description": "State basics.", - "main": "index.js", - "scripts": { - "start": "webpack-dev-server", - "build": "webpack" - }, - "author": "Lemoncode and Front End Master Students", - "license": "MIT", - "dependencies": { - "bootstrap": "~4.0.0", - "react": "~16.2.0", - "react-dom": "~16.2.0", - "react-router-dom": "^4.2.2" - }, - "devDependencies": { - "@types/history": "^4.6.2", - "@types/react-router-dom": "^4.2.3", - "@types/react": "~16.0.36", - "@types/react-dom": "~16.0.3", - "babel-core": "^6.26.0", - "babel-preset-env": "^1.6.1", - "awesome-typescript-loader": "^3.4.1", - "css-loader": "~0.28.9", - "extract-text-webpack-plugin": "^3.0.2", - "file-loader": "~1.1.6", - "html-webpack-plugin": "~2.30.1", - "style-loader": "~0.20.1", - "typescript": "~2.7.1", - "url-loader": "~0.6.2", - "webpack": "~3.10.0", - "webpack-dev-server": "^2.11.1" - } -} diff --git a/15 LoginForm/readme.md b/15 LoginForm/readme.md deleted file mode 100644 index 7d8ffba..0000000 --- a/15 LoginForm/readme.md +++ /dev/null @@ -1,808 +0,0 @@ -# 15 Login form - -In this sample we are going to implement a basic login page, that will redirect -the user to another page whenever the login has completed successfully. - -We will attempt to create a [realistic layout](http://bootsnipp.com/snippets/featured/compact-login-form-bs-3), in order to keep simplicity we will -break it into subcomponents and perform some refactor in order to make the solution -more maintenable. - -We will take a startup point sample _14 ReactRouter_: - -Summary steps: - -- Let's rename pageA to LoginPage. -- Let's create a 'Pages' subfolder and reorganize pages. -- Let's build the layout for this page. -- Let's add navigation on login button clicked. -- Let's add login validation fake api. -- Let's connect it into the login button logic. -- Let's do a bit of refactor and clean up extracting functionality to reusable components. -- Let's add some form validation (mandatory fields, minlength). - -## Prerequisites - -Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. - -> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. - -## Steps to build it - -- Copy the content from _14 React Router_ and execute _npm install_. - -- Let's rename _pageA.tsx_ to _loginPage.tsx_. - -- Let's update as well the name of the component. - -_./src/loginPage.tsx_ - -```javascript -import * as React from "react" -import { Link } from 'react-router-dom'; - -export const LoginPage = () => { - return ( -
-

Hello from page A

-
- Navigate to Page B -
- ) -} -``` - -- Now it's time to reorganize the pages structure. Let's create a subfolders -called _pages_ - -- Under that subfolder let's create two more subfolders _login_ and _b_ - -- Let's place the _pages_ under that subfolders: _pages/login/loginPage.tsx_ -and _pages/b/pageB. - -``` -. -└── src/ - │ - ├── model/ - └── pages/ - ├── login/ - │ └── loginPage.tsx - └── b/ - └── pageB.tsx - -``` - -- In some cases this pages will contain more secondary files, let's create -a simple _index.tsx_ file for each of this pages. - -- Under _pages/login/index.ts. - -_./src/pages/login/index.ts_ - -```javascript -export {LoginPage} from './loginPage'; -``` - -- Under _pages/b/index.ts_ - -_./src/pages/b/index.ts_ - -```javascript -export {PageB} from './pageB' -``` -- The structure look like this: - -``` -. -└── src/ - │ - └── pages/ - ├── login/ - │ ├── index.ts - │ └── loginPage.tsx - └── b/ - ├── index.ts - └── pageB.tsx - -``` - - -- Let's update _main.tsx_ (routes, names and add a redirect from root to login page) - -_./src/main.tsx_ - -```diff -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { HashRouter, Switch, Route } from 'react-router-dom'; -- import {PageA} from './pageA'; -- import {PageB} from './pageB'; -+ import {LoginPage} from './pages/login'; -+ import {PageB} from './pages/b'; - -ReactDOM.render( - - -- -+ - - - - , - document.getElementById('root') -); -``` - -- Let's update as well the navigation from _pageB_ to _loginPage_, _pageB.tsx_ - -_./src/pages/b/pageB.tsx_ - -```diff -import * as React from "react" -import { Link } from 'react-router-dom'; - -export const PageB = () => { - return ( -
-

Hello from page B

-
-- Navigate to Page A -+ Navigate to Login -
- ) -} -``` - -- Let's make a quick test and check that everyting is still working fine. - -``` -npm start -``` - -- Let's build a proper _login_ layout, _loginPage.tsx_, we will base it in -the [following layout](http://bootsnipp.com/snippets/featured/compact-login-form-bs-3) -but we will break it down into subcomponents. - -- It's time to create something reusable, a panel is a good candidate for that. - -Our idea is to end up with something like: -**PSEUDCODE** - -```html - - // My content here - -``` -And the panel it self - -```html -
-
- - {children} - -
-``` - -> Panels and Bootstrap 4 (cards): https://getbootstrap.com/docs/4.0/components/card/ - -_./src/common/panel/components/header.tsx_ -```tsx -import * as React from "react" - -interface Props { - title : string; -} - -export const Header = (props : Props) => -
  • -

    {props.title}

    -
  • -``` - -_./src/common/panel/components/body.tsx_ - -```tsx -import * as React from "react" - -interface Props { -} - -export const Body : React.StatelessComponent = (props) => -
    - {props.children} -
    -``` - - -_./src/common/panel/panel.tsx_ - -```tsx -import * as React from "react" -import {Body} from './components/body'; -import {Header} from './components/header'; - -interface Props { - title : string; -} - -export const Panel : React.StatelessComponent = (props) => -
    -
      -
      - - {props.children} - -
    -
    -``` - -_./src/common/panel/index.ts_ - -```typescript -export {Panel} from './panel'; -``` - -> How do we end up doing this nice common component? Just by first doing it in the non optimal -way and go refining / refactoring, ... not often you get the perfect design at first try, it's -an iterative process. - -- Let's create one more index file for the _common_ folder (that will grow). - -_./src/common/index.ts_ - -```typescript -export {Panel} from './panel'; -``` - -- Let's jump back into our login pages, let's create a form component - -_./src/pages/login/components/form.tsx_ - -```tsx -import * as React from "react" - -export const Form = () => { - return ( -
    -
    -
    - -
    -
    - -
    - -
    -
    - ); -} -``` - -- Now let's update our login page (fully replace the content) - -_./src/pages/login/loginPage.tsx_ - -```javascript -import * as React from "react" -import {Panel} from '../../common'; -import {Form} from './components/form'; - -export const LoginPage = () => - -
    - -``` - -- Let's give a try and check how is it looking. - -```bash -npm start -``` - -- - -- Let's add the navigation on button clicked (later on we will check for user and pwd) _form.tsx_. -In order to do this we will use react-router 4 "withRouter" HoC (High order component). - -_./src/pages/login/form.tsx_ - -```diff -import * as React from "react" -+ import { withRouter } from 'react-router-dom'; - - -- export const Form = () => { -+ export const Form = withRouter(({history}) => { -+ const login = () => { -+ history.push('/pageB'); -+ } - - return ( -
    - -
    -
    - -
    -
    - -
    -- -+ -
    - -
    - ); -- } -+}) -``` - -- Let's give a quick try. - -```bash -npm start -``` - -- Not bad, but we rather prefer to center the dialog, instead of getting our control dirty with -bootstrap grid sizes let's create another component. - -> To make this component fully reusable it will need some tweaking, as an excercise you can play with -it adding some props to make it generic. - -_./src/common/content-center/content-center.tsx_ - -```tsx -import * as React from "react" - -export const ContentCenter : React.StatelessComponent = (props) => -
    -
    -
    - {props.children} -
    -
    -
    -``` - -- Let's add it to our common _index.ts_ - -_./src/common/index.ts_ - -```diff -export {Panel} from './panel'; -+ export {ContentCenter} from './content-center/content-center'; -``` - -- Let's add it to our _loginPage.tsx_ - -```diff -import * as React from "react" -- import {Panel} from '../../common'; -+ import {Panel, ContentCenter} from '../../common'; -import {Form} from './components/form'; - -export const LoginPage = () => -+ - -
    - -+ - -``` - -- Let's define an entity for the loginInfo let's create the following path and file -_src/model/login.ts_ - -```javascript -export interface LoginEntity { - login : string; - password : string; -} - -export const createEmptyLogin = () : LoginEntity => ({ - login: '', - password: '', -}); -``` - -- Let's add login validation fake restApi: create a folder _src/api_. and a file called -_login.ts_ - -``` -. -└── src/ - │ - ├── api/ - │ └── login.ts - ├── model/ - │ └── login.ts - └── pages/ - ├── login/ - │ ├── form.tsx - │ ├── header.tsx - │ ├── index.ts - │ └── loginPage.tsx - └── b/ - ├── index.ts - └── pageB.tsx - -``` - -_./src/api/login.ts_ - -```javascript -import {LoginEntity} from '../model/login'; - -// Just a fake loginAPI -export const isValidLogin = (loginInfo : LoginEntity) : boolean => - (loginInfo.login === 'admin' && loginInfo.password === 'test'); -``` - - -- Now we can integrate it into _form.tsx_ login button, but.. it's time to think -about how do we want to structure this, Do we want _form.tsx_ to hold the state -of current user logged in and current password, plus button handler? This should -be responsibility of the container control (Let's create a new component loginPageContainer.tsx). So let's the define as state of the _loginPageContainer_ this information plus function and pass it down to -the _loginPage_ and _form_ component. Let's start with _loginPageContainer_ - - -- Let's create a _loginPageContainer.ts_ - -Let'start by converting the component from stateless to class component. - -_./src/pages/login/loginPage.tsx_ - -```diff -import * as React from "react" -import {Link} from 'react-router-dom'; -import {Header} from './header'; -import {Form} from './form' -+ import {LoginEntity} from '../../model/login'; - -+ interface State { -+ loginInfo : LoginEntity; -+ } - -+ interface Props { -+ history; -+ } - -- export const LoginPage = () => { -+ export class LoginPage extends React.Component { -+ -+ constructor(props) { -+ super(props); -+ this.state = {loginInfo: createEmptyLogin()}; -+ } -+ -+ public render() { - return ( -
    -
    -
    -
    -
    - -
    -
    -
    -
    -+ } - ); -} -``` -Now it's time to add the login api integration - -```diff -import * as React from "react" -import { Link } from 'react-router-dom'; -import { Header } from './header'; -import { Form } from './form' -import { LoginEntity, createEmptyLogin } from '../../model/login'; -+ import { withRouter } from 'react-router-dom'; -+ import { loginApi } from "../../api/login"; - -interface State { - loginInfo: LoginEntity; -} - -interface Props { -+ history : History; -} - -- export class LoginPage extends React.Component { -+ export const LoginPage = withRouter(class LoginPageInner extends React.Component { - - constructor(props) { - super(props); - this.state = { loginInfo: createEmptyLogin() }; - } - -+ performLogin() { -+ if(loginApi.isValidLogin(this.state.loginInfo)) { -+ this.props.history.push('/pageB'); -+ } -+ } - -+ updateLoginEntity(loginInfo : LoginEntity) { -+ this.setState({loginInfo: loginInfo}); -+ } - - public render() { - return ( -
    -
    -
    -
    -
    - -
    -
    -
    -
    - ); - } -} -``` - -- Let's now define the properties that the _form_ child controller will accept - -_./src/pages/login/form.tsx_ - -```diff -import * as React from "react"; -import { withRouter } from 'react-router-dom'; -+ import {LoginEntity} from '../../model/login'; - -+ interface Props { -+ loginInfo : LoginEntity; -+ updateLoginInfo : (loginInfo : LoginEntity) => void; -+ performLogin : () => void; -+ history; -+ } - - --export const Form = withRouter((props: Props) => { -+ export const Form = (props: Props) => { -- const login = () => { -- history.push('/pageB'); -- } - - return ( -
    - -
    -
    - -
    -
    - -
    - -
    - -
    - ); -+ } -- }); -``` - -- Let's apply this props in the components and propagate the login change. - -_./src/pages/login/form.tsx_ - -```diff - return ( -
    -
    -
    -
    - props.updateLoginInfo({login: e.target.value, password: props.loginInfo.password })} - /> -
    -
    - props.updateLoginInfo({login: props.loginInfo.login, password: e.target.value })} - /> -
    - -
    -
    -
    - ); -``` - -- Now it's time to update the LoginPage again, including the properties that we have to pass -to the _form_ component - -```diff --
    -+ -``` - -- Congratulations, you already have the example running. It is time to do some refator and clean up. - -- First we will extract all the divs in charge of generate a centered container to a new common centeredContainer component. - -- Create a folder _pages\common_ and a file _centered.tsx_ underneath - -```javascript -import * as React from "react" - -interface Props { - children? : any; -} - -export const CenteredContainer = (props: Props) => { - return ( -
    -
    -
    -
    - {props.children} -
    -
    -
    -
    - ); -} -``` - -- Add a new _index.ts_ file to the same folder - -```javascript -import {CenteredContainer} from './centered' - -export { - CenteredContainer -} -``` - -- Now we have a component that can be used to center its content, lets use it in our _loginPage.tsx_, the render function should look like this - -```javascript -public render() { - return ( - -
    - - - ); - } -``` - -> Now you an test the solution, try _npm start_ and if you enter the combination _test_ _test_ it wil navigate to page b. - -- Time now to do some clean up in the form component, lets start creating a _formField.tsx_ file in the _common_ folder - -```javascript -import * as React from "react" - -interface Props { - text : string; - name : string; - type : string; - value?: string; - updateFieldValue : (fieldName: string, fieldValue: any) => void; -} - -export const FormField = (props: Props) => { - - - return ( -
    - props.updateFieldValue(props.name, e.target.value)} - /> -
    - ); -} -``` - -- And add it to the _index.ts_ in the same folder - -```javascript -import {CenteredContainer} from './centered' -import {FormField} from './formField' - -export { - CenteredContainer, - FormField -} -``` - -- Finally the structure look like this: - -``` -. -└── src/ - │ - ├── api/ - │ └── login.ts - ├── model/ - │ └── login.ts - ├── pages/ - │ ├── login/ - │ │ ├── form.tsx - │ │ ├── header.tsx - │ │ └── loginPage.tsx - │ ├── common/ - │ │ ├── centered.tsx - │ │ ├── formField.tsx - │ │ └── index.ts - │ │ ├── index.ts - │ └── b/ - │ ├── index.ts - │ └── pageB.tsx - ├── restApi/ - │ └── login.ts - ├── app.tsx - ├── index.html - └── main.tsx - - -``` - -- As you can see in the code above, now we have a component that can be used -to define any input field within a form and which will inform of the value changes -to the parent component. Time to use it! - -```javascript -import * as React from "react" -import {hashHistory} from 'react-router' -import {LoginEntity} from '../../model/login'; -import {FormField} from '../common/formField'; - -interface Props { - loginInfo : LoginEntity; - updateLoginInfo : (loginInfo : LoginEntity) => void; - performLogin : () => void; -} - - function updateFieldValue(fieldName: string, fieldValue: any){ - const newLoginEntity = this.props.loginInfo; - newLoginEntity[fieldName] = fieldValue; - this.props.updateLoginInfo(newLoginEntity); - } - -export const Form = () => { - return ( -
    - -
    - - - { this.props.performLogin() } } - /> -
    - -
    - ); -} -``` -- Pay attention to the new _updateFieldValue_ function which will be in charge of -receive changes in all the fields within the form and pass it to the parent component as a new LoginEntity - -- Pending to implement (easy and discussion): add a red label indicating that login failed. diff --git a/15 LoginForm/src/common/content-center/content-center.tsx b/15 LoginForm/src/common/content-center/content-center.tsx deleted file mode 100644 index 9860aaf..0000000 --- a/15 LoginForm/src/common/content-center/content-center.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from "react" - -export const ContentCenter : React.StatelessComponent = (props) => -
    -
    -
    - {props.children} -
    -
    -
    \ No newline at end of file diff --git a/15 LoginForm/src/common/index.ts b/15 LoginForm/src/common/index.ts deleted file mode 100644 index bf0cd1f..0000000 --- a/15 LoginForm/src/common/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {Panel} from './panel'; -export {ContentCenter} from './content-center/content-center'; \ No newline at end of file diff --git a/15 LoginForm/src/common/panel/components/body.tsx b/15 LoginForm/src/common/panel/components/body.tsx deleted file mode 100644 index 63e0e2e..0000000 --- a/15 LoginForm/src/common/panel/components/body.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from "react" - -interface Props { -} - -export const Body : React.StatelessComponent = (props) => -
    - {props.children} -
    \ No newline at end of file diff --git a/15 LoginForm/src/common/panel/components/header.tsx b/15 LoginForm/src/common/panel/components/header.tsx deleted file mode 100644 index bedde66..0000000 --- a/15 LoginForm/src/common/panel/components/header.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as React from "react" - -interface Props { - title : string; -} - -export const Header = (props : Props) => -
  • -

    {props.title}

    -
  • \ No newline at end of file diff --git a/15 LoginForm/src/common/panel/index.ts b/15 LoginForm/src/common/panel/index.ts deleted file mode 100644 index 5e5c164..0000000 --- a/15 LoginForm/src/common/panel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {Panel} from './panel'; \ No newline at end of file diff --git a/15 LoginForm/src/common/panel/panel.tsx b/15 LoginForm/src/common/panel/panel.tsx deleted file mode 100644 index 5b693d0..0000000 --- a/15 LoginForm/src/common/panel/panel.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from "react" -import {Body} from './components/body'; -import {Header} from './components/header'; - -interface Props { - title : string; -} - -export const Panel : React.StatelessComponent = (props) => -
    -
      -
      - - {props.children} - -
    -
    \ No newline at end of file diff --git a/15 LoginForm/src/index.html b/15 LoginForm/src/index.html deleted file mode 100644 index d972c34..0000000 --- a/15 LoginForm/src/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Sample 14: ReactRouter - - -

    Sample 14: ReactRouter

    -
    - - diff --git a/15 LoginForm/src/main.tsx b/15 LoginForm/src/main.tsx deleted file mode 100644 index 51ff8e3..0000000 --- a/15 LoginForm/src/main.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { HashRouter, Switch, Route } from 'react-router-dom'; -import { LoginPage } from './pages/login'; -import { PageB } from './pages/b'; - -ReactDOM.render( - - - - - - - , document.getElementById('root') -); diff --git a/15 LoginForm/src/pages/b/index.ts b/15 LoginForm/src/pages/b/index.ts deleted file mode 100644 index 8c7e132..0000000 --- a/15 LoginForm/src/pages/b/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {PageB} from './pageB' diff --git a/15 LoginForm/src/pages/login/components/form.tsx b/15 LoginForm/src/pages/login/components/form.tsx deleted file mode 100644 index 95de0d8..0000000 --- a/15 LoginForm/src/pages/login/components/form.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from "react" -import { withRouter } from 'react-router-dom'; - -export const Form = withRouter(({history}) => { - const login = () => { - history.push('/pageB'); - } - - return ( -
    -
    -
    - -
    -
    - -
    - -
    -
    - ); -}); diff --git a/15 LoginForm/src/pages/login/loginPage.tsx b/15 LoginForm/src/pages/login/loginPage.tsx deleted file mode 100644 index 3e16ba9..0000000 --- a/15 LoginForm/src/pages/login/loginPage.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from "react" -import {Panel, ContentCenter} from '../../common'; -import {Form} from './components/form'; - -export const LoginPage = () => - - -
    - - - diff --git a/15 LoginForm/src/pages/login/loginPageContainer.tsx b/15 LoginForm/src/pages/login/loginPageContainer.tsx deleted file mode 100644 index 61cf1be..0000000 --- a/15 LoginForm/src/pages/login/loginPageContainer.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from "react" -import {LoginPage} from './loginPage'; -import {LoginEntity, createEmptyLogin} from '../../model/login'; - -interface State { - loginInfo: LoginEntity; -} - -interface Props { - history; -} - -export class LoginPageContainer extends React.Component { - constructor(props) { - super(props); - - this.state = {loginInfo : createEmptyLogin()} - } - - public render() { - return ( - - ) - } - -} diff --git a/15 LoginForm/tsconfig.json b/15 LoginForm/tsconfig.json deleted file mode 100644 index ba8b3b7..0000000 --- a/15 LoginForm/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "es6", - "moduleResolution": "node", - "declaration": false, - "noImplicitAny": false, - "jsx": "react", - "sourceMap": true, - "noLib": false, - "suppressImplicitAnyIndexErrors": true - }, - "compileOnSave": false, - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/15 LoginForm/webpack.config.js b/15 LoginForm/webpack.config.js deleted file mode 100644 index 9b58da0..0000000 --- a/15 LoginForm/webpack.config.js +++ /dev/null @@ -1,86 +0,0 @@ -var path = require('path'); -var webpack = require('webpack'); -var HtmlWebpackPlugin = require('html-webpack-plugin'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); - -var basePath = __dirname; - -module.exports = { - context: path.join(basePath, "src"), - resolve: { - extensions: ['.js', '.ts', '.tsx'] - }, - - entry: [ - './main.tsx', - '../node_modules/bootstrap/dist/css/bootstrap.css' - ], - output: { - path: path.join(basePath, 'dist'), - filename: 'bundle.js' - }, - - devtool: 'source-map', - - devServer: { - contentBase: './dist', // Content base - inline: true, // Enable watch and live reload - host: 'localhost', - port: 8080, - stats: 'errors-only' - }, - - module: { - rules: [ - { - test: /\.(ts|tsx)$/, - exclude: /node_modules/, - loader: 'awesome-typescript-loader', - options: { - useBabel: true, - }, - }, - { - test: /\.css$/, - include: /node_modules/, - loader: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: { - loader: 'css-loader', - }, - }), - }, - // Loading glyphicons => https://github.com/gowravshekar/bootstrap-webpack - // Using here url-loader and file-loader - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/font-woff' - }, - { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=application/octet-stream' - }, - { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url-loader?limit=10000&mimetype=image/svg+xml' - }, - { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file-loader' - }, - ] - }, - plugins: [ - // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin - new HtmlWebpackPlugin({ - filename: 'index.html', // Name of file in ./dist/ - template: 'index.html', // Name of template in ./src - hash: true - }), - new ExtractTextPlugin({ - filename: '[chunkhash].[name].css', - disable: false, - allChunks: true, - }), - ] -} diff --git a/15_LoginForm/.babelrc b/15_LoginForm/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/15_LoginForm/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/15_LoginForm/package.json b/15_LoginForm/package.json new file mode 100644 index 0000000..d836dc5 --- /dev/null +++ b/15_LoginForm/package.json @@ -0,0 +1,41 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-router-dom": "^4.3.1" + } +} diff --git a/15_LoginForm/readme.md b/15_LoginForm/readme.md new file mode 100644 index 0000000..bfed34a --- /dev/null +++ b/15_LoginForm/readme.md @@ -0,0 +1,845 @@ +## 15 Login form + +In this sample we are going to implement a basic login page, that will redirect +the user to another page whenever the login has completed successfully. + +We will attempt to create a [realistic layout](http://bootsnipp.com/snippets/featured/compact-login-form-bs-3), in order to keep simplicity we will +break it into subcomponents and perform some refactor in order to make the solution +more maintenable. + +We will take a startup point sample _14 ReactRouter_: + +## Summary steps: + +- Let's rename pageA to LoginPage. +- Let's create a 'Pages' subfolder and reorganize pages. +- Let's build the layout for this page. +- Let's add navigation on login button clicked. +- Let's add login validation fake api. +- Let's connect it into the login button logic. +- Let's do a bit of refactor and clean up extracting functionality to reusable components. +- Let's add some form validation (mandatory fields, minlength). + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _14 React Router_ and execute _npm install_. + +- Let's rename _pageA.tsx_ to _loginPage.tsx_. + +- Let's update as well the name of the component. + +_./src/loginPage.tsx_ + +```javascript +import * as React from "react" +import { Link } from 'react-router-dom'; + +export const LoginPage = () => { + return ( +
    +

    Hello from page A

    +
    + Navigate to Page B +
    + ) +} +``` + +- Now it's time to reorganize the pages structure. Let's create a subfolders +called _pages_ + +- Under that subfolder let's create two more subfolders _login_ and _b_ + +- Let's place the _pages_ under that subfolders: _pages/login/loginPage.tsx_ and _pages/b/pageB. + +``` +. +└── src/ + │ + ├── model/ + └── pages/ + ├── login/ + │ └── loginPage.tsx + └── b/ + └── pageB.tsx + +``` + +- In some cases this pages will contain more secondary files, let's create a simple _index.tsx_ file for each of this pages. + +- Under _pages/login/index.ts_. + +_./src/pages/login/index.ts_ + +```javascript +export {LoginPage} from './loginPage'; +``` + +- Under _pages/b/index.ts_ + +_./src/pages/b/index.ts_ + +```javascript +export {PageB} from './pageB'; +``` + +- The structure look like this: + +``` +. +└── src/ + │ + └── pages/ + ├── login/ + │ ├── index.ts + │ └── loginPage.tsx + └── b/ + ├── index.ts + └── pageB.tsx +``` + +- Let's update _main.tsx_ (routes, names and add a redirect from root to login page). + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +- import { PageA } from './pageA'; +- import { PageB } from './pageB'; ++ import { LoginPage } from './pages/login'; ++ import { PageB } from './pages/b'; + +ReactDOM.render( + + +- ++ + + + + , + document.getElementById('root') +); +``` + +- Let's update as well the navigation from _pageB_ to _loginPage_, _pageB.tsx_. + +_./src/pages/b/pageB.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; + +export const PageB = () => { + return ( +
    +

    Hello from page B

    +
    +- Navigate to Page A ++ Navigate to Login +
    + ) +} +``` + +- Let's make a quick test and check that everyting is still working fine. + +``` +npm start +``` + +- Time to remove 'Sample app' text from the main _html_. + +_./src/index.html_ + +```diff + + + + + + + +
    +-

    Sample app

    +
    +
    + + +``` + +- Let's build a proper _login_ layout, _loginPage.tsx_, we will base it in the [following layout](http://bootsnipp.com/snippets/featured/compact-login-form-bs-3)but we will break it down into subcomponents. + +- To build a nice layout, we will install _@material-ui/core_ + +```bash +npm install @material-ui/core @material-ui/icons --save-dev +``` + +- Now we could create a login form it could look somethin like: + +_./src/pages/loginPage.tsx_ + +```javascript +import * as React from "react" +import { Link } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { FormHelperText } from "@material-ui/core"; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface Props extends RouteComponentProps, WithStyles { +} + +const LoginPageInner = (props : Props) => { + const { classes } = props; + + return ( + + + +
    + + + +
    +
    +
    + ) +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- This can be ok, but if we take a deeper look to this component, we could break down into two, one is the card itself the other the form dialog, so it should finally look like: + +** Proposal ** + +```javascript + + + + + + +``` + +- Let's create the loginformcomponent: + +_./src/pages/login/loginForm.tsx_ + +```javascript +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; + +export const LoginForm = (props) => { + return ( +
    + + + +
    + ) +} +``` + +- And let's update the _loginPage.tsx_ + +_./src/pages/loginPage.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { FormHelperText } from "@material-ui/core"; ++ import { LoginForm } from './loginForm'; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface Props extends RouteComponentProps, WithStyles { +} + +const LoginPageInner = (props : Props) => { + const { classes } = props; + + return ( + + + ++ +-
    +- +- +- +-
    +
    +
    + ) +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Let's give a try and check how is it looking. + +```bash +npm start +``` + +- Le'ts add the navigation on button clicked, we will do it in two steps. + +- First we will expose a method to do that in the loginPage. + +_./src/pages/login/loginPage.tsx_ + +```diff +// ... + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface Props extends RouteComponentProps, WithStyles { +} + +const LoginPageInner = (props) => { + const { classes } = props; + ++ const onLogin = () => { ++ props.history.push('/pageB'); ++ } + + return ( + + + +- ++ + + + ) +} + +- export const LoginPage = withStyles(styles)(LoginPageInner); ++ export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Let's add the navigation on button clicked (later on we will check for user and pwd) _form.tsx_. +In order to do this we will use react-router 4 "withRouter" HoC (High order component). + +_./src/pages/login/LoginForm.tsx_ + +```diff +import * as React from "react" + +interface Props { ++ onLogin : () => void; +} + ++export const LoginForm = (props : Props) => { +- export const LoginForm = () => { ++ const { onLogin } = this.props; + + return ( +
    + +
    +
    + +
    +
    + +
    +- +- +
    + +
    + ); +- } ++}) +``` + +- Let's give a quick try. + +```bash +npm start +``` + +Ok, we can navigate whenever we click on the login page. + +- Let's keep on progressing, now is time to collect the username and password info, and check if password is valid or not. + +- Let's define an entity for the loginInfo let's create the following path and file + +_src/model/login.ts_ + +```javascript +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); +``` + +- Let's add login validation fake restApi: create a folder _src/api_. and a file called _login.ts_ + +_./src/api/login.ts_ + +```javascript +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); +``` + +- How it should look + +``` +. +└── src/ + │ + ├── api/ + │ └── login.ts + ├── model/ + │ └── login.ts + └── pages/ + ├── login/ + │ ├── form.tsx + │ ├── header.tsx + │ ├── index.ts + │ └── loginPage.tsx + └── b/ + ├── index.ts + └── pageB.tsx +``` + +- Let's add the _api_ integration, plus navigation on login succeeded: + +- First let's create a login state and add the api integration. + +_./src/pages/login/loginPage.tsx_ + +```diff +import * as React from "react" +import { withStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter } from 'react-router-dom'; +import {History} from 'history'; ++ import { LoginEntity, createEmptyLogin } from '../../model/login'; ++ import { isValidLogin } from '../../api/login'; + +const styles = theme => ({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + ++ interface State { ++ loginInfo: LoginEntity; ++ } + +interface Props extends RouteComponentProps, WithStyles { +} + +- const LoginPageInner = (props) => { ++ class LoginPageInner extends React.Component { +- const { classes, history } = props; + ++ constructor(props) { ++ super(props); ++ ++ this.state = { loginInfo: createEmptyLogin() } ++ } + +- const onLogin = () => { ++ onLogin = () => { ++ if (isValidLogin(this.state.loginInfo)) { ++ this.props.history.push('/pageB'); +- history.push('/pageB'); ++ } + } + ++ public render() { ++ const { classes } = this.props; + + return ( + + + +- ++ + + + ) ++ } +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Now let's read the data from the textfields components (user and password). + +_./src/pages/login/loginPage.tsx_ + +```diff +class LoginPageInner extends React.Component { + + onLogin = () => { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); + } + } + ++ onUpdateLoginField = (name, value) => { ++ this.setState({loginInfo: { ++ ...this.state.loginInfo, ++ [name]: value, ++ }}) ++ } + + render() { + const { classes } = this.props; + + return ( + + + + + + + ) + + } +} +``` + +_./src/pages/login/loginForm.tsx_ + +```diff +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; ++ import { LoginEntity } from "../../model/login"; + +interface Props { + onLogin : () => void; ++ onUpdateField: (string, any) => void; ++ loginInfo : LoginEntity; +} + +export const LoginForm = (props : Props) => { +- const { onLogin } = props; ++ const { onLogin, onUpdateField, loginInfo } = props; + ++ // TODO: Enhacement move this outside the stateless component discuss why is a good idea ++ const onTexFieldChange = (fieldId) => (e) => { ++ onUpdateField(fieldId, e.target.value); ++ } + + return ( +
    + + + +
    + ) +} +``` + +- Let's display a notification when the login validation fails. + +- First we will create a simple notification component, base on _react material ui_ _snackbar_ + +_./src/common/notification.tsx_ + +```javascript +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles } from "@material-ui/core"; + +interface Props { + classes?: any; + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => ({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const {classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); +``` + +- Let's expose this common component via an _index_ file. + +_./src/common/index.tsx_ + +```javascript +export * from './notification'; +``` + +- Now let's instantiate this in our _loginPage_ + +_./src/pages/login/loginPage.tsx_ + +```diff +import * as React from "react" +import { withStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { History } from 'history'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; ++ import { NotificationComponent } from '../../common' + +const styles = theme => ({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface State { + loginInfo: LoginEntity; ++ showLoginFailedMsg: boolean; +} + + +interface Props extends RouteComponentProps { + classes?: any; +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { loginInfo: createEmptyLogin(), ++ showLoginFailedMsg : false, + } + } + + onLogin = () => { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); +- } ++ } else { ++ this.setState({showLoginFailedMsg: true}); ++ } + } + + onUpdateLoginField = (name: string, value) => { + this.setState({ + loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }) + } + + render() { + const { classes } = this.props; + + return ( ++ <> + + + + + + ++ this.setState({showLoginFailedMsg: false})} ++ /> ++ + ) + + } +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- We are getting some warnings because of Typography new version, let's add a Theme to fix that. + +Watchout new typgoraphy and snackbar: https://github.com/mui-org/material-ui/issues/13144 + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; ++ import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import {LoginPage} from './pages/login'; +import {PageB} from './pages/b'; + ++ const theme = createMuiTheme({ ++ typography: { ++ useNextVariants: true, ++ }, ++ }); + + +ReactDOM.render( ++ + + + + + + ++ + ,document.getElementById('root') +); +``` + +- We are getting some warning on the snack bar, you can find a fix here: + +https://github.com/mui-org/material-ui/issues/13144 + +https://codesandbox.io/s/zz6wnqklzm + +> And form validation? There are several libraries available, one that we had created in lemoncode is lc-form-validation we will create a sample including this lib to validate the login form +(required fields) diff --git a/15_LoginForm/readme_es.md b/15_LoginForm/readme_es.md new file mode 100644 index 0000000..ae3f8a6 --- /dev/null +++ b/15_LoginForm/readme_es.md @@ -0,0 +1,836 @@ +## 15 Login form + +En esta muestra vamos a implementar una página básica de login, que redireccionará al usuario a otra página cuando el login sea correcto. + +Intentaremos de crear un [realistic layout](http://bootsnipp.com/snippets/featured/compact-login-form-bs-3), para hacerlo simple vamos a romper esto en subcomponentes y hacer algunas refactorizaciones para hacer la solución más mantenible. + +Tomaremos como punto de partida la muestra _14 ReactRouter_: + +## Resumen de pasos: + +- Renombrar pageA a LoginPage. +- Crear una subcarpeta 'Pages' y reorganizar las páginas. +- Construir el layout para esta página. +- Añadir navegación en el botón de login. +- Añadir validación de login en la api falsa. +- Conectarlo dentro de la lógica del botón de login. +- Hacer una pequeña refactorización y limpiar funcionalidades extras a un componente reusable. +- Añadir algunas validaciones de formulario (campos obligatorios, longitud mínima). + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x and npm 3.x.x usando los comandos `node -v` y `npm -v` en un terminal/consola. Versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _14 React Router_ y ejecuta _npm install_. + +- Renombra _pageA.tsx_ a _loginPage.tsx_. + +- Actualiza también el nombre del componente. + +_./src/loginPage.tsx_ + +```javascript +import * as React from "react" +import { Link } from 'react-router-dom'; + +export const LoginPage = () => { + return ( +
    +

    Hello from page A

    +
    + Navigate to Page B +
    + ) +} +``` + +- Ahora es momento de reorganizar la estuctura de las páginas. Crea una subcarpeta llamada _pages_ + +- Bajo ese subcarpeta crea dos subcarpetas: _pages/login/loginPage.tsx_ y _pages/b/pageB. + +``` +. +└── src/ + │ + ├── model/ + └── pages/ + ├── login/ + │ └── loginPage.tsx + └── b/ + └── pageB.tsx + +``` + +- En algunos casos estas páginas contienen más ficheros secundarios, crea un fichero _index.tsx_ simple por cada una de estas páginas. + +- Bajo _pages/login/index.ts. + +```javascript +export {LoginPage} from './loginPage'; +``` + +- Bajo _pages/b/index.ts_ + +_./src/pages/b/index.ts_ + +```javascript +export {PageB} from './pageB'; +``` + +- La estrutura queda tal que así: + +``` +. +└── src/ + │ + └── pages/ + ├── login/ + │ ├── index.ts + │ └── loginPage.tsx + └── b/ + ├── index.ts + └── pageB.tsx +``` + +- Vamos a actualizar _main.tsx_ (rutas, nombes y añade una redirección de root a la página login). + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +- import { PageA } from './pageA'; +- import { PageB } from './pageB'; ++ import { LoginPage } from './pages/login'; ++ import { PageB } from './pages/b'; + +ReactDOM.render( + + +- ++ + + + + , + document.getElementById('root') +); +``` + +- Actualiza también la navegación de _pageB_ a _loginPage_, _pageB.tsx_. + +_./src/pages/b/pageB.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; + +export const PageB = () => { + return ( +
    +

    Hello from page B

    +
    +- Navigate to Page A ++ Navigate to Login +
    + ) +} +``` + +- Haz un test rápido y verifica que todo aún funciona bien. + +``` +npm start +``` + +- Momento de borrar el fichero 'Sample app' del fichero principal _html_. + +```diff + + + + + + + +
    +-

    Sample app

    +
    +
    + + +``` + +- Vamos a crear un adecuado _login_ layout, _loginPage.tsx_, basada en [following layout](http://bootsnipp.com/snippets/featured/compact-login-form-bs-3) pero romperemos esto en subcomponentes. + +- Construyamos a bonito layout, instalaremos _@material-ui/core_ + +```bash +npm install @material-ui/core @material-ui/icons --save-dev +``` + +- Ahora podremos crear un formulario de login podría ser algo como esto: + +_./src/pages/loginPage.tsx_ + +```javascript +import * as React from "react" +import { Link } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { FormHelperText } from "@material-ui/core"; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface Props extends RouteComponentProps, WithStyles { +} + +const LoginPageInner = (props : Props) => { + const { classes } = props; + + return ( + + + +
    + + + +
    +
    +
    + ) +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Esto puede estár ok, pero si analizamos este componente más a fondo, prodíamos dividirlo en dos, una es la tarjeta en sí, la otra el cuadro del diálogo, por lo que finalmente dibería verse como + +** Propósito ** + +```javascript + + + + + + +``` + +- Creamos el loginformcomponent: + +_./src/pages/login/loginForm.tsx_ + +```javascript +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; + +export const LoginForm = (props) => { + return ( +
    + + + +
    + ) +} +``` + +- Y actualizamos _loginPage.tsx_ + +_./src/pages/loginPage.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { FormHelperText } from "@material-ui/core"; ++ import { LoginForm } from './loginForm'; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface Props extends RouteComponentProps, WithStyles { +} + +const LoginPageInner = (props : Props) => { + const { classes } = props; + + return ( + + + ++ +-
    +- +- +- +-
    +
    +
    + ) +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Vamos a intentar y verificar como se está viendo. + +```bash +npm start +``` + +- Añadamos una navegación en el botón, hagámoslo en dos pasos. + +- Primero exponemos un método para hacer esto en loginPage. + +_./src/pages/login/loginPage.tsx_ + +```diff +// ... + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface Props extends RouteComponentProps, WithStyles { +} + +const LoginPageInner = (props) => { + const { classes } = props; + ++ const onLogin = () => { ++ props.history.push('/pageB'); ++ } + + return ( + + + +- ++ + + + ) +} + +- export const LoginPage = withStyles(styles)(LoginPageInner); ++ export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Añadamos la navegación en el botón (mas tarde verificaremos el usuario y la contraseña) _form.tsx_. +En order de hacer esto usaremos react-router 4 "withRouter" HoC +(High order component). + +_./src/pages/login/LoginForm.tsx_ + +```diff +import * as React from "react" + +interface Props { ++ onLogin : () => void; +} + ++export const LoginForm = (props : Props) => { +- export const LoginForm = () => { ++ const { onLogin } = this.props; + + return ( +
    +
    +
    +
    + +
    +
    + +
    +- +- +
    +
    +
    + ); +- } ++}) +``` + +- Demos un vistazo rápido. + +```bash +npm start +``` + +Ok, podemos navegar cuando nosotros clicamos en la página de login. + +- Guardemos el progreso, ahora es momento de recoger la información username y password, y verificaremos si es válida la contraseña. + +- Definamos una entidad para loginInfo crearemos la seguiente ruta y el fichero + +_src/model/login.ts_ + +```javascript +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); +``` + +- Añadamos a la restApi falsa y añadamos la validación de login: crearemos una carpeta _src/api_. y un fichero llamado _login.ts_ + +_./src/api/login.ts_ + +```javascript +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); +``` + +- How it should look + +``` +. +└── src/ + │ + ├── api/ + │ └── login.ts + ├── model/ + │ └── login.ts + └── pages/ + ├── login/ + │ ├── form.tsx + │ ├── header.tsx + │ ├── index.ts + │ └── loginPage.tsx + └── b/ + ├── index.ts + └── pageB.tsx +``` + +- Añadamos una integración a _api_, más una navegación en el existo de login. + +- Primero crearemos un estado de login y añade la integración de la api. + +_./src/pages/login/loginPage.tsx_ + +```diff +import * as React from "react" +import { withStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter } from 'react-router-dom'; +import {History} from 'history'; ++ import { LoginEntity, createEmptyLogin } from '../../model/login'; ++ import { isValidLogin } from '../../api/login'; + +const styles = theme => ({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + ++ interface State { ++ loginInfo: LoginEntity; ++ } + +interface Props extends RouteComponentProps, WithStyles { +} + +- const LoginPageInner = (props) => { ++ class LoginPageInner extends React.Component { +- const { classes, history } = props; + ++ constructor(props) { ++ super(props); ++ ++ this.state = { loginInfo: createEmptyLogin() } ++ } + +- const onLogin = () => { ++ onLogin = () => { ++ if (isValidLogin(this.state.loginInfo)) { ++ this.props.history.push('/pageB'); +- history.push('/pageB'); ++ } + } + ++ public render() { ++ const { classes } = this.props; + + return ( + + + +- ++ + + + ) ++ } +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Ahora leamos un dato del componente textfields (usuario y contraseña). + +_./src/pages/login/loginPage.tsx_ + +```diff +class LoginPageInner extends React.Component { + + onLogin = () => { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); + } + } + ++ onUpdateLoginField = (name, value) => { ++ this.setState({loginInfo: { ++ ...this.state.loginInfo, ++ [name]: value, ++ }}) ++ } + + render() { + const { classes } = this.props; + + return ( + + + + + + + ) + + } +} +``` + +_./src/pages/login/loginForm.tsx_ + +```diff +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; ++ import { LoginEntity } from "../../model/login"; + +interface Props { + onLogin : () => void; ++ onUpdateField: (string, any) => void; ++ loginInfo : LoginEntity; +} + +export const LoginForm = (props : Props) => { +- const { onLogin } = props; ++ const { onLogin, onUpdateField, loginInfo } = props; + ++ // TODO: Enhacement move this outside the stateless component discuss why is a good idea ++ const onTexFieldChange = (fieldId) => (e) => { ++ onUpdateField(fieldId, e.target.value); ++ } + + return ( +
    + + + +
    + ) +} +``` + +- Mostremos una notificación cuando la validación de login falle. + +- Primero crearemos un componente de notificación simple, basado en _react material ui_ _snackbar_ + +_./src/common/notification.tsx_ + +```javascript +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles } from "@material-ui/core"; + +interface Props { + classes?: any; + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => ({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const {classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); +``` + +- Vamos a exponer este componente común via un fichero _index_. + +_./src/common/index.tsx_ + +```javascript +export * from './notification'; +``` + +- Ahora lo instanciamos en nuestro _loginPage_ + +_./src/pages/login/loginPage.tsx_ + +```diff +import * as React from "react" +import { withStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { History } from 'history'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; ++ import { NotificationComponent } from '../../common' + +const styles = theme => ({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + +interface State { + loginInfo: LoginEntity; ++ showLoginFailedMsg: boolean; +} + + +interface Props extends RouteComponentProps { + classes?: any; +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { loginInfo: createEmptyLogin(), ++ showLoginFailedMsg : false, + } + } + + onLogin = () => { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); +- } ++ } else { ++ this.setState({showLoginFailedMsg: true}); ++ } + } + + onUpdateLoginField = (name: string, value) => { + this.setState({ + loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }) + } + + render() { + const { classes } = this.props; + + return ( ++ <> + + + + + + ++ this.setState({showLoginFailedMsg: false})} ++ /> ++ + ) + + } +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); +``` + +- Estamos recibiendo algunas advertencias debido a la nueva versión de la tipografía, agreguemos un tema para solucionarlo. + +Cuidado con la nueva tipografía y la snackbar: +https://github.com/mui-org/material-ui/issues/13144 + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; ++ import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import {LoginPage} from './pages/login'; +import {PageB} from './pages/b'; + ++ const theme = createMuiTheme({ ++ typography: { ++ useNextVariants: true, ++ }, ++ }); + + +ReactDOM.render( ++ + + + + + + ++ + ,document.getElementById('root') +); +``` + +- Estamos recibiendo algunas advertencias en la snackbar, pu puedes arreglarlo aquí: + +https://github.com/mui-org/material-ui/issues/13144 + +https://codesandbox.io/s/zz6wnqklzm + +> ¿ Y la validación del formulario ? Hay muchas librerías disponibles, una que nosotros hemos creado en lemoncode es lc-form-validation crearemos una muestra incluyendo esta librería (campos requeridos) \ No newline at end of file diff --git a/15 LoginForm/src/api/login.ts b/15_LoginForm/src/api/login.ts similarity index 99% rename from 15 LoginForm/src/api/login.ts rename to 15_LoginForm/src/api/login.ts index d542d9e..1f7d4f3 100644 --- a/15 LoginForm/src/api/login.ts +++ b/15_LoginForm/src/api/login.ts @@ -3,4 +3,3 @@ import {LoginEntity} from '../model/login'; // Just a fake loginAPI export const isValidLogin = (loginInfo : LoginEntity) : boolean => (loginInfo.login === 'admin' && loginInfo.password === 'test'); - diff --git a/15_LoginForm/src/common/index.tsx b/15_LoginForm/src/common/index.tsx new file mode 100644 index 0000000..3f0d15b --- /dev/null +++ b/15_LoginForm/src/common/index.tsx @@ -0,0 +1 @@ +export * from './notification'; \ No newline at end of file diff --git a/15_LoginForm/src/common/notification.tsx b/15_LoginForm/src/common/notification.tsx new file mode 100644 index 0000000..5396faf --- /dev/null +++ b/15_LoginForm/src/common/notification.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; + +interface Props extends WithStyles { + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => createStyles({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const { classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); diff --git a/15_LoginForm/src/hello.tsx b/15_LoginForm/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/15_LoginForm/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/15_LoginForm/src/index.html b/15_LoginForm/src/index.html new file mode 100644 index 0000000..0fcc01e --- /dev/null +++ b/15_LoginForm/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
    +
    +
    + + diff --git a/15_LoginForm/src/main.tsx b/15_LoginForm/src/main.tsx new file mode 100644 index 0000000..62678a0 --- /dev/null +++ b/15_LoginForm/src/main.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + + + + + + + + + , document.getElementById('root') +); diff --git a/15 LoginForm/src/model/login.ts b/15_LoginForm/src/model/login.ts similarity index 97% rename from 15 LoginForm/src/model/login.ts rename to 15_LoginForm/src/model/login.ts index a9da6e2..081e6fb 100644 --- a/15 LoginForm/src/model/login.ts +++ b/15_LoginForm/src/model/login.ts @@ -6,4 +6,4 @@ export interface LoginEntity { export const createEmptyLogin = () : LoginEntity => ({ login: '', password: '', -}); \ No newline at end of file +}); diff --git a/15_LoginForm/src/nameEdit.tsx b/15_LoginForm/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/15_LoginForm/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/15_LoginForm/src/pages/b/index.ts b/15_LoginForm/src/pages/b/index.ts new file mode 100644 index 0000000..913631b --- /dev/null +++ b/15_LoginForm/src/pages/b/index.ts @@ -0,0 +1 @@ +export {PageB} from './pageB'; \ No newline at end of file diff --git a/15 LoginForm/src/pages/b/pageB.tsx b/15_LoginForm/src/pages/b/pageB.tsx similarity index 59% rename from 15 LoginForm/src/pages/b/pageB.tsx rename to 15_LoginForm/src/pages/b/pageB.tsx index 259758c..bbde6e3 100644 --- a/15 LoginForm/src/pages/b/pageB.tsx +++ b/15_LoginForm/src/pages/b/pageB.tsx @@ -1,12 +1,9 @@ import * as React from "react" import { Link } from 'react-router-dom'; -export const PageB = () => { - return ( +export const PageB = () =>

    Hello from page B


    - Navigate to Login + Navigate to Login
    - ) -} diff --git a/15 LoginForm/src/pages/login/index.ts b/15_LoginForm/src/pages/login/index.ts similarity index 100% rename from 15 LoginForm/src/pages/login/index.ts rename to 15_LoginForm/src/pages/login/index.ts diff --git a/15_LoginForm/src/pages/login/loginForm.tsx b/15_LoginForm/src/pages/login/loginForm.tsx new file mode 100644 index 0000000..3776270 --- /dev/null +++ b/15_LoginForm/src/pages/login/loginForm.tsx @@ -0,0 +1,39 @@ +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity } from "../../model/login"; + +interface Props { + onLogin: () => void; + onUpdateField: (string, any) => void; + loginInfo : LoginEntity; +} + +export const LoginForm = (props: Props) => { + const { onLogin, onUpdateField, loginInfo } = props; + + const onTexFieldChange = (fieldId) => (e) => { + onUpdateField(fieldId, e.target.value); + } + + return ( +
    + + + +
    + ) +} diff --git a/15_LoginForm/src/pages/login/loginPage.tsx b/15_LoginForm/src/pages/login/loginPage.tsx new file mode 100644 index 0000000..8aa6efa --- /dev/null +++ b/15_LoginForm/src/pages/login/loginPage.tsx @@ -0,0 +1,82 @@ +import * as React from "react" +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; +import { NotificationComponent } from '../../common' + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + + + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; +} + + +interface Props extends RouteComponentProps, WithStyles { +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { loginInfo: createEmptyLogin(), + showLoginFailedMsg : false, + } + } + + onLogin = () => { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); + } else { + this.setState({showLoginFailedMsg: true}); + } + } + + onUpdateLoginField = (name: string, value) => { + this.setState({ + loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }) + } + + render() { + const { classes, onLogin } = this.props; + return ( + <> + + + + + + + this.setState({showLoginFailedMsg: false})} + /> + + ) + + } +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); diff --git a/15_LoginForm/tsconfig.json b/15_LoginForm/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/15_LoginForm/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/15_LoginForm/webpack.config.js b/15_LoginForm/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/15_LoginForm/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/16_Validation/.babelrc b/16_Validation/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/16_Validation/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/16_Validation/package.json b/16_Validation/package.json new file mode 100644 index 0000000..be1e062 --- /dev/null +++ b/16_Validation/package.json @@ -0,0 +1,42 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "lc-form-validation": "^2.0.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-router-dom": "^4.3.1" + } +} diff --git a/16_Validation/readme.md b/16_Validation/readme.md new file mode 100644 index 0000000..01dfaec --- /dev/null +++ b/16_Validation/readme.md @@ -0,0 +1,316 @@ +# 15 Login form Validation + +Let's add validation support to this form. + +For this we will use lc-form-validation library + +Summary steps: + +- Install lc-form-validation library. +- Refactor input component to a common component and include error validation info. +- Let's define the validation for the form. +- Let's hook it. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _15 React Form_ and execute _npm install_. + +```bash +npm install +``` + +- Let's install the library (it includes already the typings). + +```bash +npm install lc-form-validation --save-dev +``` + +- To avoid having too much repeated code let's move to common an input component, including it's +label plus validation text. + +_./common/forms/textFieldForm.tsx_ + +```tsx +import * as React from "react"; +import TextField from "@material-ui/core/TextField"; + +interface Props { + name: string; + label: string; + onChange: any; + value: string; + error?: string; + type? : string; +} + +const defaultProps : Partial = { + type: 'text', +} + +const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => { + onChange(fieldId, e.target.value); +} + +export const TextFieldForm : React.StatelessComponent = (props) => { + const {name, label, onChange, value, error, type} = props; + return ( + <> + + + {props.error} + + + ) +} + +``` + + +- Now let's define a basic validation for the form, we want to ensure both fields are informed. + +_./src/pages/login/loginValidations.ts_ + +```typescript +import { + createFormValidation, ValidationConstraints, Validators, +} from 'lc-form-validation'; + +const loginFormValidationConstraints: ValidationConstraints = { + fields: { + login: [ + { validator: Validators.required }, + ], + password: [ + { validator: Validators.required }, + ], + }, +}; + +export const loginFormValidation = createFormValidation(loginFormValidationConstraints); +``` + +- Let's create now a class to hold the dataFormErrors. + +_./src/login/viewmodel.ts_ + +```typescript +import { FieldValidationResult } from 'lc-form-validation'; + +export interface LoginFormErrors { + login: FieldValidationResult; + password: FieldValidationResult; +} + +export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ + login: new FieldValidationResult(), + password: new FieldValidationResult(), +}); +``` + +- Now let's go for the component side. + +- First let's add the dataFormErrors to the state of the component. + +_./src/pages/login/loginPage.tsx_ + +```diff +import { isValidLogin } from '../../api/login'; ++ import {LoginFormErrors} from './viewmodel'; + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; ++ loginFormErrors : LoginFormErrors; +} +``` + +- Now let's update the onUpdate callback to include the validation. + +_./src/pages/login/loginPage.tsx_ + +// Adding imports +```diff +import { isValidLogin } from '../../api/login'; ++ import {LoginFormErrors, createDefaultLoginFormErrors} from './viewmodel'; ++ import { loginFormValidation } from './loginValidations'; +``` + +_./src/pages/login/loginPageContainer.tsx_ + +```diff + constructor(props) { + super(props); + + this.state = { loginInfo: createEmptyLogin(), + showLoginFailedMsg : false, ++ loginFormErrors: createDefaultLoginFormErrors(), + } + } + ++ // This could be simplified and made in one go + updateLoginField = (name, value) => { + this.setState({loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }); + ++ loginFormValidation.validateField(this.state.loginInfo, name, value) ++ .then((fieldValidationResult) => { + ++ this.setState({loginFormErrors: { ++ ...this.state.loginFormErrors, ++ [name]: fieldValidationResult, ++ }); ++ }); + } +``` + +- We need to pass down dataFormErrors + +_./src/loginPageContainer.tsx_ + +```diff + public render() { + return ( + + ) + } +``` + +- Now we need to define the property in the loginForm component. + +_./src/loginForm.tsx_ + +```diff +import { LoginEntity } from "../../model/login"; ++ import {LoginFormErrors} from './viewmodel'; + +interface Props { + loginInfo: LoginEntity; + updateField: (string, any) => void; + doLogin: () => void; ++ loginFormErrors : LoginFormErrors; +} +``` + + +- Now let's update our components to match the new input component. + +_./src/common/pages/loginForm.tsx_ + +```diff +import { LoginEntity } from "../../model/login"; +import {LoginFormErrors} from './viewmodel'; ++ import { TextFieldForm } from '../../common/forms/textFieldForm'; +``` + +_./src/common/pages/loginForm.tsx_ + +```diff +export const LoginForm = (props: Props) => { +- const { onLogin, onUpdateField, loginInfo } = props; ++ const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; + +- const onTexFieldChange = (fieldId) => (e) => { +- onUpdateField(fieldId, e.target.value); +- } + + return ( +
    +- ++ + +- ++ + + + +
    + ) +} +``` + +- Let's give a try. + +```bash +npm start +``` + +- And let's add an alert (Excercise and a notification) when the user clicks and +the form all the fields are valid. + +_./src/pages/login/loginPageContainer.tsx_ + +```diff + onLogin = () => { ++ loginFormValidation.validateForm(this.state.loginInfo) ++ .then((formValidationResult) => { ++ if(formValidationResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } ++ } else { ++ alert('error, review the fields'); ++ const updatedLoginFormErrors = { ++ ...this.state.loginFormErrors, ++ ...formValidationResult.fieldErrors, ++ } + ++ this.setState({loginFormErrors: updatedLoginFormErrors}) + ++ } ++ }) + } +``` + +// TODO: mapFormValidationResultToFieldValidationErrors + +> Excercise create a generic info snack bar and remove alert. diff --git a/16_Validation/src/api/login.ts b/16_Validation/src/api/login.ts new file mode 100644 index 0000000..1f7d4f3 --- /dev/null +++ b/16_Validation/src/api/login.ts @@ -0,0 +1,5 @@ +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); diff --git a/16_Validation/src/common/forms/textFieldForm.tsx b/16_Validation/src/common/forms/textFieldForm.tsx new file mode 100644 index 0000000..808091c --- /dev/null +++ b/16_Validation/src/common/forms/textFieldForm.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography/Typography"; + +interface Props { + name: string; + label: string; + onChange: any; + value: string; + error?: string; + type? : string; +} + +const defaultProps : Partial = { + type: 'text', +} + +const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => { + onChange(fieldId, e.target.value); +} + +export const TextFieldForm : React.StatelessComponent = (props) => { + const {name, label, onChange, value, error, type} = props; + + return ( + <> + + + {props.error} + + + ) +} diff --git a/16_Validation/src/common/index.tsx b/16_Validation/src/common/index.tsx new file mode 100644 index 0000000..3f0d15b --- /dev/null +++ b/16_Validation/src/common/index.tsx @@ -0,0 +1 @@ +export * from './notification'; \ No newline at end of file diff --git a/16_Validation/src/common/notification.tsx b/16_Validation/src/common/notification.tsx new file mode 100644 index 0000000..5396faf --- /dev/null +++ b/16_Validation/src/common/notification.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; + +interface Props extends WithStyles { + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => createStyles({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const { classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); diff --git a/16_Validation/src/hello.tsx b/16_Validation/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/16_Validation/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/16_Validation/src/index.html b/16_Validation/src/index.html new file mode 100644 index 0000000..0fcc01e --- /dev/null +++ b/16_Validation/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
    +
    +
    + + diff --git a/16_Validation/src/main.tsx b/16_Validation/src/main.tsx new file mode 100644 index 0000000..62678a0 --- /dev/null +++ b/16_Validation/src/main.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + + + + + + + + + , document.getElementById('root') +); diff --git a/16_Validation/src/model/login.ts b/16_Validation/src/model/login.ts new file mode 100644 index 0000000..081e6fb --- /dev/null +++ b/16_Validation/src/model/login.ts @@ -0,0 +1,9 @@ +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); diff --git a/16_Validation/src/nameEdit.tsx b/16_Validation/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/16_Validation/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/16_Validation/src/pages/b/index.ts b/16_Validation/src/pages/b/index.ts new file mode 100644 index 0000000..913631b --- /dev/null +++ b/16_Validation/src/pages/b/index.ts @@ -0,0 +1 @@ +export {PageB} from './pageB'; \ No newline at end of file diff --git a/16_Validation/src/pages/b/pageB.tsx b/16_Validation/src/pages/b/pageB.tsx new file mode 100644 index 0000000..bbde6e3 --- /dev/null +++ b/16_Validation/src/pages/b/pageB.tsx @@ -0,0 +1,9 @@ +import * as React from "react" +import { Link } from 'react-router-dom'; + +export const PageB = () => +
    +

    Hello from page B

    +
    + Navigate to Login +
    diff --git a/16_Validation/src/pages/login/index.ts b/16_Validation/src/pages/login/index.ts new file mode 100644 index 0000000..50d85bb --- /dev/null +++ b/16_Validation/src/pages/login/index.ts @@ -0,0 +1 @@ +export {LoginPage} from './loginPage'; \ No newline at end of file diff --git a/16_Validation/src/pages/login/loginForm.tsx b/16_Validation/src/pages/login/loginForm.tsx new file mode 100644 index 0000000..eda83bd --- /dev/null +++ b/16_Validation/src/pages/login/loginForm.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity } from "../../model/login"; +import {LoginFormErrors} from './viewmodel'; +import { TextFieldForm } from '../../common/forms/textFieldForm'; + +interface Props { + onLogin: () => void; + onUpdateField: (string, any) => void; + loginInfo : LoginEntity; + loginFormErrors : LoginFormErrors; +} + +export const LoginForm = (props: Props) => { + const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; + + const onTexFieldChange = (fieldId) => (e) => { + onUpdateField(fieldId, e.target.value); + } + + return ( +
    + + + +
    + ) +} diff --git a/16_Validation/src/pages/login/loginPage.tsx b/16_Validation/src/pages/login/loginPage.tsx new file mode 100644 index 0000000..74d6e39 --- /dev/null +++ b/16_Validation/src/pages/login/loginPage.tsx @@ -0,0 +1,106 @@ +import * as React from "react" +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; +import { NotificationComponent } from '../../common' +import { LoginFormErrors, createDefaultLoginFormErrors } from './viewmodel'; +import { loginFormValidation } from './loginValidations'; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; + loginFormErrors: LoginFormErrors; +} + + +interface Props extends RouteComponentProps, WithStyles { +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { + loginInfo: createEmptyLogin(), + showLoginFailedMsg: false, + loginFormErrors: createDefaultLoginFormErrors(), + } + } + + + onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }) + } + + onUpdateLoginField = (name: string, value) => { + this.setState({loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }); + + loginFormValidation.validateField(this.state.loginInfo, name, value).then( + (fieldValidationResult) => { + this.setState({ + loginFormErrors: { + ...this.state.loginFormErrors, + [name]: fieldValidationResult, + } + }); + } + ); + } + + render() { + const { classes } = this.props; + return ( + <> + + + + + + + this.setState({ showLoginFailedMsg: false })} + /> + + ) + + } +} + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); diff --git a/16_Validation/src/pages/login/loginValidations.ts b/16_Validation/src/pages/login/loginValidations.ts new file mode 100644 index 0000000..d0eee27 --- /dev/null +++ b/16_Validation/src/pages/login/loginValidations.ts @@ -0,0 +1,16 @@ +import { + createFormValidation, ValidationConstraints, Validators, +} from 'lc-form-validation'; + +const loginFormValidationConstraints: ValidationConstraints = { + fields: { + login: [ + { validator: Validators.required }, + ], + password: [ + { validator: Validators.required }, + ], + }, +}; + +export const loginFormValidation = createFormValidation(loginFormValidationConstraints); diff --git a/16_Validation/src/pages/login/viewmodel.ts b/16_Validation/src/pages/login/viewmodel.ts new file mode 100644 index 0000000..b374fa6 --- /dev/null +++ b/16_Validation/src/pages/login/viewmodel.ts @@ -0,0 +1,11 @@ +import { FieldValidationResult } from 'lc-form-validation'; + +export interface LoginFormErrors { + login: FieldValidationResult; + password: FieldValidationResult; +} + +export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ + login: new FieldValidationResult(), + password: new FieldValidationResult(), +}); diff --git a/16_Validation/tsconfig.json b/16_Validation/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/16_Validation/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/16_Validation/webpack.config.js b/16_Validation/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/16_Validation/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/17_Context/.babelrc b/17_Context/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/17_Context/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/17_Context/Readme.md b/17_Context/Readme.md new file mode 100644 index 0000000..995a295 --- /dev/null +++ b/17_Context/Readme.md @@ -0,0 +1,262 @@ +## Intro + +In this sample we are going to learn how React 16 context api works. + +This will allow us to share information between components without having to go through props drilldown or having to add redux support to our project. + +We will take a startup point sample _16 Validation_: + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- We want to store just the _login_ field once the user logs in and display it in the page B (or in wathever page or component we need it), let's add a default value ('no user'). + +- Let's start by creating a context, we will call it _sessionContext_, and add the proper typing + +_./src/common/sessionContext.tsx_ + +```javascript +import * as React from "react"; + +export interface SessionContextProps { + login: string; +} + +const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', +}); + +export const SessionContext = React.createContext(createDefaultUser()); +``` + +- This session context will expose a _provider_ (it will serve us to set the login name in the context), and a _consumer_ (that will let us consume the login name from the context at any point of the application). +We will create a component (we will name it _SessionProvider_) that on one hand will store in the state the login name and bind it to the _SessionContext_ and on the other hand it will act as a wrapper (usually it will sit on top of the application and wrap the application). + +_./src/common/sessionContext.tsx_ + +```diff +import * as React from "react"; + +export interface SessionContextProps { + login: string; +} + +const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', +}); + +export const SessionContext = React.createContext(createDefaultUser()); + ++ interface State extends SessionContextProps { ++ } ++ ++ export class SessionProvider extends React.Component<{}, State> { ++ ++ constructor(props) { ++ super(props); ++ this.state = createDefaultUser(); ++ } ++ ++ render() { ++ return ( ++ ++ {this.props.children} ++ ++ ); ++ } ++ } +``` + +- Let's add this to the common _index_ barrel. + +_./src/common/index.tsx_ + +```diff +export * from './notification'; ++ export * from './sessionContext'; +``` + +- Is time to expose this provider on top of our application. + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; ++ import { SessionProvider } from './common'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + ++ + + + + + + ++ + , + document.getElementById('root') +); +``` + +- On pageB let's consume the SessionContext login field. + +_./src/pages/b/pageB.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; ++ import { SessionContext } from '../../common'; + +export const PageB = () => +
    ++ ++ { ++ ({login}) => ( ++ <> +

    Hello from page B

    ++
    ++

    Login: {login}

    +
    + Navigate to Login ++ ++ ) ++ } ++
    +
    +``` + +- If we run the sample we can navigate to page B and see the default login name being displayed. + +```bash +npm start +``` + +- Showing a default name is not a bad thing, but we need to display the real login name entered by the user, to do this we will expose a function into our context that will let any consumer update the value. + +- First let's add an update login method. + +_./src/common/sessionContext.tsx_ + +```diff +export interface SessionContextProps { + login: string; ++ updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', ++ updateLogin: (value) => {}, +}); +``` + +- Let's configure this in the provider state. + +```diff +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); +- this.state = createDefaultUser(); ++ this.state = { ++ login: createDefaultUser().login, ++ updateLogin: this.setLoginInfo, ++ } + } + ++ setLoginInfo = (newLogin) => { ++ this.setState({login: newLogin}); ++ } + + render() { + return ( + + {this.props.children} + + ); + } +} +``` + +- Time to set up this value when we click on the login button. + +- Let's add an import to our login page. + +_./src/pages/login/loginPage.tsx_ + +```diff ++ import { SessionContext } from '../../common'; +``` + +- Let's update our login component props to accept the updateLogin method. + +_./src/pages/login/loginPage.tsx_ + +```diff +interface Props extends RouteComponentProps, WithStyles { ++ updateLogin: (value) => void; +} +``` + +- We will create an intermediate component (in our next sample we will port it to a generic HoC). + +_./src/pages/login/loginPage.tsx_ + +```diff ++ export const LoginPageInner2 = (props) => ++ ++ { ++ ({updateLogin}) => ++ ++ } ++ + +- export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); ++ export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); +``` + +- Let's call the setLogin when the user clicks the button. + +_./src/pages/login/loginPage.tsx_ + +```diff +onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { ++ this.props.updateLogin(this.state.loginInfo.login); + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }); +} +``` + +- If we run the app we can check that now we get the right result. + +```bash +npm start +``` + +> If you have to nest many render props, you can end up having a heavy nested component, in that case checkout react-composer micro library (https://github.com/jamesplease/react-composer) \ No newline at end of file diff --git a/17_Context/package.json b/17_Context/package.json new file mode 100644 index 0000000..be1e062 --- /dev/null +++ b/17_Context/package.json @@ -0,0 +1,42 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "lc-form-validation": "^2.0.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-router-dom": "^4.3.1" + } +} diff --git a/17_Context/readme_es.md b/17_Context/readme_es.md new file mode 100644 index 0000000..3daf082 --- /dev/null +++ b/17_Context/readme_es.md @@ -0,0 +1,262 @@ +# Intro + +En este ejemplo vamos a aprender cómo funciona la api de context en React 16. + +Esto nos permitirá compartir información entre componentes sin tener que ir añadiendo propiedades por todo el árbol o tener que añadir redux a nuestro proyecto. + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en una terminal o consola. Las versiones anteriores pueden producir errores. + +Vamos a tomar como punto de partida el ejemplo _16 Validation_: + +## Pasos para construirlo + +- Queremos guardar el campo de _login_ una vez el usuario inicie sesión y mostrarlo en la página B (o en cualquier página o componente que lo necesite), vamos a añadir un valor por defecto ('no user'). + +- Empezaremos por crear un contexto, lo llamaremos _sessionContext_, y añadiremos los tipos apropiados + +_./src/common/sessionContext.tsx_ + +```javascript +import * as React from "react"; + +export interface SessionContextProps { + login: string; +} + +const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', +}); + +export const SessionContext = React.createContext(createDefaultUser()); +``` + +- Este contexto de sesión expondrá un _proveedor_ (que nos servirá para establecer el nombre de inicio de sesión en el contexto), y un _consumidor_ (que nos permitirá consumir el nombre de inicio de sesión del contexto en cualquier punto de la aplicación). +Crearemos un componente (lo llamaremos _SessionProvider_) que por un lado guardará en el estado el nombre del login y lo atará a _SessionContext_ y por otro lado acturá como un envoltorio (normalmente está al principio de la aplicación y envuelve la aplicación). + +_./src/common/sessionContext.tsx_ + +```diff +import * as React from "react"; + +export interface SessionContextProps { + login: string; +} + +const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', +}); + +export const SessionContext = React.createContext(createDefaultUser()); + ++ interface State extends SessionContextProps { ++ } ++ ++ export class SessionProvider extends React.Component<{}, State> { ++ ++ constructor(props) { ++ super(props); ++ this.state = createDefaultUser(); ++ } ++ ++ render() { ++ return ( ++ ++ {this.props.children} ++ ++ ); ++ } ++ } +``` + +- Vamos a añadirlo a un _index_ barrel común. + +_./src/common/index.tsx_ + +```diff +export * from './notification'; ++ export * from './sessionContext'; +``` + +- Es hora de exponer este proveedor al principio de nuestra aplicación. + +_./src/main.tsx_ + +```diff +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; ++ import { SessionProvider } from './common'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + ++ + + + + + + ++ + , + document.getElementById('root') +); +``` + +- En la página B consumiremos el campo login de SessionContext. + +_./src/pages/b/pageB.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; ++ import { SessionContext } from '../../common'; + +export const PageB = () => +
    ++ ++ { ++ ({login}) => ( ++ <> +

    Hello from page B

    ++
    ++

    Login: {login}

    +
    + Navigate to Login ++ ++ ) ++ } ++
    +
    +``` + +- Si ejecutamos el ejemplo podemos navegar a la página B y ver el nombre por defecto. + +```bash +npm start +``` + +- No es malo mostrar un nombre por defecto, pero necesitamos mostrar el nombre que introduzca el usuario, para hacer esto expondremos una función dentro de nuestro contexto que permitirá a cualquier consumidor actualizar el valor. + +- Primero añadiremos un método para actualizar el login. + +_./src/common/sessionContext.tsx_ + +```diff +export interface SessionContextProps { + login: string; ++ updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', ++ updateLogin: (value) => {}, +}); +``` + +- Vamos a configurar esto en el estado del proveedor. + +```diff +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); +- this.state = createDefaultUser(); ++ this.state = { ++ login: createDefaultUser().login, ++ updateLogin: this.setLoginInfo, ++ } + } + ++ setLoginInfo = (newLogin) => { ++ this.setState({login: newLogin}); ++ } + + render() { + return ( + + {this.props.children} + + ); + } +} +``` + +- Es hora de configurar este valor cuando hacemos clic en el botón de inicio de sesión. + +- Vamos a añadir un import a nuestra página de inicio de sesión. + +_./src/pages/login/loginPage.tsx_ + +```diff ++ import { SessionContext } from '../../common'; +``` + +- Vamos a actualizar las propiedades del componente login para aceptar el método updateLogin. + +_./src/pages/login/loginPage.tsx_ + +```diff +interface Props extends RouteComponentProps, WithStyles { ++ updateLogin: (value) => void; +} +``` + +- Vamos a crear un componente intermedio (en el siguiente ejemplo lo cambiaremos para que sea un HoC genérico). + +_./src/pages/login/loginPage.tsx_ + +```diff ++ export const LoginPageInner2 = (props) => ++ ++ { ++ ({updateLogin}) => ++ ++ } ++ + +- export const LoginPage = withStyles(styles)(withRouter((LoginPageInner))); ++ export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); +``` + +- Vamos a llamar al método setLogin cuando el usuario pulse el botón. + +_./src/pages/login/loginPage.tsx_ + +```diff +onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { ++ this.props.updateLogin(this.state.loginInfo.login); + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }); +} +``` + +- Si ejecutamos la aplicación podremos veremos el resultado adecuado. + +```bash +npm start +``` + +> Si tenemos que anidar muchos render props, podemos acabar teniendo mucho anidamiento en un componente, en ese caso echa un vistazo a la librería react-composer (https://github.com/jamesplease/react-composer). \ No newline at end of file diff --git a/17_Context/src/api/login.ts b/17_Context/src/api/login.ts new file mode 100644 index 0000000..1f7d4f3 --- /dev/null +++ b/17_Context/src/api/login.ts @@ -0,0 +1,5 @@ +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); diff --git a/17_Context/src/common/forms/textFieldForm.tsx b/17_Context/src/common/forms/textFieldForm.tsx new file mode 100644 index 0000000..808091c --- /dev/null +++ b/17_Context/src/common/forms/textFieldForm.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography/Typography"; + +interface Props { + name: string; + label: string; + onChange: any; + value: string; + error?: string; + type? : string; +} + +const defaultProps : Partial = { + type: 'text', +} + +const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => { + onChange(fieldId, e.target.value); +} + +export const TextFieldForm : React.StatelessComponent = (props) => { + const {name, label, onChange, value, error, type} = props; + + return ( + <> + + + {props.error} + + + ) +} diff --git a/17_Context/src/common/index.tsx b/17_Context/src/common/index.tsx new file mode 100644 index 0000000..8d8eeb0 --- /dev/null +++ b/17_Context/src/common/index.tsx @@ -0,0 +1,2 @@ +export * from './notification'; +export * from './sessionContext'; \ No newline at end of file diff --git a/17_Context/src/common/notification.tsx b/17_Context/src/common/notification.tsx new file mode 100644 index 0000000..5396faf --- /dev/null +++ b/17_Context/src/common/notification.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; + +interface Props extends WithStyles { + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => createStyles({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const { classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); diff --git a/17_Context/src/common/sessionContext.tsx b/17_Context/src/common/sessionContext.tsx new file mode 100644 index 0000000..c9060c9 --- /dev/null +++ b/17_Context/src/common/sessionContext.tsx @@ -0,0 +1,39 @@ +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; diff --git a/17_Context/src/hello.tsx b/17_Context/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/17_Context/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/17_Context/src/index.html b/17_Context/src/index.html new file mode 100644 index 0000000..0fcc01e --- /dev/null +++ b/17_Context/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
    +
    +
    + + diff --git a/17_Context/src/main.tsx b/17_Context/src/main.tsx new file mode 100644 index 0000000..247c55f --- /dev/null +++ b/17_Context/src/main.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import { SessionProvider } from './common'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + + + + + + + + + + + , document.getElementById('root') +); diff --git a/17_Context/src/model/login.ts b/17_Context/src/model/login.ts new file mode 100644 index 0000000..081e6fb --- /dev/null +++ b/17_Context/src/model/login.ts @@ -0,0 +1,9 @@ +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); diff --git a/17_Context/src/nameEdit.tsx b/17_Context/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/17_Context/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/17_Context/src/pages/b/index.ts b/17_Context/src/pages/b/index.ts new file mode 100644 index 0000000..913631b --- /dev/null +++ b/17_Context/src/pages/b/index.ts @@ -0,0 +1 @@ +export {PageB} from './pageB'; \ No newline at end of file diff --git a/17_Context/src/pages/b/pageB.tsx b/17_Context/src/pages/b/pageB.tsx new file mode 100644 index 0000000..5606910 --- /dev/null +++ b/17_Context/src/pages/b/pageB.tsx @@ -0,0 +1,21 @@ +import * as React from "react" +import { Link } from 'react-router-dom'; +import { SessionContext } from '../../common/' + +export const PageB = () => +
    + + { + ({ login }) => ( + <> +

    Hello from page B

    +
    +
    +

    Login: {login}

    + + Navigate to Login + + ) + } +
    +
    diff --git a/17_Context/src/pages/login/index.ts b/17_Context/src/pages/login/index.ts new file mode 100644 index 0000000..50d85bb --- /dev/null +++ b/17_Context/src/pages/login/index.ts @@ -0,0 +1 @@ +export {LoginPage} from './loginPage'; \ No newline at end of file diff --git a/17_Context/src/pages/login/loginForm.tsx b/17_Context/src/pages/login/loginForm.tsx new file mode 100644 index 0000000..eda83bd --- /dev/null +++ b/17_Context/src/pages/login/loginForm.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity } from "../../model/login"; +import {LoginFormErrors} from './viewmodel'; +import { TextFieldForm } from '../../common/forms/textFieldForm'; + +interface Props { + onLogin: () => void; + onUpdateField: (string, any) => void; + loginInfo : LoginEntity; + loginFormErrors : LoginFormErrors; +} + +export const LoginForm = (props: Props) => { + const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; + + const onTexFieldChange = (fieldId) => (e) => { + onUpdateField(fieldId, e.target.value); + } + + return ( +
    + + + +
    + ) +} diff --git a/17_Context/src/pages/login/loginPage.tsx b/17_Context/src/pages/login/loginPage.tsx new file mode 100644 index 0000000..5742172 --- /dev/null +++ b/17_Context/src/pages/login/loginPage.tsx @@ -0,0 +1,120 @@ +import * as React from "react" +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; +import { NotificationComponent } from '../../common' +import { LoginFormErrors, createDefaultLoginFormErrors } from './viewmodel'; +import { loginFormValidation } from './loginValidations'; +import {SessionContext} from '../../common'; +import { Session } from "inspector"; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; + loginFormErrors: LoginFormErrors; +} + +interface Props extends RouteComponentProps, WithStyles { + updateLogin: (value) => void +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { + loginInfo: createEmptyLogin(), + showLoginFailedMsg: false, + loginFormErrors: createDefaultLoginFormErrors(), + } + } + + + onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { + this.props.updateLogin(this.state.loginInfo.login); + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }) + } + + onUpdateLoginField = (name: string, value) => { + this.setState({loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }); + + loginFormValidation.validateField(this.state.loginInfo, name, value).then( + (fieldValidationResult) => { + this.setState({ + loginFormErrors: { + ...this.state.loginFormErrors, + [name]: fieldValidationResult, + } + }); + } + ); + } + + render() { + const { classes } = this.props; + return ( + <> + + + + + + + this.setState({ showLoginFailedMsg: false })} + /> + + ) + + } +} + +export const LoginPageInner2 = (props) => + <> + + { + ({updateLogin}) => + + } + + + + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); diff --git a/17_Context/src/pages/login/loginValidations.ts b/17_Context/src/pages/login/loginValidations.ts new file mode 100644 index 0000000..d0eee27 --- /dev/null +++ b/17_Context/src/pages/login/loginValidations.ts @@ -0,0 +1,16 @@ +import { + createFormValidation, ValidationConstraints, Validators, +} from 'lc-form-validation'; + +const loginFormValidationConstraints: ValidationConstraints = { + fields: { + login: [ + { validator: Validators.required }, + ], + password: [ + { validator: Validators.required }, + ], + }, +}; + +export const loginFormValidation = createFormValidation(loginFormValidationConstraints); diff --git a/17_Context/src/pages/login/viewmodel.ts b/17_Context/src/pages/login/viewmodel.ts new file mode 100644 index 0000000..b374fa6 --- /dev/null +++ b/17_Context/src/pages/login/viewmodel.ts @@ -0,0 +1,11 @@ +import { FieldValidationResult } from 'lc-form-validation'; + +export interface LoginFormErrors { + login: FieldValidationResult; + password: FieldValidationResult; +} + +export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ + login: new FieldValidationResult(), + password: new FieldValidationResult(), +}); diff --git a/17_Context/tsconfig.json b/17_Context/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/17_Context/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/17_Context/webpack.config.js b/17_Context/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/17_Context/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/18_Hoc/.babelrc b/18_Hoc/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/18_Hoc/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/18_Hoc/Readme.md b/18_Hoc/Readme.md new file mode 100644 index 0000000..41c6af3 --- /dev/null +++ b/18_Hoc/Readme.md @@ -0,0 +1,75 @@ +## Hoc + +We are going to implement a High Order Component,this let us extract common functionallity and expose it via composition. + +We will take a startup point sample _17 Context_: + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +In the previous sample we had to create an intermediate _LoginPageInner2_ component in order to inject to the component the _loginInfo_ and _setLoginInfo_ fields from the Session context. + +This boilerplate is a bit ugly, and it would be worse if we want to access that info from other pages. + +By implementing an Hoc we can just create a reusable function that will make easier to access the SessionContext consumer. + +- We will start by creating our Hoc (let's add this at the bottom of the file). + +_./src/common/sessionContext.tsx_ + +```javascript +export const withSessionContext = (Component) => (props) => ( + + { + ({ login, updateLogin }) => ( + + ) + } + +); +``` + +- Now let's import it in our loginPage. + +_./src/pages/login/loginPage.tsx_ + +```diff +- import { SessionContext } from '../../common'; ++ import { SessionContext, withSessionContext } from '../../common'; +``` + +- And let's remove LoginPageInner2 and add our Hoc: + +_./src/pages/login/loginPage.tsx_ + +```diff +- export const LoginPageInner2 = (props) => +- <> +- +- { +- ({updateLogin}) => +- +- } +- +- +- + +- export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); ++ export const LoginPage = withSessionContext(withStyles(styles)(withRouter((LoginPageInner)))); +``` + +- We can run and check that the sample is working. + +> As an excercise create a Page C and make use of the Hoc. + +> Nesting HOC's can make code difficult to read, we can use lodash flow +to alleviate this. diff --git a/18_Hoc/package.json b/18_Hoc/package.json new file mode 100644 index 0000000..be1e062 --- /dev/null +++ b/18_Hoc/package.json @@ -0,0 +1,42 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "lc-form-validation": "^2.0.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-router-dom": "^4.3.1" + } +} diff --git a/18_Hoc/readme_es.md b/18_Hoc/readme_es.md new file mode 100644 index 0000000..f2c365c --- /dev/null +++ b/18_Hoc/readme_es.md @@ -0,0 +1,74 @@ +## Hoc + +Vamos a implementar a High Order Component, esto permitirá extraer funcionalidad común y exponerla via composición. + +Vamos a tomar como punto de partida el ejemplo _17 Context_: + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en una terminal o consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +En el ejemplo anterior tuvimos que crear un componente _LoginPageInner2_ intermedio para inyectar al componente los campos _loginInfo_ y _setLoginInfo_ de la sesión de contexto. + +Este boilerplate es un poco feo, y sería peor si queremos acceder a esa información desde otras páginas. + +Al implementar un Hoc podemos crear una función reusable que facilitará el acceso al consumidor SessionContext. + +- Empezaremospor crear nuestro Hoc (vamos a añadir esto a abajo del fichero). + +_./src/common/sessionContext.tsx_ + +```javascript +export const withSessionContext = (Component) => (props) => ( + + { + ({ login, updateLogin }) => ( + + ) + } + +); +``` + +- Ahora vamosm a importar esto en nuestro loginPage. + +_./src/pages/login/loginPage.tsx_ + +```diff +- import { SessionContext } from '../../common'; ++ import { SessionContext, withSessionContext } from '../../common'; +``` + +- Y vamos a borrar LoginPageInner2 y añadir nuestro Hoc: + +_./src/pages/login/loginPage.tsx_ + +```diff +- export const LoginPageInner2 = (props) => +- <> +- +- { +- ({updateLogin}) => +- +- } +- +- +- + +- export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); ++ export const LoginPage = withSessionContext(withStyles(styles)(withRouter((LoginPageInner)))); +``` + +- Podemos ejecutar y verificar que el ejemplo está funcionando. + +> Como un ejercicio crea una página C y haz uso del Hoc. + +> Anidando HOCS podemos hacer código difícil de leer, nosotros podemos usar lodash flow para mitigar esto. \ No newline at end of file diff --git a/18_Hoc/src/api/login.ts b/18_Hoc/src/api/login.ts new file mode 100644 index 0000000..1f7d4f3 --- /dev/null +++ b/18_Hoc/src/api/login.ts @@ -0,0 +1,5 @@ +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); diff --git a/18_Hoc/src/common/forms/textFieldForm.tsx b/18_Hoc/src/common/forms/textFieldForm.tsx new file mode 100644 index 0000000..808091c --- /dev/null +++ b/18_Hoc/src/common/forms/textFieldForm.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography/Typography"; + +interface Props { + name: string; + label: string; + onChange: any; + value: string; + error?: string; + type? : string; +} + +const defaultProps : Partial = { + type: 'text', +} + +const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => { + onChange(fieldId, e.target.value); +} + +export const TextFieldForm : React.StatelessComponent = (props) => { + const {name, label, onChange, value, error, type} = props; + + return ( + <> + + + {props.error} + + + ) +} diff --git a/18_Hoc/src/common/index.tsx b/18_Hoc/src/common/index.tsx new file mode 100644 index 0000000..8d8eeb0 --- /dev/null +++ b/18_Hoc/src/common/index.tsx @@ -0,0 +1,2 @@ +export * from './notification'; +export * from './sessionContext'; \ No newline at end of file diff --git a/18_Hoc/src/common/notification.tsx b/18_Hoc/src/common/notification.tsx new file mode 100644 index 0000000..5396faf --- /dev/null +++ b/18_Hoc/src/common/notification.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; + +interface Props extends WithStyles { + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => createStyles({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const { classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); diff --git a/18_Hoc/src/common/sessionContext.tsx b/18_Hoc/src/common/sessionContext.tsx new file mode 100644 index 0000000..9bb90d4 --- /dev/null +++ b/18_Hoc/src/common/sessionContext.tsx @@ -0,0 +1,53 @@ +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + +export const withSessionContext = (Component) => (props) => ( + + { + ({ login, updateLogin }) => ( + + ) + } + +); diff --git a/18_Hoc/src/hello.tsx b/18_Hoc/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/18_Hoc/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/18_Hoc/src/index.html b/18_Hoc/src/index.html new file mode 100644 index 0000000..0fcc01e --- /dev/null +++ b/18_Hoc/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
    +
    +
    + + diff --git a/18_Hoc/src/main.tsx b/18_Hoc/src/main.tsx new file mode 100644 index 0000000..247c55f --- /dev/null +++ b/18_Hoc/src/main.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import { SessionProvider } from './common'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + + + + + + + + + + + , document.getElementById('root') +); diff --git a/18_Hoc/src/model/login.ts b/18_Hoc/src/model/login.ts new file mode 100644 index 0000000..081e6fb --- /dev/null +++ b/18_Hoc/src/model/login.ts @@ -0,0 +1,9 @@ +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); diff --git a/18_Hoc/src/nameEdit.tsx b/18_Hoc/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/18_Hoc/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/18_Hoc/src/pages/b/index.ts b/18_Hoc/src/pages/b/index.ts new file mode 100644 index 0000000..913631b --- /dev/null +++ b/18_Hoc/src/pages/b/index.ts @@ -0,0 +1 @@ +export {PageB} from './pageB'; \ No newline at end of file diff --git a/18_Hoc/src/pages/b/pageB.tsx b/18_Hoc/src/pages/b/pageB.tsx new file mode 100644 index 0000000..1c63c13 --- /dev/null +++ b/18_Hoc/src/pages/b/pageB.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import { Link } from 'react-router-dom'; +import { SessionContext, withSessionContext } from '../../common/' + +interface Props { + login : string; +} + +const PageBInner = (props : Props) => + <> +

    Hello from page B

    +
    +
    +

    Login: {props.login}

    + + Navigate to Login + + +export const PageB = withSessionContext(PageBInner); \ No newline at end of file diff --git a/18_Hoc/src/pages/login/index.ts b/18_Hoc/src/pages/login/index.ts new file mode 100644 index 0000000..50d85bb --- /dev/null +++ b/18_Hoc/src/pages/login/index.ts @@ -0,0 +1 @@ +export {LoginPage} from './loginPage'; \ No newline at end of file diff --git a/18_Hoc/src/pages/login/loginForm.tsx b/18_Hoc/src/pages/login/loginForm.tsx new file mode 100644 index 0000000..eda83bd --- /dev/null +++ b/18_Hoc/src/pages/login/loginForm.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity } from "../../model/login"; +import {LoginFormErrors} from './viewmodel'; +import { TextFieldForm } from '../../common/forms/textFieldForm'; + +interface Props { + onLogin: () => void; + onUpdateField: (string, any) => void; + loginInfo : LoginEntity; + loginFormErrors : LoginFormErrors; +} + +export const LoginForm = (props: Props) => { + const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; + + const onTexFieldChange = (fieldId) => (e) => { + onUpdateField(fieldId, e.target.value); + } + + return ( +
    + + + +
    + ) +} diff --git a/18_Hoc/src/pages/login/loginPage.tsx b/18_Hoc/src/pages/login/loginPage.tsx new file mode 100644 index 0000000..a84fc55 --- /dev/null +++ b/18_Hoc/src/pages/login/loginPage.tsx @@ -0,0 +1,109 @@ +import * as React from "react" +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; +import { NotificationComponent } from '../../common' +import { LoginFormErrors, createDefaultLoginFormErrors } from './viewmodel'; +import { loginFormValidation } from './loginValidations'; +import {SessionContext, withSessionContext} from '../../common'; +import { Session } from "inspector"; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; + loginFormErrors: LoginFormErrors; +} + +interface Props extends RouteComponentProps, WithStyles { + updateLogin: (value) => void +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { + loginInfo: createEmptyLogin(), + showLoginFailedMsg: false, + loginFormErrors: createDefaultLoginFormErrors(), + } + } + + + onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { + this.props.updateLogin(this.state.loginInfo.login); + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }) + } + + onUpdateLoginField = (name: string, value) => { + this.setState({loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }); + + loginFormValidation.validateField(this.state.loginInfo, name, value).then( + (fieldValidationResult) => { + this.setState({ + loginFormErrors: { + ...this.state.loginFormErrors, + [name]: fieldValidationResult, + } + }); + } + ); + } + + render() { + const { classes } = this.props; + return ( + <> + + + + + + + this.setState({ showLoginFailedMsg: false })} + /> + + ) + + } +} + +export const LoginPage = withSessionContext(withStyles(styles)(withRouter((LoginPageInner)))); diff --git a/18_Hoc/src/pages/login/loginValidations.ts b/18_Hoc/src/pages/login/loginValidations.ts new file mode 100644 index 0000000..d0eee27 --- /dev/null +++ b/18_Hoc/src/pages/login/loginValidations.ts @@ -0,0 +1,16 @@ +import { + createFormValidation, ValidationConstraints, Validators, +} from 'lc-form-validation'; + +const loginFormValidationConstraints: ValidationConstraints = { + fields: { + login: [ + { validator: Validators.required }, + ], + password: [ + { validator: Validators.required }, + ], + }, +}; + +export const loginFormValidation = createFormValidation(loginFormValidationConstraints); diff --git a/18_Hoc/src/pages/login/viewmodel.ts b/18_Hoc/src/pages/login/viewmodel.ts new file mode 100644 index 0000000..b374fa6 --- /dev/null +++ b/18_Hoc/src/pages/login/viewmodel.ts @@ -0,0 +1,11 @@ +import { FieldValidationResult } from 'lc-form-validation'; + +export interface LoginFormErrors { + login: FieldValidationResult; + password: FieldValidationResult; +} + +export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ + login: new FieldValidationResult(), + password: new FieldValidationResult(), +}); diff --git a/18_Hoc/tsconfig.json b/18_Hoc/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/18_Hoc/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/18_Hoc/webpack.config.js b/18_Hoc/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/18_Hoc/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/19_RenderProps/.babelrc b/19_RenderProps/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/19_RenderProps/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/19_RenderProps/Readme.md b/19_RenderProps/Readme.md new file mode 100644 index 0000000..24e4b93 --- /dev/null +++ b/19_RenderProps/Readme.md @@ -0,0 +1,157 @@ +## Intro + +In this sample we are going to learn how use render props in React. + +In this case we will implement a component that will inject the session to other components via render props. + +The main advantage of using this approach instead of HOC is that the child component gets a clear contract of the props it receives. + +We will take a startup point sample _18 Hoc_: + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Let's first add the component that will expose the render prop, we will append it to +the _sessionContext_ file. + +_./src/common/sessionContext.tsx_ + +```diff +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + ++ interface Props { ++ render : (login : string) => React.ReactNode; ++ } ++ ++ export class Session extends React.Component { ++ constructor(props : Props) { ++ super(props); ++ } ++ ++ render() { ++ return ( ++ ++ { ++ ({ login, updateLogin }) => ++ <> ++ {this.props.render(login)} ++ ++ } ++ ++ ) ++ } ++ } +``` + +- Now in the _pageB.tsx_ we can invoke it like that (first approach): + +_./src/pages/b/pageB.tsx_ + +```jsx +import * as React from "react" +import { Link } from 'react-router-dom'; +import { Session } from '../../common/' +import { checkPropTypes } from "prop-types"; + +export const PageB = () => +
    + ( + <> +

    Hello from page B

    +
    +
    +

    Login: {login}

    + + Navigate to Login + + )} + > +
    +
    +``` + +- Let's add one refactor to make the code more readable: + +_./src/pages/b/pageB.tsx_ + +```jsx +import * as React from "react" +import { Link } from 'react-router-dom'; +import { Session } from '../../common/' +import { checkPropTypes } from "prop-types"; + + +interface Props { + login : string; +} + +const PageBComponent = (props: Props) => + <> +

    Hello from page B

    +
    +
    +

    Login: {props.login}

    + + Navigate to Login + + + +export const PageB = () => +
    + ( + + )} + > + +
    +``` + +> If you need to nest several render props, you can use _react-composer_: https://github.com/jamesplease/react-composer + diff --git a/19_RenderProps/package.json b/19_RenderProps/package.json new file mode 100644 index 0000000..be1e062 --- /dev/null +++ b/19_RenderProps/package.json @@ -0,0 +1,42 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "lc-form-validation": "^2.0.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2", + "react-router-dom": "^4.3.1" + } +} diff --git a/19_RenderProps/readme_es.md b/19_RenderProps/readme_es.md new file mode 100644 index 0000000..60caea4 --- /dev/null +++ b/19_RenderProps/readme_es.md @@ -0,0 +1,155 @@ +## Introducción + +En este ejemplo vamos a aprender como usar render props en React. + +En este caso implementaremos un componente que inyectará la sesión a otros componentes vía render props. + +El principal ventaja de usar este aproximación en lugar de HOC es que el componente hijo obtiene un contrato de las propiedades que recibe. + +Tomaremos como punto de entrada el ejemplo _18 Hoc_: + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en una terminal o consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Primero vamos a añadir el componente que expondrá el reder prop, lo añadiremos al fichero _sessionContext_. + +_./src/common/sessionContext.tsx_ + +```diff +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + ++ interface Props { ++ render : (login : string) => React.ReactNode; ++ } ++ ++ export class Session extends React.Component { ++ constructor(props : Props) { ++ super(props); ++ } ++ ++ render() { ++ return ( ++ ++ { ++ ({ login, updateLogin }) => ++ <> ++ {this.props.render(login)} ++ ++ } ++ ++ ) ++ } ++ } +``` + +- Ahora en _pageB.tsx_ podemos invocarlo como (primera aproximación): + +_./src/pages/b/pageB.tsx_ + +```jsx +import * as React from "react" +import { Link } from 'react-router-dom'; +import { Session } from '../../common/' +import { checkPropTypes } from "prop-types"; + +export const PageB = () => +
    + ( + <> +

    Hello from page B

    +
    +
    +

    Login: {login}

    + + Navigate to Login + + )} + > +
    +
    +``` + +- Vamos a añadir una refactorización para hacer el código más legible: + +_./src/pages/b/pageB.tsx_ + +```jsx +import * as React from "react" +import { Link } from 'react-router-dom'; +import { Session } from '../../common/' +import { checkPropTypes } from "prop-types"; + + +interface Props { + login : string; +} + +const LoginComponent = (props: Props) => + <> +

    Hello from page B

    +
    +
    +

    Login: {props.login}

    + + Navigate to Login + + + +export const PageB = () => +
    + ( + + )} + > + +
    +``` + +- Si necesitas anidar muchas reder props, puedes usar _react-composer_: https://github.com/jamesplease/react-composer \ No newline at end of file diff --git a/19_RenderProps/src/api/login.ts b/19_RenderProps/src/api/login.ts new file mode 100644 index 0000000..1f7d4f3 --- /dev/null +++ b/19_RenderProps/src/api/login.ts @@ -0,0 +1,5 @@ +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); diff --git a/19_RenderProps/src/common/forms/textFieldForm.tsx b/19_RenderProps/src/common/forms/textFieldForm.tsx new file mode 100644 index 0000000..808091c --- /dev/null +++ b/19_RenderProps/src/common/forms/textFieldForm.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography/Typography"; + +interface Props { + name: string; + label: string; + onChange: any; + value: string; + error?: string; + type? : string; +} + +const defaultProps : Partial = { + type: 'text', +} + +const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => { + onChange(fieldId, e.target.value); +} + +export const TextFieldForm : React.StatelessComponent = (props) => { + const {name, label, onChange, value, error, type} = props; + + return ( + <> + + + {props.error} + + + ) +} diff --git a/19_RenderProps/src/common/index.tsx b/19_RenderProps/src/common/index.tsx new file mode 100644 index 0000000..8d8eeb0 --- /dev/null +++ b/19_RenderProps/src/common/index.tsx @@ -0,0 +1,2 @@ +export * from './notification'; +export * from './sessionContext'; \ No newline at end of file diff --git a/19_RenderProps/src/common/notification.tsx b/19_RenderProps/src/common/notification.tsx new file mode 100644 index 0000000..5396faf --- /dev/null +++ b/19_RenderProps/src/common/notification.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; + +interface Props extends WithStyles { + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => createStyles({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const { classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); diff --git a/19_RenderProps/src/common/sessionContext.tsx b/19_RenderProps/src/common/sessionContext.tsx new file mode 100644 index 0000000..f83ed61 --- /dev/null +++ b/19_RenderProps/src/common/sessionContext.tsx @@ -0,0 +1,62 @@ +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + +interface Props { + render : (login : string) => React.ReactNode; +} + +export class Session extends React.Component { + constructor(props : Props) { + super(props); + } + + render() { + return ( + + { + ({ login, updateLogin }) => + <> + {this.props.render(login)} + + } + + ) + } +} diff --git a/19_RenderProps/src/hello.tsx b/19_RenderProps/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/19_RenderProps/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/19_RenderProps/src/index.html b/19_RenderProps/src/index.html new file mode 100644 index 0000000..0fcc01e --- /dev/null +++ b/19_RenderProps/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
    +
    +
    + + diff --git a/19_RenderProps/src/main.tsx b/19_RenderProps/src/main.tsx new file mode 100644 index 0000000..247c55f --- /dev/null +++ b/19_RenderProps/src/main.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import { SessionProvider } from './common'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + + + + + + + + + + + , document.getElementById('root') +); diff --git a/19_RenderProps/src/model/login.ts b/19_RenderProps/src/model/login.ts new file mode 100644 index 0000000..081e6fb --- /dev/null +++ b/19_RenderProps/src/model/login.ts @@ -0,0 +1,9 @@ +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); diff --git a/19_RenderProps/src/nameEdit.tsx b/19_RenderProps/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/19_RenderProps/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/19_RenderProps/src/pages/b/index.ts b/19_RenderProps/src/pages/b/index.ts new file mode 100644 index 0000000..913631b --- /dev/null +++ b/19_RenderProps/src/pages/b/index.ts @@ -0,0 +1 @@ +export {PageB} from './pageB'; \ No newline at end of file diff --git a/19_RenderProps/src/pages/b/pageB.tsx b/19_RenderProps/src/pages/b/pageB.tsx new file mode 100644 index 0000000..1f2258d --- /dev/null +++ b/19_RenderProps/src/pages/b/pageB.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import { Link } from 'react-router-dom'; +import { Session } from '../../common/' +import { checkPropTypes } from "prop-types"; + + +interface Props { + login : string; +} + +const LoginComponent = (props: Props) => + <> +

    Hello from page B

    +
    +
    +

    Login: {props.login}

    + + Navigate to Login + + + +export const PageB = () => +
    + ( + + )} + > + +
    diff --git a/19_RenderProps/src/pages/login/index.ts b/19_RenderProps/src/pages/login/index.ts new file mode 100644 index 0000000..50d85bb --- /dev/null +++ b/19_RenderProps/src/pages/login/index.ts @@ -0,0 +1 @@ +export {LoginPage} from './loginPage'; \ No newline at end of file diff --git a/19_RenderProps/src/pages/login/loginForm.tsx b/19_RenderProps/src/pages/login/loginForm.tsx new file mode 100644 index 0000000..eda83bd --- /dev/null +++ b/19_RenderProps/src/pages/login/loginForm.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity } from "../../model/login"; +import {LoginFormErrors} from './viewmodel'; +import { TextFieldForm } from '../../common/forms/textFieldForm'; + +interface Props { + onLogin: () => void; + onUpdateField: (string, any) => void; + loginInfo : LoginEntity; + loginFormErrors : LoginFormErrors; +} + +export const LoginForm = (props: Props) => { + const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; + + const onTexFieldChange = (fieldId) => (e) => { + onUpdateField(fieldId, e.target.value); + } + + return ( +
    + + + +
    + ) +} diff --git a/19_RenderProps/src/pages/login/loginPage.tsx b/19_RenderProps/src/pages/login/loginPage.tsx new file mode 100644 index 0000000..5742172 --- /dev/null +++ b/19_RenderProps/src/pages/login/loginPage.tsx @@ -0,0 +1,120 @@ +import * as React from "react" +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; +import { NotificationComponent } from '../../common' +import { LoginFormErrors, createDefaultLoginFormErrors } from './viewmodel'; +import { loginFormValidation } from './loginValidations'; +import {SessionContext} from '../../common'; +import { Session } from "inspector"; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; + loginFormErrors: LoginFormErrors; +} + +interface Props extends RouteComponentProps, WithStyles { + updateLogin: (value) => void +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { + loginInfo: createEmptyLogin(), + showLoginFailedMsg: false, + loginFormErrors: createDefaultLoginFormErrors(), + } + } + + + onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { + this.props.updateLogin(this.state.loginInfo.login); + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }) + } + + onUpdateLoginField = (name: string, value) => { + this.setState({loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }); + + loginFormValidation.validateField(this.state.loginInfo, name, value).then( + (fieldValidationResult) => { + this.setState({ + loginFormErrors: { + ...this.state.loginFormErrors, + [name]: fieldValidationResult, + } + }); + } + ); + } + + render() { + const { classes } = this.props; + return ( + <> + + + + + + + this.setState({ showLoginFailedMsg: false })} + /> + + ) + + } +} + +export const LoginPageInner2 = (props) => + <> + + { + ({updateLogin}) => + + } + + + + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); diff --git a/19_RenderProps/src/pages/login/loginValidations.ts b/19_RenderProps/src/pages/login/loginValidations.ts new file mode 100644 index 0000000..d0eee27 --- /dev/null +++ b/19_RenderProps/src/pages/login/loginValidations.ts @@ -0,0 +1,16 @@ +import { + createFormValidation, ValidationConstraints, Validators, +} from 'lc-form-validation'; + +const loginFormValidationConstraints: ValidationConstraints = { + fields: { + login: [ + { validator: Validators.required }, + ], + password: [ + { validator: Validators.required }, + ], + }, +}; + +export const loginFormValidation = createFormValidation(loginFormValidationConstraints); diff --git a/19_RenderProps/src/pages/login/viewmodel.ts b/19_RenderProps/src/pages/login/viewmodel.ts new file mode 100644 index 0000000..b374fa6 --- /dev/null +++ b/19_RenderProps/src/pages/login/viewmodel.ts @@ -0,0 +1,11 @@ +import { FieldValidationResult } from 'lc-form-validation'; + +export interface LoginFormErrors { + login: FieldValidationResult; + password: FieldValidationResult; +} + +export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ + login: new FieldValidationResult(), + password: new FieldValidationResult(), +}); diff --git a/19_RenderProps/tsconfig.json b/19_RenderProps/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/19_RenderProps/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/19_RenderProps/webpack.config.js b/19_RenderProps/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/19_RenderProps/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/20_ErrorBoundaries/.babelrc b/20_ErrorBoundaries/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/20_ErrorBoundaries/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/20_ErrorBoundaries/package.json b/20_ErrorBoundaries/package.json new file mode 100644 index 0000000..63b6302 --- /dev/null +++ b/20_ErrorBoundaries/package.json @@ -0,0 +1,37 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.5.2", + "react-dom": "^16.5.2" + } +} diff --git a/20_ErrorBoundaries/readme.md b/20_ErrorBoundaries/readme.md new file mode 100644 index 0000000..8b3a3dc --- /dev/null +++ b/20_ErrorBoundaries/readme.md @@ -0,0 +1,171 @@ +## 20 Error Boundaries + +In this example we will play with the Error Boundary concept. + + +## Summary steps: + + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Copy the content from _03 State_ and execute `npm install`. + +- Let's create a faulty component: + +_./src/faultyComponent.tsx_ + +```jsx +import * as React from 'react'; + +export class FaultyComponent extends React.Component { + componentDidMount() { + throw "I'm the faulty component, generating a bad crash." + } + + render() { + return ( +

    Hello from Faulty Component

    + ) + } +} +``` + +- Let's instantiate this component in our _app.tsx_ + +_./src/app.tsx_ + +```diff +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; ++ import { FaultyComponent } from './faultyComponent'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + ++ + + ); + } +} +``` + +- Let's run the app. + +```bash +npm start +``` + +- If you open the console you will see a bad crash being reported, that's something that you wouldn't like to suffer when you are using plugins and other components that you may not trust, why not wrap any error in a safe area ? +and display a friendly component failed to load in case of an uncontroller error happen in that area (and keep the rest of application working as expected). + +- Let's create an Error Boundary. + +_./src/erroBoundary.tsx_ + +```jsx +import * as React from 'React'; + +export class ErrorBoundary extends React.Component { + state = { error: null, errorInfo: null }; + + componentDidCatch(error, errorInfo) { + this.setState({ + error: error, + errorInfo: errorInfo + }); + } + + render() { + if (this.state.errorInfo) { + return ( +
    +

    Plugin Failed to load, optional error info:

    +
    + {this.state.error && this.state.error.toString()} +
    + {this.state.errorInfo.componentStack} +
    +
    + ); + } + + return this.props.children; + } +} +``` + +- And let's wrap our faultyComponent inside this error boundary (we could wrap a set of components if needed). + +_./src/app.tsx_ + +```diff +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; +import { FaultyComponent } from './faultyComponent'; ++ import { ErrorBoundary } from './errorBoundary'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + ++ + ++ + + ); + } +} +``` + +> There's a nice generic wrapper for this ErrorBoundary: https://github.com/bvaughn/react-error-boundary + +> Error boundaries and event handlers: https://github.com/facebook/react/issues/11409 \ No newline at end of file diff --git a/20_ErrorBoundaries/readme_es.md b/20_ErrorBoundaries/readme_es.md new file mode 100644 index 0000000..7c1cd8d --- /dev/null +++ b/20_ErrorBoundaries/readme_es.md @@ -0,0 +1,168 @@ +## 20 Error Boundaries + +En este ejemplo jugaremos con el concepto de barrera de errores. + +## Resumen de pasos: + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en una terminal o consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Copia el contenido de _03 State_ y ejecuta `npm install`. + +- Vamos a crear un componente defectuoso: + +_./src/faultyComponent.tsx_ + +```jsx +import * as React from 'react'; + +export class FaultyComponent extends React.Component { + componentDidMount() { + throw "I'm the faulty component, generating a bad crash." + } + + render() { + return ( +

    Hello from Faulty Component

    + ) + } +} +``` + +- Vamos a instanciar este componente en nuestro _app.tsx_ + +_./src/app.tsx_ + +```diff +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; ++ import { FaultyComponent } from './faultyComponent'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + ++ + + ); + } +} +``` + +- Vamos a ejecutar app + +```bash +npm start +``` + +- Si abres la consola verás que se informa de un fallo grave, eso es algo que no te gustaría sufrir cuando usas plugins y otros componentes en los que no confies, ¿por que no envolver cualquier error en un área segura?y mostrar un componente amigable que no se pudo cargar en caso de que ocurra un error de descontrolador en esa área (y mantener el resto de la aplicación funcionando como se esperaba). + +- Vamos a crear una barrera de error. + +_./src/erroBoundary.tsx_ + +```jsx +import * as React from 'React'; + +export class ErrorBoundary extends React.Component { + state = { error: null, errorInfo: null }; + + componentDidCatch(error, errorInfo) { + this.setState({ + error: error, + errorInfo: errorInfo + }); + } + + render() { + if (this.state.errorInfo) { + return ( +
    +

    Plugin Failed to load, optional error info:

    +
    + {this.state.error && this.state.error.toString()} +
    + {this.state.errorInfo.componentStack} +
    +
    + ); + } + + return this.props.children; + } +} +``` + +- Y envolvamos nuestro faultyComponent dentro de esta barrera de error (podriamos envolver un conjunto de componentes si es necesario). + +_./src/app.tsx_ + +```diff +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; +import { FaultyComponent } from './faultyComponent'; ++ import { ErrorBoundary } from './errorBoundary'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + ++ + ++ + + ); + } +} +``` + +> Hay un bonito envoltorio genérico para este ErrorBoundary: https://github.com/bvaughn/react-error-boundary + +> Barreras de error y manejador de enventos: https://github.com/facebook/react/issues/11409 \ No newline at end of file diff --git a/20_ErrorBoundaries/src/app.tsx b/20_ErrorBoundaries/src/app.tsx new file mode 100644 index 0000000..7f42f20 --- /dev/null +++ b/20_ErrorBoundaries/src/app.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { HelloComponent } from './hello'; +import { NameEditComponent } from './nameEdit'; +import { FaultyComponent } from './faultyComponent'; +import { ErrorBoundary } from './errorBoundary'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + + + + + + ); + } +} \ No newline at end of file diff --git a/20_ErrorBoundaries/src/errorBoundary.tsx b/20_ErrorBoundaries/src/errorBoundary.tsx new file mode 100644 index 0000000..4658af0 --- /dev/null +++ b/20_ErrorBoundaries/src/errorBoundary.tsx @@ -0,0 +1,29 @@ +import * as React from 'React'; + +export class ErrorBoundary extends React.Component { + state = { error: null, errorInfo: null }; + + componentDidCatch(error, errorInfo) { + this.setState({ + error: error, + errorInfo: errorInfo + }); + } + + render() { + if (this.state.errorInfo) { + return ( +
    +

    Plugin Failed to load, optional error info:

    +
    + {this.state.error && this.state.error.toString()} +
    + {this.state.errorInfo.componentStack} +
    +
    + ); + } + + return this.props.children; + } +} \ No newline at end of file diff --git a/20_ErrorBoundaries/src/faultyComponent.tsx b/20_ErrorBoundaries/src/faultyComponent.tsx new file mode 100644 index 0000000..4d4f7ed --- /dev/null +++ b/20_ErrorBoundaries/src/faultyComponent.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; + +export class FaultyComponent extends React.Component { + componentDidMount() { + throw "I'm the faulty component, generating a bad crash." + } + + render() { + return ( +

    Hello from Faulty Component

    + ) + } +} \ No newline at end of file diff --git a/20_ErrorBoundaries/src/hello.tsx b/20_ErrorBoundaries/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/20_ErrorBoundaries/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/20_ErrorBoundaries/src/index.html b/20_ErrorBoundaries/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/20_ErrorBoundaries/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
    +

    Sample app

    +
    +
    + + diff --git a/20_ErrorBoundaries/src/main.tsx b/20_ErrorBoundaries/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/20_ErrorBoundaries/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/20_ErrorBoundaries/src/nameEdit.tsx b/20_ErrorBoundaries/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/20_ErrorBoundaries/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/20_ErrorBoundaries/tsconfig.json b/20_ErrorBoundaries/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/20_ErrorBoundaries/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/20_ErrorBoundaries/webpack.config.js b/20_ErrorBoundaries/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/20_ErrorBoundaries/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/21_Hooks/.babelrc b/21_Hooks/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/21_Hooks/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/21_Hooks/package.json b/21_Hooks/package.json new file mode 100644 index 0000000..24b318b --- /dev/null +++ b/21_Hooks/package.json @@ -0,0 +1,39 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "core-js": "^2.5.7", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9", + "whatwg-fetch": "^3.0.0" + }, + "dependencies": { + "react": "^16.7.0-alpha.0", + "react-dom": "^16.7.0-alpha.0" + } +} diff --git a/21_Hooks/readme.md b/21_Hooks/readme.md new file mode 100644 index 0000000..de38e3c --- /dev/null +++ b/21_Hooks/readme.md @@ -0,0 +1,155 @@ +## Intro + +In this sample we will make use of hooks a cool concept introduced in React 16.7.0 + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Let's copy the code from sample _12_TableHttp_. + +- Let's install the dependencies. + +```bash +npm install +``` + +- Now we are going to uninstall current version of react and react-dom: + +```bash +npm uninstall react react-dom --save +``` + +- And install the 16.7 alfa version: + +```bash +npm install react@16.7.0-alpha.0 react-dom@16.7.0-alpha.0 --save +``` + +- We are going to move the current _membersTable.tsx_ class based component to an stateless one (we will replace the current state using hooks). + +- Let's start by cleaning up code and adding a hook to hold the members list. + +_./src/membersTable.tsx_ + +```diff +- interface Props { +- } + +- // We define members as a state (the compoment holding this will be a container +- // component) +- interface State { +- members: Array +-} + +- // Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX +- export class MembersTableComponent extends React.Component { ++ export const MembersTableComponent = () => { +- constructor(props: Props) { +- super(props); +- // set initial state +- this.state = { members: [] }; +- } + ++ const [members, setMembers] = React.useState([]); + +- // Standard react lifecycle function: +- // https://facebook.github.io/react/docs/component-specs.html +- public componentDidMount() { ++ const loadMembers = () => { + memberAPI.getAllMembers().then((members) => +- this.setState({ members: members }) ++ setMembers(members) + ); + } + +- public render() { + + return ( +
    +

    Members Page

    + + + + + + { +- this.state.members.map((member: MemberEntity) => ++ members.map((member: MemberEntity) => + + ) + } + +
    +
    + ); +- } +} +``` + +- Now we have tetchy issue... _componentDidMount_ we don't have this on hooks component, how can we do that? To do that we can make use of react hooks _useEffect_. + +_./src/membersTable.tsx_ + +```diff +export const MembersTableComponent = () => { + + const [members, setMembers] = React.useState([]); + + const loadMembers = () => { + memberAPI.getAllMembers().then((members) => + setMembers(members) + ); + } + ++ React.useEffect(() => { ++ loadMembers(); ++ }) +``` + +- That was nice, but what if we could be interested in reusing this hook?. We can extract it into a function: + +_./src/membersTable.tsx_ + +```diff ++ function useMembers() { ++ const [members, setMembers] = React.useState([]); ++ ++ const loadMembers = () => { ++ memberAPI.getAllMembers().then((members) => ++ setMembers(members) ++ ); ++ } ++ ++ return {members, loadMembers }; ++} + +export const MembersTableComponent = () => { ++ const { members, loadMembers } = useMembers(); +- const [members, setMembers] = React.useState([]); + +- const loadMembers = () => { +- memberAPI.getAllMembers().then((members) => +- setMembers(members) +- ); +- } + + React.useEffect(() => { + loadMembers(); + }); +``` + +- Now if we ran this it will get ran on every rerender, in order to limit this we can pass an empty array as a second argument of _useEffect_, this tell React that you effect doesn't depend on any values from props or state, is it nevers needs to re-rerun. + +```diff + React.useEffect(() => { + loadMembers(); +- }); ++ },[]); +``` + +> More info about _hooks-effect_: https://reactjs.org/docs/hooks-effect.html \ No newline at end of file diff --git a/21_Hooks/readme_es.md b/21_Hooks/readme_es.md new file mode 100644 index 0000000..3253948 --- /dev/null +++ b/21_Hooks/readme_es.md @@ -0,0 +1,155 @@ +## Intro + +En este ejemplo haremos uso de hooks un concepto chulo que se introdució en React 16.7.0 + +## Prerrequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en una terminal o consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Vamos a copiar el código del ejemplo _12_TableHttp_. + +- Vamos a instalar las dependencias. + +```bash +npm install +``` + +- Ahora vamos a desinstalar la versión actual de react y react-dom + +```bash +npm uninstall react react-dom --save +``` + +- E instalamos la versión 16.7 alfa: + +```bash +npm install react@16.7.0-alpha.0 react-dom@16.7.0-alpha.0 --save +``` + +- Vamos a mover el componente actual basado en la clase _membersTable.tsx_ a uno sin estado (reemplazaremos el estado actual usando hooks). + +- Vamos a empezar por limpiar el código y añadiendo un hook que mantenga la lista de mienbros. + +_./src/membersTable.tsx_ + +```diff +- interface Props { +- } + +- // We define members as a state (the compoment holding this will be a container +- // component) +- interface State { +- members: Array +-} + +- // Nice tsx guide: https://github.com/Microsoft/TypeScript/wiki/JSX +- export class MembersTableComponent extends React.Component { ++ export const MembersTableComponent = () => { +- constructor(props: Props) { +- super(props); +- // set initial state +- this.state = { members: [] }; +- } + ++ const [members, setMembers] = React.useState([]); + +- // Standard react lifecycle function: +- // https://facebook.github.io/react/docs/component-specs.html +- public componentDidMount() { ++ const loadMembers = () => { + memberAPI.getAllMembers().then((members) => +- this.setState({ members: members }) ++ setMembers(members) + ); + } + +- public render() { + + return ( +
    +

    Members Page

    + + + + + + { +- this.state.members.map((member: MemberEntity) => ++ members.map((member: MemberEntity) => + + ) + } + +
    +
    + ); +- } +} +``` + +- Ahora tenemos un gran problema... _componentDidMount_ no tenemos esto en el componente hooks, ¿Como podemos hacer esto? Para ello podemos hacer uso de los hooks de react _useEffect_. + +_./src/membersTable.tsx_ + +```diff +export const MembersTableComponent = () => { + + const [members, setMembers] = React.useState([]); + + const loadMembers = () => { + memberAPI.getAllMembers().then((members) => + setMembers(members) + ); + } + ++ React.useEffect(() => { ++ loadMembers(); ++ }) +``` + +- Eso fué genial, pero ¿Y si pudiéramos estár interesandos en reutilizar esté hook? Podríamos extraer esto en una función: + +_./src/membersTable.tsx_ + +```diff ++ function useMembers() { ++ const [members, setMembers] = React.useState([]); ++ ++ const loadMembers = () => { ++ memberAPI.getAllMembers().then((members) => ++ setMembers(members) ++ ); ++ } ++ ++ return {members, loadMembers }; ++} + +export const MembersTableComponent = () => { ++ const { members, loadMembers } = useMembers(); +- const [members, setMembers] = React.useState([]); + +- const loadMembers = () => { +- memberAPI.getAllMembers().then((members) => +- setMembers(members) +- ); +- } + + React.useEffect(() => { + loadMembers(); + }); +``` + +- Ahora si lo ejecutamos, se ejecutará en cada repetición, para limitar esto podemos pasar un array vació como un segundo argumento de _useEffect_, esto le dice a React que su efecto no dependen de ningún valor de props o del estado, esto nunca necesita volver a ejecutar. + +```diff + React.useEffect(() => { + loadMembers(); +- }); ++ },[]); +``` + +> Más información sobre _hooks-effect_: https://reactjs.org/docs/hooks-effect.html \ No newline at end of file diff --git a/21_Hooks/src/api/memberAPI.ts b/21_Hooks/src/api/memberAPI.ts new file mode 100644 index 0000000..6185ee0 --- /dev/null +++ b/21_Hooks/src/api/memberAPI.ts @@ -0,0 +1,46 @@ +import {MemberEntity} from '../model/member'; +import {fetch} from 'whatwg-fetch'; + +// Sync mock data API, inspired from: +// https://gist.github.com/coryhouse/fd6232f95f9d601158e4 +class MemberAPI { + + // Just return a copy of the mock data + getAllMembers() : Promise { + const gitHubMembersUrl : string = 'https://api.github.com/orgs/lemoncode/members'; + + return fetch(gitHubMembersUrl) + .then((response) => this.checkStatus(response)) + .then((response) => this.parseJSON(response)) + .then((data) => this.resolveMembers(data)); + } + + private checkStatus(response : Response) : Promise { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response); + } else { + let error = new Error(response.statusText); + throw error; + } + } + + private parseJSON(response : Response) : any { + return response.json(); + } + + private resolveMembers (data : any) : Promise { + const members = data.map((gitHubMember) => { + var member : MemberEntity = { + id: gitHubMember.id, + login: gitHubMember.login, + avatar_url: gitHubMember.avatar_url, + }; + + return member; + }); + + return Promise.resolve(members); + } +} + +export const memberAPI = new MemberAPI(); diff --git a/21_Hooks/src/app.tsx b/21_Hooks/src/app.tsx new file mode 100644 index 0000000..74a4736 --- /dev/null +++ b/21_Hooks/src/app.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import {MembersTableComponent} from './membersTable'; + +interface Props { +} + +interface State { + userName: string; +} + +export class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { userName: 'defaultUserName' }; + } + + setUsernameState = (event) => { + this.setState({ userName: event.target.value }); + } + + + public render() { + return ( + <> + + + ); + } +} \ No newline at end of file diff --git a/21_Hooks/src/hello.tsx b/21_Hooks/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/21_Hooks/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/21_Hooks/src/index.html b/21_Hooks/src/index.html new file mode 100644 index 0000000..b0b7d25 --- /dev/null +++ b/21_Hooks/src/index.html @@ -0,0 +1,13 @@ + + + + + + + +
    +

    Sample app

    +
    +
    + + diff --git a/21_Hooks/src/main.tsx b/21_Hooks/src/main.tsx new file mode 100644 index 0000000..be3985e --- /dev/null +++ b/21_Hooks/src/main.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import {App} from './app'; + +import { HelloComponent } from './hello'; + +ReactDOM.render( + , + document.getElementById('root') +); diff --git a/21_Hooks/src/memberHead.tsx b/21_Hooks/src/memberHead.tsx new file mode 100644 index 0000000..1d8a947 --- /dev/null +++ b/21_Hooks/src/memberHead.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { MemberEntity } from './model/member'; + +export const MemberHead = () => + + + Avatar + + + Id + + + Name + + diff --git a/21_Hooks/src/memberRow.tsx b/21_Hooks/src/memberRow.tsx new file mode 100644 index 0000000..9e56039 --- /dev/null +++ b/21_Hooks/src/memberRow.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import {MemberEntity} from './model/member'; + +export const MemberRow = (props: {member : MemberEntity}) => + + + + + + {props.member.id} + + + {props.member.login} + + diff --git a/21_Hooks/src/membersTable.tsx b/21_Hooks/src/membersTable.tsx new file mode 100644 index 0000000..8dbee69 --- /dev/null +++ b/21_Hooks/src/membersTable.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import { MemberEntity } from './model/member'; +import { memberAPI } from './api/memberAPI'; +import { MemberRow } from './memberRow'; +import { MemberHead } from './memberHead'; +import { } from 'core-js'; + +function useMembers() { + const [members, setMembers] = React.useState([]); + + const loadMembers = () => { + memberAPI.getAllMembers().then((members) => + setMembers(members) + ); + } + + return { members, loadMembers}; +} + +export const MembersTableComponent = () => { + const { members, loadMembers } = useMembers(); + + React.useEffect(() => { + loadMembers(); + }, []); + + return ( +
    +

    Members Page

    + + + + + + { + members.map((member: MemberEntity) => + + ) + } + +
    +
    + ); +} diff --git a/21_Hooks/src/model/member.ts b/21_Hooks/src/model/member.ts new file mode 100644 index 0000000..8977f30 --- /dev/null +++ b/21_Hooks/src/model/member.ts @@ -0,0 +1,11 @@ +export interface MemberEntity { + id: number; + login: string; + avatar_url: string; +} + +export const createEmptyMember = () : MemberEntity => ({ + id: -1, + login: "", + avatar_url: "" +}); diff --git a/21_Hooks/src/model/memberMockData.ts b/21_Hooks/src/model/memberMockData.ts new file mode 100644 index 0000000..8a2846a --- /dev/null +++ b/21_Hooks/src/model/memberMockData.ts @@ -0,0 +1,17 @@ +import {MemberEntity} from './member'; + +var MembersMockData : MemberEntity[] = + [ + { + id: 1457912, + login: "brauliodiez", + avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3" + }, + { + id: 4374977, + login: "Nasdan", + avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3" + } + ]; + +export default MembersMockData; diff --git a/21_Hooks/src/nameEdit.tsx b/21_Hooks/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/21_Hooks/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/21_Hooks/tsconfig.json b/21_Hooks/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/21_Hooks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/21_Hooks/webpack.config.js b/21_Hooks/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/21_Hooks/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/22_Hooks_UseContext/.babelrc b/22_Hooks_UseContext/.babelrc new file mode 100644 index 0000000..957cae3 --- /dev/null +++ b/22_Hooks_UseContext/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "entry" + } + ] + ] +} diff --git a/22_Hooks_UseContext/Readme.md b/22_Hooks_UseContext/Readme.md new file mode 100644 index 0000000..2665db9 --- /dev/null +++ b/22_Hooks_UseContext/Readme.md @@ -0,0 +1,151 @@ +## Intro + +As we saw in sample _17 Context_ this is a powerfull feature. + +Getting data from a _Context.Consumer_ needs some plumbing, we have used so far HOC (example 18) and Render Props (example 19) to wrap that into some reusable code, that was nice but it needed to add some extra markup to our components, making heavy use of HOC and RenderProps can lead you to the _markup hell_ (lot of nested HOC / Render props). + +Let's see how this is solved using Hooks + Use Context. + +## Prerequisites + +Install [Node.js and npm](https://nodejs.org/en/) (v6.6.0) if they are not already installed on your computer. + +> Verify that you are running at least node v6.x.x and npm 3.x.x by running `node -v` and `npm -v` in a terminal/console window. Older versions may produce errors. + +## Steps to build it + +- Let's copy the code from sample _19 RenderProps_. + +- Let's install the dependencies. + +```bash +npm install +``` + +- Now we are going to uninstall current version of react and react-dom: + +```bash +npm uninstall react react-dom --save +``` + +- And install the 16.7 alfa version: + +```bash +npm install react@16.7.0-alpha.0 react-dom@16.7.0-alpha.0 --save +``` + +- Let's replace the render props solution with a _UseContext_. + +_./src/pages/pageB.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; +- import { Session } from '../../common/'; ++ import { SessionContext } from '../../common'; + +// ... + +- export const PageB = () => ++ export const PageB = () => { ++ const loginContext = React.useContext(SessionContext) ++ ++ return ( + +
    +- ( +- +- )} +- > ++ +
    ++} +``` + +- So now our _PageB_ component looks as simple as: + +_./src/pages/pageB.tsx_ + +```tsx +export const PageB = () => { + const loginContext = React.useContext(SessionContext); + + return ( +
    + +
    + ) +} +``` + +- Now we can get rid of the _renderProps_ helper we created. + +_./src/common/sessionContext.tsx_ + +```diff +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + +interface Props { + render : (login : string) => React.ReactNode; +} + +- export class Session extends React.Component { +- constructor(props : Props) { +- super(props); +- } +- +- render() { +- return ( +- +- { +- ({ login, updateLogin }) => +- <> +- {this.props.render(login)} +- +- } +- +- ) +- } +- } +``` + diff --git a/22_Hooks_UseContext/package.json b/22_Hooks_UseContext/package.json new file mode 100644 index 0000000..4726fa1 --- /dev/null +++ b/22_Hooks_UseContext/package.json @@ -0,0 +1,42 @@ +{ + "name": "reactbysample", + "version": "1.0.0", + "description": "In this sample we setup the basic plumbing to \"build\" our project and launch it in a dev server.", + "main": "index.js", + "scripts": { + "start": "webpack-dev-server --mode development --inline --hot --open", + "build": "webpack --mode development", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/cli": "^7.1.2", + "@babel/core": "^7.1.2", + "@babel/polyfill": "^7.0.0", + "@babel/preset-env": "^7.1.0", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", + "@types/react": "^16.4.16", + "@types/react-dom": "^16.0.9", + "@types/react-router-dom": "^4.3.1", + "awesome-typescript-loader": "^5.2.1", + "babel-loader": "^8.0.4", + "css-loader": "^1.0.0", + "file-loader": "^2.0.0", + "html-webpack-plugin": "^3.2.0", + "lc-form-validation": "^2.0.0", + "mini-css-extract-plugin": "^0.4.3", + "style-loader": "^0.23.1", + "typescript": "^3.1.1", + "url-loader": "^1.1.1", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.9" + }, + "dependencies": { + "react": "^16.7.0-alpha.0", + "react-dom": "^16.7.0-alpha.0", + "react-router-dom": "^4.3.1" + } +} diff --git a/22_Hooks_UseContext/readme_es.md b/22_Hooks_UseContext/readme_es.md new file mode 100644 index 0000000..5035bd7 --- /dev/null +++ b/22_Hooks_UseContext/readme_es.md @@ -0,0 +1,150 @@ +## Intro + +Como vemos en el ejemplo _17 Context_ esto es una poderosa característica. + +Obtener datos de un _Context.Consumer_ necesita un poco de tubería, hasta ahora hemos utilizado HOC (ejemplo 18) y Render Props (ejemplo 19) para incluir eso en un código reutilizable, eso estuvo bien, pero necesitabas agregar un margen de beneficio adicional a nuestros componentes, hacer un uso intensivo de HOC y RenderProps puede llevarlo a _markup hell_ (muchos accesorios de HOC / Render props). + +Veamos como se resuelve esto utilizando Hooks + Use Context. + +## Prerequisitos + +Instalar [Node.js y npm](https://nodejs.org/en/) (v6.6.0 o superior) si no las tenemos instaladas en nuestro ordenador. + +> Verifica que estás usando al menos node v6.x.x y npm 3.x.x usando los comandos `node -v` y `npm -v` en una terminal o consola. Las versiones anteriores pueden producir errores. + +## Pasos para construirlo + +- Vamos a copiar el código del ejemplo _19 RenderProps_. + +- Vamos a instalar las dependencias. + +```bash +npm install +``` + +- Ahora vamos a desistalar la versión actual de react y react-dom: + +```bash +npm uninstall react react-dom --save +``` + +- E instalamos la versión alfa 16.7: + +```bash +npm install react@16.7.0-alpha.0 react-dom@16.7.0-alpha.0 --save +``` + +- Vamos a reemplazar la solución de render props con un _UseContext_ + +_./src/pages/pageB.tsx_ + +```diff +import * as React from "react" +import { Link } from 'react-router-dom'; +- import { Session } from '../../common/'; ++ import { SessionContext } from '../../common'; + +// ... + +- export const PageB = () => ++ export const PageB = () => { ++ const loginContext = React.useContext(SessionContext) ++ ++ return ( + +
    +- ( +- +- )} +- > ++ +
    ++} +``` + +- Asi que ahora nuestro componente _PageB_ se muestra tan simple como: + +_./src/pages/pageB.tsx_ + +```tsx +export const PageB = () => { + const loginContext = React.useContext(SessionContext); + + return ( +
    + +
    + ) +} +``` + +- Ahora podemos eliminar el helper de _renderProps_ que creamos. + +_./src/common/sessionContext.tsx_ + +```diff +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + +interface Props { + render : (login : string) => React.ReactNode; +} + +- export class Session extends React.Component { +- constructor(props : Props) { +- super(props); +- } +- +- render() { +- return ( +- +- { +- ({ login, updateLogin }) => +- <> +- {this.props.render(login)} +- +- } +- +- ) +- } +- } +``` \ No newline at end of file diff --git a/22_Hooks_UseContext/src/api/login.ts b/22_Hooks_UseContext/src/api/login.ts new file mode 100644 index 0000000..1f7d4f3 --- /dev/null +++ b/22_Hooks_UseContext/src/api/login.ts @@ -0,0 +1,5 @@ +import {LoginEntity} from '../model/login'; + +// Just a fake loginAPI +export const isValidLogin = (loginInfo : LoginEntity) : boolean => + (loginInfo.login === 'admin' && loginInfo.password === 'test'); diff --git a/22_Hooks_UseContext/src/common/forms/textFieldForm.tsx b/22_Hooks_UseContext/src/common/forms/textFieldForm.tsx new file mode 100644 index 0000000..808091c --- /dev/null +++ b/22_Hooks_UseContext/src/common/forms/textFieldForm.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography/Typography"; + +interface Props { + name: string; + label: string; + onChange: any; + value: string; + error?: string; + type? : string; +} + +const defaultProps : Partial = { + type: 'text', +} + +const onTextFieldChange = (fieldId : string, onChange: (fieldId, value) => void) => (e) => { + onChange(fieldId, e.target.value); +} + +export const TextFieldForm : React.StatelessComponent = (props) => { + const {name, label, onChange, value, error, type} = props; + + return ( + <> + + + {props.error} + + + ) +} diff --git a/22_Hooks_UseContext/src/common/index.tsx b/22_Hooks_UseContext/src/common/index.tsx new file mode 100644 index 0000000..8d8eeb0 --- /dev/null +++ b/22_Hooks_UseContext/src/common/index.tsx @@ -0,0 +1,2 @@ +export * from './notification'; +export * from './sessionContext'; \ No newline at end of file diff --git a/22_Hooks_UseContext/src/common/notification.tsx b/22_Hooks_UseContext/src/common/notification.tsx new file mode 100644 index 0000000..5396faf --- /dev/null +++ b/22_Hooks_UseContext/src/common/notification.tsx @@ -0,0 +1,53 @@ +import * as React from "react" +import Button from '@material-ui/core/Button'; +import Snackbar from '@material-ui/core/Snackbar'; +import IconButton from '@material-ui/core/IconButton'; +import CloseIcon from '@material-ui/icons/Close'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; + +interface Props extends WithStyles { + message: string; + show: boolean; + onClose: () => void; +} + +const styles = theme => createStyles({ + close: { + padding: theme.spacing.unit / 2, + }, +}); + +const NotificationComponentInner = (props: Props) => { + const { classes, message, show, onClose } = props; + + return ( + {message}} + action={[ + + + , + ]} + + /> + ) +} + +export const NotificationComponent = withStyles(styles)(NotificationComponentInner); diff --git a/22_Hooks_UseContext/src/common/sessionContext.tsx b/22_Hooks_UseContext/src/common/sessionContext.tsx new file mode 100644 index 0000000..f83ed61 --- /dev/null +++ b/22_Hooks_UseContext/src/common/sessionContext.tsx @@ -0,0 +1,62 @@ +import * as React from "react" + +export interface SessionContextProps { + login: string; + updateLogin: (value) => void; +} + +export const createDefaultUser = (): SessionContextProps => ({ + login: 'no user', + updateLogin: (value) => { }, +}); + +export const SessionContext = React.createContext(createDefaultUser()); + +interface State extends SessionContextProps { +} + +export class SessionProvider extends React.Component<{}, State> { + + constructor(props) { + super(props); + this.state = { + login: createDefaultUser().login, + updateLogin: this.setLoginInfo + } + } + + setLoginInfo = (newLogin) => { + this.setState({ login: newLogin }) + } + + render() { + return ( + + {this.props.children} + + ) + }; +}; + +interface Props { + render : (login : string) => React.ReactNode; +} + +export class Session extends React.Component { + constructor(props : Props) { + super(props); + } + + render() { + return ( + + { + ({ login, updateLogin }) => + <> + {this.props.render(login)} + + } + + ) + } +} diff --git a/22_Hooks_UseContext/src/hello.tsx b/22_Hooks_UseContext/src/hello.tsx new file mode 100644 index 0000000..5636921 --- /dev/null +++ b/22_Hooks_UseContext/src/hello.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +export const HelloComponent = (props: {userName : string}) => { + return ( +

    Hello user: {props.userName} !

    + ); +} diff --git a/22_Hooks_UseContext/src/index.html b/22_Hooks_UseContext/src/index.html new file mode 100644 index 0000000..0fcc01e --- /dev/null +++ b/22_Hooks_UseContext/src/index.html @@ -0,0 +1,14 @@ + + + + + + + + + +
    +
    +
    + + diff --git a/22_Hooks_UseContext/src/main.tsx b/22_Hooks_UseContext/src/main.tsx new file mode 100644 index 0000000..247c55f --- /dev/null +++ b/22_Hooks_UseContext/src/main.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { HashRouter, Switch, Route } from 'react-router-dom'; +import { LoginPage } from './pages/login'; +import { PageB } from './pages/b'; +import { createMuiTheme, MuiThemeProvider } from '@material-ui/core/styles'; +import { SessionProvider } from './common'; + +const theme = createMuiTheme({ + typography: { + useNextVariants: true, + }, +}); + +ReactDOM.render( + + + + + + + + + + + , document.getElementById('root') +); diff --git a/22_Hooks_UseContext/src/model/login.ts b/22_Hooks_UseContext/src/model/login.ts new file mode 100644 index 0000000..081e6fb --- /dev/null +++ b/22_Hooks_UseContext/src/model/login.ts @@ -0,0 +1,9 @@ +export interface LoginEntity { + login : string; + password : string; +} + +export const createEmptyLogin = () : LoginEntity => ({ + login: '', + password: '', +}); diff --git a/22_Hooks_UseContext/src/nameEdit.tsx b/22_Hooks_UseContext/src/nameEdit.tsx new file mode 100644 index 0000000..6c544b2 --- /dev/null +++ b/22_Hooks_UseContext/src/nameEdit.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; + +interface Props { + userName : string; + onChange : (event) => void; +} + +export const NameEditComponent = (props : Props) => + <> + + + diff --git a/22_Hooks_UseContext/src/pages/b/index.ts b/22_Hooks_UseContext/src/pages/b/index.ts new file mode 100644 index 0000000..913631b --- /dev/null +++ b/22_Hooks_UseContext/src/pages/b/index.ts @@ -0,0 +1 @@ +export {PageB} from './pageB'; \ No newline at end of file diff --git a/22_Hooks_UseContext/src/pages/b/pageB.tsx b/22_Hooks_UseContext/src/pages/b/pageB.tsx new file mode 100644 index 0000000..f70c234 --- /dev/null +++ b/22_Hooks_UseContext/src/pages/b/pageB.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import { Link } from 'react-router-dom'; +import { Session } from '../../common/'; +import { SessionContext } from '../../common'; + +interface Props { + login : string; +} + +const LoginComponent = (props: Props) => + <> +

    Hello from page B

    +
    +
    +

    Login: {props.login}

    + + Navigate to Login + + + +export const PageB = () => { + const loginContext = React.useContext(SessionContext); + + return ( +
    + +
    + ) +} diff --git a/22_Hooks_UseContext/src/pages/login/index.ts b/22_Hooks_UseContext/src/pages/login/index.ts new file mode 100644 index 0000000..50d85bb --- /dev/null +++ b/22_Hooks_UseContext/src/pages/login/index.ts @@ -0,0 +1 @@ +export {LoginPage} from './loginPage'; \ No newline at end of file diff --git a/22_Hooks_UseContext/src/pages/login/loginForm.tsx b/22_Hooks_UseContext/src/pages/login/loginForm.tsx new file mode 100644 index 0000000..eda83bd --- /dev/null +++ b/22_Hooks_UseContext/src/pages/login/loginForm.tsx @@ -0,0 +1,43 @@ +import * as React from "react" +import TextField from "@material-ui/core/TextField"; +import Button from "@material-ui/core/Button"; +import { LoginEntity } from "../../model/login"; +import {LoginFormErrors} from './viewmodel'; +import { TextFieldForm } from '../../common/forms/textFieldForm'; + +interface Props { + onLogin: () => void; + onUpdateField: (string, any) => void; + loginInfo : LoginEntity; + loginFormErrors : LoginFormErrors; +} + +export const LoginForm = (props: Props) => { + const { onLogin, onUpdateField, loginInfo, loginFormErrors } = props; + + const onTexFieldChange = (fieldId) => (e) => { + onUpdateField(fieldId, e.target.value); + } + + return ( +
    + + + +
    + ) +} diff --git a/22_Hooks_UseContext/src/pages/login/loginPage.tsx b/22_Hooks_UseContext/src/pages/login/loginPage.tsx new file mode 100644 index 0000000..5742172 --- /dev/null +++ b/22_Hooks_UseContext/src/pages/login/loginPage.tsx @@ -0,0 +1,120 @@ +import * as React from "react" +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardContent from '@material-ui/core/CardContent'; +import { LoginForm } from './loginForm'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { LoginEntity, createEmptyLogin } from '../../model/login'; +import { isValidLogin } from '../../api/login'; +import { NotificationComponent } from '../../common' +import { LoginFormErrors, createDefaultLoginFormErrors } from './viewmodel'; +import { loginFormValidation } from './loginValidations'; +import {SessionContext} from '../../common'; +import { Session } from "inspector"; + +// https://material-ui.com/guides/typescript/ +const styles = theme => createStyles({ + card: { + maxWidth: 400, + margin: '0 auto', + }, +}); + + +interface State { + loginInfo: LoginEntity; + showLoginFailedMsg: boolean; + loginFormErrors: LoginFormErrors; +} + +interface Props extends RouteComponentProps, WithStyles { + updateLogin: (value) => void +} + +class LoginPageInner extends React.Component { + + constructor(props) { + super(props); + + this.state = { + loginInfo: createEmptyLogin(), + showLoginFailedMsg: false, + loginFormErrors: createDefaultLoginFormErrors(), + } + } + + + onLogin = () => { + loginFormValidation.validateForm(this.state.loginInfo) + .then((formValidatinResult) => { + if(formValidatinResult.succeeded) { + if (isValidLogin(this.state.loginInfo)) { + this.props.updateLogin(this.state.loginInfo.login); + this.props.history.push('/pageB'); + } else { + this.setState({ showLoginFailedMsg: true }); + } + } else { + alert('error, review the fields'); + } + }) + } + + onUpdateLoginField = (name: string, value) => { + this.setState({loginInfo: { + ...this.state.loginInfo, + [name]: value, + } + }); + + loginFormValidation.validateField(this.state.loginInfo, name, value).then( + (fieldValidationResult) => { + this.setState({ + loginFormErrors: { + ...this.state.loginFormErrors, + [name]: fieldValidationResult, + } + }); + } + ); + } + + render() { + const { classes } = this.props; + return ( + <> + + + + + + + this.setState({ showLoginFailedMsg: false })} + /> + + ) + + } +} + +export const LoginPageInner2 = (props) => + <> + + { + ({updateLogin}) => + + } + + + + +export const LoginPage = withStyles(styles)(withRouter((LoginPageInner2))); diff --git a/22_Hooks_UseContext/src/pages/login/loginValidations.ts b/22_Hooks_UseContext/src/pages/login/loginValidations.ts new file mode 100644 index 0000000..d0eee27 --- /dev/null +++ b/22_Hooks_UseContext/src/pages/login/loginValidations.ts @@ -0,0 +1,16 @@ +import { + createFormValidation, ValidationConstraints, Validators, +} from 'lc-form-validation'; + +const loginFormValidationConstraints: ValidationConstraints = { + fields: { + login: [ + { validator: Validators.required }, + ], + password: [ + { validator: Validators.required }, + ], + }, +}; + +export const loginFormValidation = createFormValidation(loginFormValidationConstraints); diff --git a/22_Hooks_UseContext/src/pages/login/viewmodel.ts b/22_Hooks_UseContext/src/pages/login/viewmodel.ts new file mode 100644 index 0000000..b374fa6 --- /dev/null +++ b/22_Hooks_UseContext/src/pages/login/viewmodel.ts @@ -0,0 +1,11 @@ +import { FieldValidationResult } from 'lc-form-validation'; + +export interface LoginFormErrors { + login: FieldValidationResult; + password: FieldValidationResult; +} + +export const createDefaultLoginFormErrors = (): LoginFormErrors => ({ + login: new FieldValidationResult(), + password: new FieldValidationResult(), +}); diff --git a/22_Hooks_UseContext/tsconfig.json b/22_Hooks_UseContext/tsconfig.json new file mode 100644 index 0000000..885d474 --- /dev/null +++ b/22_Hooks_UseContext/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "es6", + "moduleResolution": "node", + "declaration": false, + "noImplicitAny": false, + "jsx": "react", + "sourceMap": true, + "noLib": false, + "suppressImplicitAnyIndexErrors": true + }, + "compileOnSave": false, + "exclude": [ + "node_modules" + ] +} diff --git a/22_Hooks_UseContext/webpack.config.js b/22_Hooks_UseContext/webpack.config.js new file mode 100644 index 0000000..7c85f49 --- /dev/null +++ b/22_Hooks_UseContext/webpack.config.js @@ -0,0 +1,64 @@ +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var MiniCssExtractPlugin = require('mini-css-extract-plugin'); +var webpack = require('webpack'); +var path = require('path'); + +var basePath = __dirname; + +module.exports = { + context: path.join(basePath, "src"), + resolve: { + extensions: ['.js', '.ts', '.tsx'] + }, + entry: ['@babel/polyfill', + './main.tsx' + ], + output: { + path: path.join(basePath, 'dist'), + filename: 'bundle.js' + }, + devtool: 'source-map', + devServer: { + contentBase: './dist', // Content base + inline: true, // Enable watch and live reload + host: 'localhost', + port: 8080, + stats: 'errors-only' + }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + loader: 'awesome-typescript-loader', + options: { + useBabel: true, + "babelCore": "@babel/core", // needed for Babel v7 + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, "css-loader"] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: 'assets/img/[name].[ext]?[hash]' + } + }, + ], + }, + plugins: [ + //Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', //Name of file in ./dist/ + template: 'index.html', //Name of template in ./src + hash: true, + }), + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: "[id].css" + }), + ], +}; diff --git a/readme.md b/readme.md index f06be12..fdb8c53 100644 --- a/readme.md +++ b/readme.md @@ -3,94 +3,130 @@ The goal of this project is to provide a set of step by step guided samples, covering core concepts of React (props, state, replace, cycle...). -Characteristics: +Topics covered: + ++ Creating a basic starting point from scratch. ++ Basics creating components and managing with props. ++ Managing State plus callbacks. ++ Playing with currified functions. ++ Displaying tabular data. ++ Controlling render lifecycle. ++ Routing. ++ Managing form + validations ++ Next context api. ++ Creating High order components. -+ Bundling based on webpack. -+ React + Typescript based. -+ Simple navigation using react-router. Contributors and reviewers are more than welcome. ## To get started: 1. Install [NodeJS](http://www.nodejs.org). -2. Install webpack - `npm install webpack -g`. -4. Download this repo. -5. Open the command line of your choice and cd to the root directory of this repo on your machine, +2. Download this repo. +3. Open the command line of your choice and cd to the root directory of this repo on your machine, then cd to one of the demos projects. -6. Install the required packages - `npm install`. -7. Builds the project and launch a lite dev web server - `npm start`. -8. Navigate to [http://localhost:8080/](http://localhost:8080/) if your browser doesn't open automatically. +4. Install the required packages - `npm install`. +5. Builds the project and launch a lite dev web server - `npm start`. +6. Navigate to [http://localhost:8080/](http://localhost:8080/) if your browser doesn't open automatically. ## samples -### [00 Boiler plate](https://github.com/Lemoncode/react-by-sample/tree/master/00%20Boilerplate) +### [00 Boiler plate](./00_Boilerplate/readme.md) Bundling + npm start based on webpack. -### [01 Hello React](https://github.com/Lemoncode/react-by-sample/tree/master/01%20HelloReact) +### [01 Hello React](./01_HelloReact/readme.md) Hello world, simples react render sample. -### [02 Properties](https://github.com/Lemoncode/react-by-sample/tree/master/02%20Properties) +### [02 Properties](./02_Properties/readme.md) Introduce a basic React concept, handling properties. -### [03 State](https://github.com/Lemoncode/react-by-sample/tree/master/03%20State) +### [03 State](./03_State/readme.md) Introduce a basic React concept, handling State. -### [04 Callback](https://github.com/Lemoncode/react-by-sample/tree/master/04%20Callback) +### [04 Callback](./04_Callback/readme.md) Using callbacks. -### [05 Refactor](https://github.com/Lemoncode/react-by-sample/tree/master/05%20Refactor) +### [05 Refactor](./05_Refactor/readme.md) Refactor the job done. -### [06 Move Back To Stateless](https://github.com/Lemoncode/react-by-sample/tree/master/06%20MoveBackToStateless) +### [06 Move Back To Stateless](./06_MoveBackToStateless/readme.md) Remove state from a child control just to have clear governance of state. -### [07 Enable](https://github.com/Lemoncode/react-by-sample/tree/master/07%20Enable) +### [07 Enable](./07_Enable/readme.md) Enable/disable components. -### [08 ColorPicker](https://github.com/Lemoncode/react-by-sample/tree/master/08%20Colorpicker) +### [08 ColorPicker](./08_Colorpicker/readme.md) Simple color picker demo (show how properties work). -### [09 ColorPicker Refactor](https://github.com/Lemoncode/react-by-sample/tree/master/09%20ColorpRefactor) +### [09 ColorPicker Refactor](./09_ColorpRefactor/readme.md) ColorPicker refactor. -### [10 Sidebar](https://github.com/Lemoncode/react-by-sample/tree/master/10%20Sidebar) +### [10 Sidebar](./10_Sidebar/readme.md) Implementation of a single sidebar. -### [11 Table Mock](https://github.com/Lemoncode/react-by-sample/tree/master/11%20TableMock) +### [11 Table Mock](./11_TableMock/readme.md) Render a table and use a child component to render each row. -### [12 Table Http](https://github.com/Lemoncode/react-by-sample/tree/master/12%20TableHttp) +### [12 Table Http](./12_TableHttp/readme.md) Using Promises. -### [13 Should Update](https://github.com/Lemoncode/react-by-sample/tree/master/13%20ShouldUpdate) +### [13 Should Update](./13_ShouldUpdate/readme.md) Enhance rendering performance hooking to 'shouldComponentUpdate'. -### [14 React Router](https://github.com/Lemoncode/react-by-sample/tree/master/14%20ReactRouter) +### [14 React Router](./14_ReactRouter/readme.md) -Sample of navigation. +React Router navigation example. -### [15 Login Form](https://github.com/Lemoncode/react-by-sample/tree/master/15%20LoginForm) +### [15 Login Form](./15_LoginForm/readme.md) Basic implementation of a login page. -# About Lemoncode +### [16 Validation](./16_Validation/readme.md) -We are a team of long-term experienced freelance developers, established as a group in 2010. -We specialize in Front End technologies and .NET. [Click here](http://lemoncode.net/services/en/#en-home) to get more info about us. +React Form validation, using lc-form-validation library -For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend +### [17 Context](./17_Context/Readme.md) + +How to use React 16 context api. + +### [18 HOC](./18_Hoc/Readme.md) + +High Order component sample.. + +### [19 RenderProps](./19_RenderProps/Readme.md) +Learn how use render props in React + +### [20 Error Boundaries](./20_ErrorBoundaries/readme.md) +Play with the Error Boundary concept + +### [21 Hooks](./21_Hooks/readme.md) +Play with the Error Boundary concept +make use of hooks a cool concept introduced in React 16.7.0 +### [22 Hooks_UseContext](./22_Hooks_UseContext/Readme.md) +Let's see how this is solved using Hooks + Use Context. + + + +# About Basefactor + Lemoncode + +We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. + +[Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. + +[Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. + +For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend