├── .gitignore ├── .travis.yml ├── README.md ├── how-to-translate.md ├── img ├── chai.png ├── eslint.png ├── flow.png ├── gulp.png ├── js.png ├── mocha.png ├── npm.png ├── react.png ├── redux.png ├── webpack.png └── yarn.png ├── mdlint.js ├── package.json ├── tutorial ├── 1-node-npm-yarn-package-json │ ├── README.md │ ├── index.js │ └── package.json ├── 10-immutable-redux-improvements │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── actions │ │ │ │ └── dog-actions.js │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ │ ├── button.jsx │ │ │ │ └── message.jsx │ │ │ ├── containers │ │ │ │ ├── bark-button.js │ │ │ │ └── bark-message.js │ │ │ └── reducers │ │ │ │ └── dog-reducer.js │ │ ├── server │ │ │ └── index.js │ │ └── shared │ │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 11-testing-mocha-chai-sinon │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── actions │ │ │ │ └── dog-actions.js │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ │ ├── button.jsx │ │ │ │ └── message.jsx │ │ │ ├── containers │ │ │ │ ├── bark-button.js │ │ │ │ └── bark-message.js │ │ │ └── reducers │ │ │ │ └── dog-reducer.js │ │ ├── server │ │ │ └── index.js │ │ ├── shared │ │ │ └── dog.js │ │ └── test │ │ │ ├── client │ │ │ └── state-test.js │ │ │ └── shared │ │ │ └── dog-test.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 12-flow │ ├── .flowconfig │ ├── .gitignore │ ├── README.md │ ├── dist │ │ ├── client-bundle.js.map │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ ├── actions │ │ │ │ └── dog-actions.js │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ │ ├── button.jsx │ │ │ │ └── message.jsx │ │ │ ├── containers │ │ │ │ ├── bark-button.js │ │ │ │ └── bark-message.js │ │ │ └── reducers │ │ │ │ └── dog-reducer.js │ │ ├── server │ │ │ └── index.js │ │ ├── shared │ │ │ └── dog.js │ │ └── test │ │ │ ├── client │ │ │ └── state-test.js │ │ │ └── shared │ │ │ └── dog-test.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 2-packages │ ├── .gitignore │ ├── README.md │ ├── index.js │ ├── package.json │ └── yarn.lock ├── 3-es6-babel-gulp │ ├── .gitignore │ ├── README.md │ ├── gulpfile.js │ ├── package.json │ ├── src │ │ └── index.js │ └── yarn.lock ├── 4-es6-syntax-class │ ├── .gitignore │ ├── README.md │ ├── gulpfile.js │ ├── package.json │ ├── src │ │ ├── dog.js │ │ └── index.js │ └── yarn.lock ├── 5-es6-modules-syntax │ ├── .gitignore │ ├── README.md │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── dog.js │ │ └── index.js │ └── yarn.lock ├── 6-eslint │ ├── .gitignore │ ├── README.md │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── dog.js │ │ └── index.js │ └── yarn.lock ├── 7-client-webpack │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ └── app.js │ │ ├── server │ │ │ └── index.js │ │ └── shared │ │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock ├── 8-react │ ├── .gitignore │ ├── README.md │ ├── dist │ │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ │ ├── client │ │ │ └── app.jsx │ │ ├── server │ │ │ └── index.js │ │ └── shared │ │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock └── 9-redux │ ├── .gitignore │ ├── README.md │ ├── dist │ └── index.html │ ├── gulpfile.babel.js │ ├── package.json │ ├── src │ ├── client │ │ ├── actions │ │ │ └── dog-actions.js │ │ ├── app.jsx │ │ ├── components │ │ │ ├── button.jsx │ │ │ └── message.jsx │ │ ├── containers │ │ │ ├── bark-button.js │ │ │ └── bark-message.js │ │ └── reducers │ │ │ └── dog-reducer.js │ ├── server │ │ └── index.js │ └── shared │ │ └── dog.js │ ├── webpack.config.babel.js │ └── yarn.lock └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | "6" 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | **ATTENZIONE: QUESTO REPOSITORY NON È AGGIORNATO!!!** 3 | 4 | **NUOVA VERSIONE AGGIORNATA QUI: https://github.com/fbertone/guida-javascript-moderno** 5 | 6 | # Stack JavaScript da Zero 7 | 8 | [![Build Status](https://travis-ci.org/verekia/js-stack-from-scratch.svg?branch=master)](https://travis-ci.org/verekia/js-stack-from-scratch) [![Entra in chat (in inglese) su https://gitter.im/js-stack-from-scratch/Lobby](https://badges.gitter.im/js-stack-from-scratch/Lobby.svg)](https://gitter.im/js-stack-from-scratch/Lobby) 9 | 10 | [![Yarn](/img/yarn.png)](https://yarnpkg.com/) 11 | [![React](/img/react.png)](https://facebook.github.io/react/) 12 | [![Gulp](/img/gulp.png)](http://gulpjs.com/) 13 | [![Redux](/img/redux.png)](http://redux.js.org/) 14 | [![ESLint](/img/eslint.png)](http://eslint.org/) 15 | [![Webpack](/img/webpack.png)](https://webpack.github.io/) 16 | [![Mocha](/img/mocha.png)](https://mochajs.org/) 17 | [![Chai](/img/chai.png)](http://chaijs.com/) 18 | [![Flow](/img/flow.png)](https://flowtype.org/) 19 | 20 | Benvenuto a questa guida sul Javascript moderno: **Stack Javascript da zero**. 21 | 22 | **ATTENZIONE: QUESTO REPOSITORY NON È AGGIORNATO!!!** 23 | 24 | **NUOVA VERSIONE AGGIORNATA QUI: https://github.com/fbertone/guida-javascript-moderno** 25 | 26 | Puoi trovare la versione originale della guida, in inglese, qua: [**JavaScript Stack from Scratch**](https://github.com/verekia/js-stack-from-scratch). 27 | 28 | Questa è una guida rapida per la creazione da zero di uno stack Javascript. Per seguire questa guida dovresti avere già qulche fondamento di programmazione generica e alcune basi di Javascript. Questa guida é focalizzata su come **collegare assieme vari tool di sviluppo** e fornesce un esempio il piú semplice possibile per ogni strumento trattato. Puoi vedere questa guida come un modo per *costruirti da zero il tuo boilerplate personalizzato*. 29 | 30 | 31 | Non avrai bisogno di utilizzare interamente questo stack se vuoi realizzare una semplice pagina web con alcune interazioni JS (una combinazione di Browserify/Webpack + Babel + jQuery é sufficiente per scrivere codice ES6 in diversi file con compilazione da linea di comando), ma se vuoi realizzare una web app scalabile questo tutorial sará perfetto. 32 | 33 | Siccome lo scopo di questa guida è di utilizzare vari strumenti in sequenza, non entrerò nei dettagli di ciascuno strumento. Se ti interessano dei dettagli maggiori o vuoi imparare più a fondo il funzionamento di un tool specifico puoi fare riferimento alla sua documentazione ufficiale o puoi cercare altri tutorial più specifici. 34 | 35 | Una parte sostanziosa di questa guida fa riferimento a React. Se stai iniziando adesso e vuoi solo imparare React, [create-react-app](https://github.com/facebookincubator/create-react-app) ti preparerá molto rapidamente con un ambiente React preconfigurato. Consiglierei questo approccio ad esempio a qualcuno che entra in un team che sta giá utilizzando React e deve mettersi al passo utilizzando un ambiente per fare delle prove. In questa guida non ti faró utilizzare una configurazione prefabbricata, perché voglio che tu comprenda realmente quello che sta succedendo. 36 | 37 | Esempi di codice sono disponibili in ogni capitolo e puoi avviarli utilizzando il comando `yarn && yarn start` oppure `npm install && npm start`. Ti suggerisco di riscrivere tu tutto da zero seguendo le **istruzioni passo a passo** di ogni capitolo. 38 | 39 | **Ogni capitolo contiene il codice dei capitoli precedenti**, se ti interessa unicamente creare un progetto di base contenente tutto puoi semplicemente clonare l'ultimo capitolo e sarai pronto per iniziare. 40 | 41 | Nota: l'ordine dei capitoli non è necessariamente il più didattico. Ad esempio, i test potrebbero essere stati spiegati prima di introdurre React. È abbastanza complicato riordinare i capitoli o modificare quelli passati perchè devo replicare ogni modifica sui capitoli successivi. Quando il tutto sarà più stabile potrei riorganizzare la guida in modo migliore. 42 | 43 | Il codice presentato in questa guida funziona in Linux, macOS e Windows. 44 | 45 | ## Indice 46 | 47 | [1 - Node, NPM, Yarn, e package.json](/tutorial/1-node-npm-yarn-package-json) 48 | 49 | [2 - Installare e utilizzare un pacchetto](/tutorial/2-packages) 50 | 51 | [3 - Configurare ES6 con Babel e Gulp](/tutorial/3-es6-babel-gulp) 52 | 53 | [4 - Usare la sintassi di ES6 con una classe](/tutorial/4-es6-syntax-class) 54 | 55 | [5 - La sintassi di ES6 per i moduli](/tutorial/5-es6-modules-syntax) 56 | 57 | [6 - ESLint](/tutorial/6-eslint) 58 | 59 | [7 - App client con Webpack](/tutorial/7-client-webpack) 60 | 61 | [8 - React](/tutorial/8-react) 62 | 63 | [9 - Redux](/tutorial/9-redux) 64 | 65 | [10 - Immutable JS e Migliorie di Redux](/tutorial/10-immutable-redux-improvements) 66 | 67 | [11 - Testare con Mocha, Chai, e Sinon](/tutorial/11-testing-mocha-chai-sinon) 68 | 69 | [12 - Type Checking con Flow](/tutorial/12-flow) 70 | 71 | ## Prossimamente 72 | 73 | Ambienti di sviluppo e produzione, Express, React Router, Server-Side Rendering, Styling, Enzyme, Git Hooks. 74 | 75 | ## Traduzioni 76 | 77 | - [English](https://github.com/verekia/js-stack-from-scratch) by [@verekia](https://twitter.com/verekia) 78 | - [中文](https://github.com/pd4d10/js-stack-from-scratch) by [@pd4d10](http://github.com/pd4d10) 79 | - [Italiano](https://github.com/fbertone/js-stack-from-scratch) by [Fabrizio Bertone](https://github.com/fbertone) 80 | - [日本語](https://github.com/takahashim/js-stack-from-scratch) by [@takahashim](https://github.com/takahashim) 81 | 82 | Se vuoi aggiungere una traduzione fai riferimento a [translation recommendations](https://github.com/verekia/js-stack-from-scratch/how-to-translate.md) per cominciare! 83 | 84 | ## Credits 85 | 86 | Creato da [@verekia](https://twitter.com/verekia) – [verekia.com](http://verekia.com/). 87 | Tradotto da Fabrizio Bertone 88 | 89 | Licenza: MIT 90 | -------------------------------------------------------------------------------- /how-to-translate.md: -------------------------------------------------------------------------------- 1 | # How to translate this tutorial 2 | 3 | Thank you for your interest in translating my tutorial! Here are a few recommendations to get started. 4 | 5 | This tutorial is in constant evolution to provide the best learning experience to readers. Both the code and `README.md` files will change over time. It is great if you do a one-shot translation that won't evolve, but it would be even better if you could try to keep up with the original English version as it changes! 6 | 7 | Here is what I think is a good workflow: 8 | 9 | - Check if there is already a translation issue open for your language. If that's the case, get in touch with the folks who opened it and consider collaborating. All maintainers will be mentioned on the English repo, so team work is encouraged! You can open issues on their translation fork project to offer your help on certain chapters for instance. 10 | 11 | - Join the [Translations Gitter room](https://gitter.im/js-stack-from-scratch/Translations) if you're feeling chatty. 12 | 13 | - Fork the main [English repository](https://github.com/verekia/js-stack-from-scratch). 14 | 15 | - Open an issue on the English repo to show you're currently working on a translation. 16 | 17 | - Translate the `README.md` files. 18 | 19 | - Add a note somewhere explaining on the main `README.md` that this is a translation, with a link to the English repository. If you don't plan to make the translation evolve over time, you can maybe add a little note saying to refer to the English one for an up-to-date version of the tutorial. I'll leave that up to your preference. 20 | 21 | - Submit a Pull Request to the English repo to add a link to your forked repository under the Translations section of the main `README.md`. It could look like this: 22 | 23 | ```md 24 | ## Translations 25 | 26 | - [Language](http://github.com/yourprofile/your-fork) by [You](http://yourwebsite.com) 27 | or 28 | - [Language](http://github.com/yourprofile/your-fork) by [@You](http://twitter.com/yourprofile) 29 | or 30 | - [Language](http://github.com/yourprofile/your-fork) by [@You](http://github.com/yourprofile) 31 | ``` 32 | 33 | Since I want to reward you for your good work as much as possible, you can put any link you like on your name (to your personal website, Twitter profile, or Github profile for instance). 34 | 35 | - After your original one-shot translation, if you want to update your repo with the latest change from the main English repo, [sync your fork](https://help.github.com/articles/syncing-a-fork/) with my repo. To make it easy to see what changed since your initial translation, you can use Github's feature to [compare commits](https://help.github.com/articles/comparing-commits-across-time/#comparing-commits). Set the **base** to the last commit from the English repo you used to translate, and compare it to **master**, like so: 36 | 37 | 38 | https://github.com/verekia/js-stack-from-scratch/compare/dfab78b581a3da800daeb3686b900dd9ea972da0...master 39 | 40 | 41 | That should give you a easy-to-read diff to see exactly what changed in `README.md` files since your translation! 42 | -------------------------------------------------------------------------------- /img/chai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/chai.png -------------------------------------------------------------------------------- /img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/eslint.png -------------------------------------------------------------------------------- /img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/flow.png -------------------------------------------------------------------------------- /img/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/gulp.png -------------------------------------------------------------------------------- /img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/js.png -------------------------------------------------------------------------------- /img/mocha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/mocha.png -------------------------------------------------------------------------------- /img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/npm.png -------------------------------------------------------------------------------- /img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/react.png -------------------------------------------------------------------------------- /img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/redux.png -------------------------------------------------------------------------------- /img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/webpack.png -------------------------------------------------------------------------------- /img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fbertone/js-stack-from-scratch/b67673bcb69b04abb91f9d4946a527dee8667c50/img/yarn.png -------------------------------------------------------------------------------- /mdlint.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const markdownlint = require('markdownlint'); 3 | 4 | const config = { 5 | 'default': true, 6 | 'line_length': false, 7 | 'no-emphasis-as-header': false 8 | } 9 | 10 | const files = glob.sync('**/*.md', { ignore: '**/node_modules/**' }); 11 | 12 | markdownlint({ files, config }, (err, result) => { 13 | if (!err) { 14 | const resultString = result.toString(); 15 | console.log('== Linting Markdown Files...'); 16 | if (resultString) { 17 | console.log(resultString); 18 | process.exit(1); 19 | } else { 20 | console.log('== OK!'); 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "test": "yarn run mdlint && cd tutorial/1-node-npm-yarn-package-json && yarn && yarn run tutorial-test && cd ../2-packages && yarn && yarn run tutorial-test && cd ../3-es6-babel-gulp && yarn && yarn run tutorial-test && cd ../4-es6-syntax-class && yarn && yarn run tutorial-test && cd ../5-es6-modules-syntax && yarn && yarn run tutorial-test && cd ../6-eslint && yarn && yarn run tutorial-test && cd ../7-client-webpack && yarn && yarn run tutorial-test && cd ../8-react && yarn && yarn run tutorial-test && cd ../9-redux && yarn && yarn run tutorial-test && cd ../10-immutable-redux-improvements && yarn && yarn run tutorial-test && cd ../11-testing-mocha-chai-sinon && yarn && yarn run tutorial-test && cd ../12-flow && yarn && yarn run tutorial-test", 7 | "mdlint": "node mdlint.js" 8 | }, 9 | "devDependencies": { 10 | "glob": "^7.1.1", 11 | "markdownlint": "^0.3.0", 12 | "yarn": "^0.16.1" 13 | }, 14 | "repository": "verekia/js-stack-from-scratch", 15 | "author": "Jonathan Verrecchia - @verekia", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /tutorial/1-node-npm-yarn-package-json/README.md: -------------------------------------------------------------------------------- 1 | # 1 - Node, NPM, Yarn, e package.json 2 | 3 | In questa sezione configureremo Node, NPM, Yarn e un file `package.json` di base. 4 | 5 | Prima di tutto dobbiamo installare Node, che non è utilizzato solo per il back-end ma è la base di tutti gli strumenti necessari per creare uno stack Javascript moderno lato Front-End. 6 | 7 | Vai alla [pagina di download](https://nodejs.org/en/download/current/) per installare su Windows o mac oppure segui le istruzioni sulla [pagina di installazione](https://nodejs.org/en/download/package-manager/) per le distribuzioni Linux. 8 | 9 | 10 | Ad esempio, su **Ubuntu / Debian**, devi eseguire i seguenti comandi per installare Node: 11 | 12 | ```bash 13 | curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 14 | sudo apt-get install -y nodejs 15 | ``` 16 | Ti serve una qualsiasi versione di Node > 6.5.0. 17 | 18 | `npm`, il package manager standard di Node, viene installato automaticamente assieme a Node, non devi quindi installarlo tu a mano. 19 | 20 | **Nota**: Se Node è già installato, installa `nvm` ([Node Version Manager](https://github.com/creationix/nvm)), utilizza `nvm` per installare in automatico l'ultima versione di Node. 21 | 22 | [Yarn](https://yarnpkg.com/) è un nuovo package manager ed è molto più veloce rispetto ad NPM, mantiene una cache dei pacchetti installati, e gestisce le dipendenze in modo più [prevedibile](https://yarnpkg.com/en/docs/yarn-lock). Da quando è [uscito](https://code.facebook.com/posts/1840075619545360) a ottobre 2016, è stato adottato molto rapidamente e sta diventando il package manager di riferimento per la comunità Javascript. In questo tutorial utilizzeremo Yarn. Se tu preferisci rimanere con NPM puoi sostituire tutti i comandi `yarn add` e `yarn add --dev` in questo tutorial rispettivamente con `npm install --save` e `npm install --dev`. 23 | 24 | - Installa Yarn seguendo le [istruzioni](https://yarnpkg.com/en/docs/install). Puoi utilizzare `npm install -g yarn` o `sudo npm install -g yarn` (si, stiamo sfruttando NPM per installare Yarn, proprio come utilizzeresti Internet Explorer o Safari per installare Chrome!). 25 | 26 | - Crea una nuova cartella di base ed entraci `cd` dentro. 27 | - Esegui `yarn init` e rispondi alle domande (`yarn init -y` per saltare tutte le domande), per generare automaticamente un file `package.json`. 28 | - Crea un file `index.js` contenente `console.log('Hello world')`. 29 | - Esegui `node .` in questa cartella (`index.js` è il file che Node cerca automaticamente all'interno della cartella corrente). Dovrebbe scrivere "Hello world". 30 | 31 | Eseguire `node .` per lanciare il nostro programma è un po' troppo di basso livello. Utilizzeremo invece uno script NPM/Yarn per avviare l'esecuzione del nostro programma. Questo ci permetterà di avere un livello di astrazione tale da poter sempre utilizare `yarn start`, anche quando il nostro programma si farà più complesso. 32 | 33 | - In `package.json`, aggiungi un oggetto `scripts` all'oggetto di base, strutturato cosí: 34 | 35 | ```json 36 | "scripts": { 37 | "start": "node ." 38 | } 39 | ``` 40 | 41 | `package.json` deve essere un file JSON valido, il che significa che non puoi avere delle virgole "finali" che sono invece utilizzabili nei normali oggetti Javascript. Fai quindi molta attenzione quando modifichi a mano il tuo file `package.json`. 42 | 43 | - Esegui `yarn start`. Dovrebbe scrivere `Hello world`. 44 | 45 | - Crea un file `.gitignore` ed aggiungici le seguenti righe: 46 | 47 | ```gitignore 48 | npm-debug.log 49 | yarn-error.log 50 | ``` 51 | 52 | **Nota**: Se dai un'occhiata a file `package.json` che fornisco, vedrai uno script `tutorial-test` in ogni capitolo. Questi script mi permettono di verificare che i capitoli sono a posto eseguendo `yarn && yarn start`. Puoi cancellarli tranquillamente nei tuoi progetti. 53 | 54 | Prossima sezione: [2 - Installare ed usare un pacchetto](/tutorial/2-packages) 55 | 56 | Torna all'[indice](https://github.com/fbertone/js-stack-from-scratch). 57 | -------------------------------------------------------------------------------- /tutorial/1-node-npm-yarn-package-json/index.js: -------------------------------------------------------------------------------- 1 | console.log('Hello world'); 2 | -------------------------------------------------------------------------------- /tutorial/1-node-npm-yarn-package-json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "node .", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "yarn start" 9 | }, 10 | "repository": "verekia/js-stack-from-scratch", 11 | "author": "Jonathan Verrecchia - @verekia", 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/README.md: -------------------------------------------------------------------------------- 1 | # 10 - Immutable JS e Migliorie Redux 2 | 3 | ## Immutable JS 4 | 5 | A differenza del capitolo precedente, questo é abbastanza semplice e consiste in piccoli miglioramenti. 6 | 7 | Per prima cosa aggiungeremo **Immutable JS** al nostro codice. Immutable é una libreria per manipolare oggetti senza modificarli. Invece di fare: 8 | 9 | ```javascript 10 | const obj = { a: 1 }; 11 | obj.a = 2; // Mutates `obj` 12 | ``` 13 | 14 | Faresti: 15 | 16 | ```javascript 17 | const obj = Immutable.Map({ a: 1 }); 18 | obj.set('a', 2); // Returns a new object without mutating `obj` 19 | ``` 20 | 21 | Questo approccio segue il paradigma della **programmazione funzionale**, che funziona molto bene con Redux. Le tue funzioni reducer *devono* essere funzioni pure che non alterano lo stato passato come parametro, ma ritornano invece un nuovo oggetto. Usiamo Immutable per assicurarci che sia cosí. 22 | 23 | - Esegui `yarn add immutable` 24 | 25 | Utilizzeremo `Map` nel nostro codice, ma ESLint e la configurazione Airbnb si lamenteranno dell'utilizzo di una dell'iniziale maiuscola per quella che non é una classe. Aggiungi questo al `package.json` sotto `eslintConfig`: 26 | 27 | ```json 28 | "rules": { 29 | "new-cap": [ 30 | 2, 31 | { 32 | "capIsNewExceptions": [ 33 | "Map", 34 | "List" 35 | ] 36 | } 37 | ] 38 | } 39 | ``` 40 | 41 | Questo fa in modo che `Map` e `List` (i due oggetti di Immutable che utilizzeremo in continuazione) siano delle eccezioni alle regole di ESLint. Questa formattazione JSON cosí prolissa é generata automaticamente da Yarn/NPM, non possiamo quindi renderla piú compatta. 42 | 43 | Ad ogni modo, torniamo a Immutable: 44 | 45 | Modifica `dog-reducer.js` in modo che diventi cosí: 46 | 47 | ```javascript 48 | import Immutable from 'immutable'; 49 | import { MAKE_BARK } from '../actions/dog-actions'; 50 | 51 | const initialState = Immutable.Map({ 52 | hasBarked: false, 53 | }); 54 | 55 | const dogReducer = (state = initialState, action) => { 56 | switch (action.type) { 57 | case MAKE_BARK: 58 | return state.set('hasBarked', action.payload); 59 | default: 60 | return state; 61 | } 62 | }; 63 | 64 | export default dogReducer; 65 | ``` 66 | 67 | Lo stato iniziale viene adesso creato utilizzando una Map di Immutable, e il nuovo stato viene generato utilizzando `set()`, impedendo ogni modifica allo stato precedente. 68 | 69 | In `containers/bark-message.js`, aggiorna la funzione `mapStateToProps` in modo da usare `.get('hasBarked')` al posto di `.hasBarked`: 70 | 71 | ```javascript 72 | const mapStateToProps = state => ({ 73 | message: state.dog.get('hasBarked') ? 'The dog barked' : 'The dog did not bark', 74 | }); 75 | ``` 76 | 77 | L'app dovrebbe ancora comportarsi esattamente come prima. 78 | 79 | **Nota**: Se Babel si lamenta del fatto che Immutable supera i 100KB, aggiungi `"compact": false` nel `package.json` alla voce `babel`. 80 | 81 | Come puoi vedere dal pezzo di codice precedente, il nostro oggetto di stato contiene ancora l'attributo `dog` che non é immutabile. Va bene cosí, ma se vuoi manipolare esclusivamente oggetti immutabili, puoi installare il pacchetto `redux-immutable` per sostituire la funzione `combineReducers` di Redux. 82 | 83 | **Opzionale**: 84 | 85 | - Esegui `yarn add redux-immutable` 86 | - Sostituisci la funzione `combineReducers` in `app.jsx` per usare quella importata da `redux-immutable`. 87 | - In `bark-message.js` sostituisci `state.dog.get('hasBarked')` con `state.getIn(['dog', 'hasBarked'])`. 88 | 89 | ## Azioni Redux 90 | 91 | Man mano che aggiungi sempre piú azioni alla tua app, ti accorgerai che stai scrivendo molto codice ripetuto. Il pacchetto `redux-actions` ti aiuterá a ridurre le ripetizioni. Con `redux-actions` puoi riscrivere il file `dog-actions.js` in modo piú compatto: 92 | 93 | ```javascript 94 | import { createAction } from 'redux-actions'; 95 | 96 | export const MAKE_BARK = 'MAKE_BARK'; 97 | export const makeBark = createAction(MAKE_BARK, () => true); 98 | ``` 99 | 100 | `redux-actions` implementa il modello di [Azione Standard Flux](https://github.com/acdlite/flux-standard-action), proprio come l'azione che abbiamo scritto precedentemente, quindi integrare `redux-actions` sará del tutto lineare se segui questo modello. 101 | 102 | - Non dimenticarti di eseguire `yarn add redux-actions`. 103 | 104 | Prossima sezione: [11 - Testare con Mocha, Chai e Sinon](/tutorial/11-testing-mocha-chai-sinon) 105 | 106 | Torna alla [sezione precedente](/tutorial/9-redux) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 107 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import del from 'del'; 7 | import webpack from 'webpack-stream'; 8 | import webpackConfig from './webpack.config.babel'; 9 | 10 | const paths = { 11 | allSrcJs: 'src/**/*.js?(x)', 12 | serverSrcJs: 'src/server/**/*.js?(x)', 13 | sharedSrcJs: 'src/shared/**/*.js?(x)', 14 | clientEntryPoint: 'src/client/app.jsx', 15 | clientBundle: 'dist/client-bundle.js?(.map)', 16 | gulpFile: 'gulpfile.babel.js', 17 | webpackFile: 'webpack.config.babel.js', 18 | libDir: 'lib', 19 | distDir: 'dist', 20 | }; 21 | 22 | gulp.task('lint', () => 23 | gulp.src([ 24 | paths.allSrcJs, 25 | paths.gulpFile, 26 | paths.webpackFile, 27 | ]) 28 | .pipe(eslint()) 29 | .pipe(eslint.format()) 30 | .pipe(eslint.failAfterError()) 31 | ); 32 | 33 | gulp.task('clean', () => del([ 34 | paths.libDir, 35 | paths.clientBundle, 36 | ])); 37 | 38 | gulp.task('build', ['lint', 'clean'], () => 39 | gulp.src(paths.allSrcJs) 40 | .pipe(babel()) 41 | .pipe(gulp.dest(paths.libDir)) 42 | ); 43 | 44 | gulp.task('main', ['lint', 'clean'], () => 45 | gulp.src(paths.clientEntryPoint) 46 | .pipe(webpack(webpackConfig)) 47 | .pipe(gulp.dest(paths.distDir)) 48 | ); 49 | 50 | gulp.task('watch', () => { 51 | gulp.watch(paths.allSrcJs, ['main']); 52 | }); 53 | 54 | gulp.task('default', ['watch', 'main']); 55 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ], 15 | "env": { 16 | "browser": true 17 | }, 18 | "rules": { 19 | "new-cap": [ 20 | 2, 21 | { 22 | "capIsNewExceptions": [ 23 | "Map", 24 | "List" 25 | ] 26 | } 27 | ] 28 | } 29 | }, 30 | "babel": { 31 | "presets": [ 32 | "latest", 33 | "react" 34 | ], 35 | "compact": false 36 | }, 37 | "dependencies": { 38 | "babel-polyfill": "^6.16.0", 39 | "immutable": "^3.8.1", 40 | "react": "^15.3.2", 41 | "react-dom": "^15.3.2", 42 | "react-redux": "^4.4.5", 43 | "redux": "^3.6.0", 44 | "redux-actions": "^0.12.0", 45 | "redux-immutable": "^3.0.8" 46 | }, 47 | "devDependencies": { 48 | "babel-loader": "^6.2.5", 49 | "babel-preset-latest": "^6.16.0", 50 | "babel-preset-react": "^6.16.0", 51 | "del": "^2.2.2", 52 | "eslint": "^3.8.1", 53 | "eslint-config-airbnb": "^12.0.0", 54 | "eslint-plugin-import": "^2.0.1", 55 | "eslint-plugin-jsx-a11y": "^2.2.3", 56 | "eslint-plugin-react": "^6.4.1", 57 | "gulp": "^3.9.1", 58 | "gulp-babel": "^6.1.2", 59 | "gulp-eslint": "^3.0.1", 60 | "webpack-stream": "^3.2.0" 61 | }, 62 | "repository": "verekia/js-stack-from-scratch", 63 | "author": "Jonathan Verrecchia - @verekia", 64 | "license": "MIT" 65 | } 66 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/actions/dog-actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | export const MAKE_BARK = 'MAKE_BARK'; 4 | export const makeBark = createAction(MAKE_BARK, () => true); 5 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { createStore } from 'redux'; 6 | import { Provider } from 'react-redux'; 7 | import { combineReducers } from 'redux-immutable'; 8 | import dogReducer from './reducers/dog-reducer'; 9 | import BarkMessage from './containers/bark-message'; 10 | import BarkButton from './containers/bark-button'; 11 | 12 | const store = createStore(combineReducers({ 13 | dog: dogReducer, 14 | })); 15 | 16 | ReactDOM.render( 17 | 18 |
19 | 20 | 21 |
22 |
23 | , document.querySelector('.app') 24 | ); 25 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/components/button.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Button = ({ action, actionLabel }) => ; 4 | 5 | Button.propTypes = { 6 | action: PropTypes.func.isRequired, 7 | actionLabel: PropTypes.string.isRequired, 8 | }; 9 | 10 | export default Button; 11 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/components/message.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Message = ({ message }) =>
{message}
; 4 | 5 | Message.propTypes = { 6 | message: PropTypes.string.isRequired, 7 | }; 8 | 9 | export default Message; 10 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/containers/bark-button.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Button from '../components/button'; 3 | import { makeBark } from '../actions/dog-actions'; 4 | 5 | const mapDispatchToProps = dispatch => ({ 6 | action: () => { dispatch(makeBark()); }, 7 | actionLabel: 'Bark', 8 | }); 9 | 10 | export default connect(null, mapDispatchToProps)(Button); 11 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/containers/bark-message.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Message from '../components/message'; 3 | 4 | const mapStateToProps = state => ({ 5 | message: state.getIn(['dog', 'hasBarked']) ? 'The dog barked' : 'The dog did not bark', 6 | }); 7 | 8 | export default connect(mapStateToProps)(Message); 9 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/client/reducers/dog-reducer.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | import { MAKE_BARK } from '../actions/dog-actions'; 3 | 4 | const initialState = Immutable.Map({ 5 | hasBarked: false, 6 | }); 7 | 8 | const dogReducer = (state = initialState, action) => { 9 | switch (action.type) { 10 | case MAKE_BARK: 11 | return state.set('hasBarked', action.payload); 12 | default: 13 | return state; 14 | } 15 | }; 16 | 17 | export default dogReducer; 18 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Dog from '../shared/dog'; 4 | 5 | const toby = new Dog('Toby'); 6 | 7 | console.log(toby.bark()); 8 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/src/shared/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | export default Dog; 13 | -------------------------------------------------------------------------------- /tutorial/10-immutable-redux-improvements/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | output: { 3 | filename: 'client-bundle.js', 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.jsx?$/, 10 | loader: 'babel-loader', 11 | exclude: [/node_modules/], 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.jsx'], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/README.md: -------------------------------------------------------------------------------- 1 | # 11 - Testare con Mocha, Chai, e Sinon 2 | 3 | ## Mocha e Chai 4 | 5 | - Crea una cartella `src/test`. Questa cartella rispecchiera' la struttura delle cartelle della nostra applicazione, crea quindi anche `src/test/client` (se vuoi puoi aggiungere anche `server` e `shared`, ma non scriveremo dei test per quelle). 6 | 7 | - In `src/test/client`, crea `state-test.js` che utilizzeremo per testare il ciclo della nostra applicazione Redux. 8 | 9 | Useremo [Mocha](http://mochajs.org/) come framework di test principale. Mocha e' semplice da usare, ha molte funzionalita', ed e' attualmente il [framework di test per JavaScript piu' popolare](http://stateofjs.com/2016/testing/). E' molto flessibile e modulare. In particolare, ti permette di utilizzare la libreria di assert che vuoi. [Chai](http://chaijs.com/) e' un'ottima libreria di assert, sono disponibili molti [plugin](http://chaijs.com/plugins/) e ti permette di scegliere diversi stili di assert. 10 | 11 | - Installiamo Mocha e Chai eseguendo `yarn add --dev mocha chai` 12 | 13 | In `state-test.js`, scrivi: 14 | 15 | ```javascript 16 | /* eslint-disable import/no-extraneous-dependencies, no-unused-expressions */ 17 | 18 | import { createStore } from 'redux'; 19 | import { combineReducers } from 'redux-immutable'; 20 | import { should } from 'chai'; 21 | import { describe, it, beforeEach } from 'mocha'; 22 | import dogReducer from '../../client/reducers/dog-reducer'; 23 | import { makeBark } from '../../client/actions/dog-actions'; 24 | 25 | should(); 26 | let store; 27 | 28 | describe('App State', () => { 29 | describe('Dog', () => { 30 | beforeEach(() => { 31 | store = createStore(combineReducers({ 32 | dog: dogReducer, 33 | })); 34 | }); 35 | describe('makeBark', () => { 36 | it('should make hasBarked go from false to true', () => { 37 | store.getState().getIn(['dog', 'hasBarked']).should.be.false; 38 | store.dispatch(makeBark()); 39 | store.getState().getIn(['dog', 'hasBarked']).should.be.true; 40 | }); 41 | }); 42 | }); 43 | }); 44 | ``` 45 | 46 | OK, analizziamo il tutto. 47 | 48 | Innanzitutto, nota come importiamo lo stile di assert `should` da `chai`. Questo ci permette di testare cose come `mynumber.should.equal(3)`, abbastanza chiaro. Per chiamare `should` su un qualsiasi oggetto, dobbiamo eseguire la funzione `should()` prima di qualsiasi altra cosa. Alcune di queste verifiche sono *espressioni*, come `mybook.should.be.true`, che faranno arrabbiare ESLint, abbiamo quindi aggiunto un commento ESLint in cima al file, in modo da disabilitare la regola `no-unused-expressions`. 49 | 50 | I test di Mocha funzionano ad albero. Nel nostro caso vogliamo testare la funzione `makeBark`, che dovrebbe modificare l'attributo `dog` dello stato dell'applicazione, ha quindi senso utilizzare la seguente sequenza dii test: `App State > Dog > makeBark`, che dichiariamo utilizzando `describe()`. `it()` e' la funzione di test effettiva, `beforeEach()` e' una funzione che viene richiamata prima di ogni test `it()`. Nel nostro caso vogliamo un nuovo store prima dell'esecuzione di un nuovo test. Dichiariamo una variabile `store` all'inizio del file perche' tornera' utile in ogni test successivo. 51 | 52 | Il nostro test per `makeBark` e' molto esplicito, e la descrizione, inserita come stringa in `it()`, lo rende ancora piu' chiaro: verifichiamo che `hasBarked` va da `false` a `true` dopo aver chiamato `makeBark`. 53 | 54 | Bene, eseguiamo questo test! 55 | 56 | - In `gulpfile.babel.js`, crea il seguente task `test` , che utilizza il plugin `gulp-mocha`: 57 | 58 | ```javascript 59 | import mocha from 'gulp-mocha'; 60 | 61 | const paths = { 62 | // [...] 63 | allLibTests: 'lib/test/**/*.js', 64 | }; 65 | 66 | // [...] 67 | 68 | gulp.task('test', ['build'], () => 69 | gulp.src(paths.allLibTests) 70 | .pipe(mocha()) 71 | ); 72 | ``` 73 | 74 | - Esegui `yarn add --dev gulp-mocha`. 75 | 76 | Come puoi notare, i test sono eseguiti sul codice convertito presente in `lib`, ecco perche' `build` e' un task che deve essere eseguito come prerequisito di `test`. `build` a sua volta ha un prerequisito, `lint`, ed in fine renderemo `test` un prerequisito di `main`, che ci porta ad avere la seguente cascata di task per `default`: `lint` > `build` > `test` > `main`. 77 | 78 | - Modifica il prerequisito di `main` a `test`: 79 | 80 | ```javascript 81 | gulp.task('main', ['test'], () => /* ... */ ); 82 | ``` 83 | 84 | - In `package.json`, sostituisci lo script `"test"` attuale con: `"test": "gulp test"`. In questo modo puoi eseguire `yarn test` per lanciare semplicemente i test. `test` e' inoltre lo script standard che viene lanciato dai tool utilizzati nei servizi di continuous integration, dovresti quindi collegarci sempre i tuoi test. `yarn start` eseguira' inoltre i test prima di fare la build Webpack del client, lo generera' quindi se tutti i test hanno successo. 85 | 86 | - Esegui `yarn test` o `yarn start`, e dovrebbe uscire il risultato dei nostri test, si sprea che sia verde. 87 | 88 | ## Sinon 89 | 90 | In alcuni casi avremo la necessita' di *falsificare* cose per eseguire un test. Ad esempio, immaginiamo di avere una funzione, `deleteEverything`, che contiene una chiamata a `deleteDatabases()`. Eseguire realmente `deleteDatabases()` causerebbe molti effetti secondari, cosa che non vogliamo succeda quando eseguiamo dei test. 91 | 92 | [Sinon](http://sinonjs.org/) e' una libreria di test che offre la possibilita' di creare **Stubs** (oltre a molte altre cose), che ci permette di neutralizzare `deleteDatabases` semplicemente monitorandola senza andarla ad eseguire realmente. In questo modo possiamo verificare se e' stata chiamata, oppure, ad esempio, con quali parametri e' stata richiamata. Questo tipicamente e' molto utile per simulare chiamate AJAX - che possono avere molti effetti sul back-end. 93 | 94 | INel contesto della nostra applicazione, aggiungeremo un metodo `barkInConsole` alla nostra classe `Dog` in `src/shared/dog.js`: 95 | 96 | ```javascript 97 | class Dog { 98 | constructor(name) { 99 | this.name = name; 100 | } 101 | 102 | bark() { 103 | return `Wah wah, I am ${this.name}`; 104 | } 105 | 106 | barkInConsole() { 107 | /* eslint-disable no-console */ 108 | console.log(this.bark()); 109 | /* eslint-enable no-console */ 110 | } 111 | } 112 | 113 | export default Dog; 114 | ``` 115 | 116 | Se eseguiamo `barkInConsole` in un test, `console.log()` scrivera' delle cose nel terminale. Nel contesto dei nostri test, questo e' considerato un effetto indesiderato. Siamo tuttavia interessati a conoscere se `console.log()` *sarebbe stata di norma chiamata*, e vogliamo verificare con quali parametri *sarebbe stata chiamata*. 117 | 118 | - Crea un nuovo file `src/test/shared/dog-test.js` e aggiungi quanto segue: 119 | 120 | ```javascript 121 | /* eslint-disable import/no-extraneous-dependencies, no-console */ 122 | 123 | import chai from 'chai'; 124 | import { stub } from 'sinon'; 125 | import sinonChai from 'sinon-chai'; 126 | import { describe, it } from 'mocha'; 127 | import Dog from '../../shared/dog'; 128 | 129 | chai.should(); 130 | chai.use(sinonChai); 131 | 132 | describe('Shared', () => { 133 | describe('Dog', () => { 134 | describe('barkInConsole', () => { 135 | it('should print a bark string with its name', () => { 136 | stub(console, 'log'); 137 | new Dog('Test Toby').barkInConsole(); 138 | console.log.should.have.been.calledWith('Wah wah, I am Test Toby'); 139 | console.log.restore(); 140 | }); 141 | }); 142 | }); 143 | }); 144 | ``` 145 | 146 | Qua abbiamo utilizzato delle *stubs* di Sinon, ed un plugin di Chai per poter eseguire delle verifiche di Chai sulle stub di Sinon. 147 | 148 | - Esegui `yarn add --dev sinon sinon-chai` per installare queste librerie. 149 | 150 | Cosa c'e' di nuovo? Prima di tutto, chiamiamo `chai.use(sinonChai)` per attivare il plugin di Chai. Poi tutta la magia avviene nel costrutto `it()`: `stub(console, 'log')` serve a neutralizzare `console.log` e monitorarla. Quando `new Dog('Test Toby').barkInConsole()` viene eseguita, si suppone che avvenga una `console.log`. Testiamo questa chiamata a `console.log` tramite `console.log.should.have.been.calledWith()`, ed infine facciamo un `restore` della `console.log` neutralizzata, in modo da farla nuovamente funzionare. 151 | 152 | **Nota importante**: Fare lo stub di `console.log` non e' raccomandabile, perche' se il test fallisce, `console.log.restore()` non viene mai chiamata, di conseguenza `console.log` rimarra' non funzionante per i successivi comandi che andrai ad eseguire sul terminale! Non riportera' neppure l'errore che ha fatto fallire il test, lasciandoti quindi con poche informazioni su quello che e' successo. Questo puo' confonderti abbastanza. Tuttavia e' un buon esempio per dimostrare l'utilizzo delle stub in questo contesto. 153 | 154 | Se tutto e' andato per il meglio in questo capitolo, dovresti avere due test passati con successo. 155 | 156 | Prossima sezione: [12 - Type Checking con Flow](/tutorial/12-flow) 157 | 158 | Torna alla [sezione precedente](/tutorial/10-immutable-redux-improvements) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 159 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import mocha from 'gulp-mocha'; 7 | import del from 'del'; 8 | import webpack from 'webpack-stream'; 9 | import webpackConfig from './webpack.config.babel'; 10 | 11 | const paths = { 12 | allSrcJs: 'src/**/*.js?(x)', 13 | serverSrcJs: 'src/server/**/*.js?(x)', 14 | sharedSrcJs: 'src/shared/**/*.js?(x)', 15 | allLibTests: 'lib/test/**/*.js', 16 | clientEntryPoint: 'src/client/app.jsx', 17 | clientBundle: 'dist/client-bundle.js?(.map)', 18 | gulpFile: 'gulpfile.babel.js', 19 | webpackFile: 'webpack.config.babel.js', 20 | libDir: 'lib', 21 | distDir: 'dist', 22 | }; 23 | 24 | gulp.task('lint', () => 25 | gulp.src([ 26 | paths.allSrcJs, 27 | paths.gulpFile, 28 | paths.webpackFile, 29 | ]) 30 | .pipe(eslint()) 31 | .pipe(eslint.format()) 32 | .pipe(eslint.failAfterError()) 33 | ); 34 | 35 | gulp.task('clean', () => del([ 36 | paths.libDir, 37 | paths.clientBundle, 38 | ])); 39 | 40 | gulp.task('build', ['lint', 'clean'], () => 41 | gulp.src(paths.allSrcJs) 42 | .pipe(babel()) 43 | .pipe(gulp.dest(paths.libDir)) 44 | ); 45 | 46 | gulp.task('test', ['build'], () => 47 | gulp.src(paths.allLibTests) 48 | .pipe(mocha()) 49 | ); 50 | 51 | gulp.task('main', ['test'], () => 52 | gulp.src(paths.clientEntryPoint) 53 | .pipe(webpack(webpackConfig)) 54 | .pipe(gulp.dest(paths.distDir)) 55 | ); 56 | 57 | gulp.task('watch', () => { 58 | gulp.watch(paths.allSrcJs, ['main']); 59 | }); 60 | 61 | gulp.task('default', ['watch', 'main']); 62 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "gulp test", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ], 15 | "env": { 16 | "browser": true 17 | }, 18 | "rules": { 19 | "new-cap": [ 20 | 2, 21 | { 22 | "capIsNewExceptions": [ 23 | "Map", 24 | "List" 25 | ] 26 | } 27 | ] 28 | } 29 | }, 30 | "babel": { 31 | "presets": [ 32 | "latest", 33 | "react" 34 | ], 35 | "compact": false 36 | }, 37 | "dependencies": { 38 | "babel-polyfill": "^6.16.0", 39 | "immutable": "^3.8.1", 40 | "react": "^15.3.2", 41 | "react-dom": "^15.3.2", 42 | "react-redux": "^4.4.5", 43 | "redux": "^3.6.0", 44 | "redux-actions": "^0.12.0", 45 | "redux-immutable": "^3.0.8" 46 | }, 47 | "devDependencies": { 48 | "babel-loader": "^6.2.5", 49 | "babel-preset-latest": "^6.16.0", 50 | "babel-preset-react": "^6.16.0", 51 | "chai": "^3.5.0", 52 | "del": "^2.2.2", 53 | "eslint": "^3.8.1", 54 | "eslint-config-airbnb": "^12.0.0", 55 | "eslint-plugin-import": "^2.0.1", 56 | "eslint-plugin-jsx-a11y": "^2.2.3", 57 | "eslint-plugin-react": "^6.4.1", 58 | "gulp": "^3.9.1", 59 | "gulp-babel": "^6.1.2", 60 | "gulp-eslint": "^3.0.1", 61 | "gulp-mocha": "^3.0.1", 62 | "mocha": "^3.1.2", 63 | "sinon": "^1.17.6", 64 | "sinon-chai": "^2.8.0", 65 | "webpack-stream": "^3.2.0" 66 | }, 67 | "repository": "verekia/js-stack-from-scratch", 68 | "author": "Jonathan Verrecchia - @verekia", 69 | "license": "MIT" 70 | } 71 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/actions/dog-actions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | 3 | export const MAKE_BARK = 'MAKE_BARK'; 4 | export const makeBark = createAction(MAKE_BARK, () => true); 5 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { createStore } from 'redux'; 6 | import { Provider } from 'react-redux'; 7 | import { combineReducers } from 'redux-immutable'; 8 | import dogReducer from './reducers/dog-reducer'; 9 | import BarkMessage from './containers/bark-message'; 10 | import BarkButton from './containers/bark-button'; 11 | 12 | const store = createStore(combineReducers({ 13 | dog: dogReducer, 14 | })); 15 | 16 | ReactDOM.render( 17 | 18 |
19 | 20 | 21 |
22 |
23 | , document.querySelector('.app') 24 | ); 25 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/components/button.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Button = ({ action, actionLabel }) => ; 4 | 5 | Button.propTypes = { 6 | action: PropTypes.func.isRequired, 7 | actionLabel: PropTypes.string.isRequired, 8 | }; 9 | 10 | export default Button; 11 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/components/message.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Message = ({ message }) =>
{message}
; 4 | 5 | Message.propTypes = { 6 | message: PropTypes.string.isRequired, 7 | }; 8 | 9 | export default Message; 10 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/containers/bark-button.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Button from '../components/button'; 3 | import { makeBark } from '../actions/dog-actions'; 4 | 5 | const mapDispatchToProps = dispatch => ({ 6 | action: () => { dispatch(makeBark()); }, 7 | actionLabel: 'Bark', 8 | }); 9 | 10 | export default connect(null, mapDispatchToProps)(Button); 11 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/containers/bark-message.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Message from '../components/message'; 3 | 4 | const mapStateToProps = state => ({ 5 | message: state.getIn(['dog', 'hasBarked']) ? 'The dog barked' : 'The dog did not bark', 6 | }); 7 | 8 | export default connect(mapStateToProps)(Message); 9 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/client/reducers/dog-reducer.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | import { MAKE_BARK } from '../actions/dog-actions'; 3 | 4 | const initialState = Immutable.Map({ 5 | hasBarked: false, 6 | }); 7 | 8 | const dogReducer = (state = initialState, action) => { 9 | switch (action.type) { 10 | case MAKE_BARK: 11 | return state.set('hasBarked', action.payload); 12 | default: 13 | return state; 14 | } 15 | }; 16 | 17 | export default dogReducer; 18 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Dog from '../shared/dog'; 4 | 5 | const toby = new Dog('Toby'); 6 | 7 | console.log(toby.bark()); 8 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/shared/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | 11 | barkInConsole() { 12 | /* eslint-disable no-console */ 13 | console.log(this.bark()); 14 | /* eslint-enable no-console */ 15 | } 16 | } 17 | 18 | export default Dog; 19 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/test/client/state-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, no-unused-expressions */ 2 | 3 | import { createStore } from 'redux'; 4 | import { combineReducers } from 'redux-immutable'; 5 | import { should } from 'chai'; 6 | import { describe, it, beforeEach } from 'mocha'; 7 | import dogReducer from '../../client/reducers/dog-reducer'; 8 | import { makeBark } from '../../client/actions/dog-actions'; 9 | 10 | should(); 11 | let store; 12 | 13 | describe('App State', () => { 14 | describe('Dog', () => { 15 | beforeEach(() => { 16 | store = createStore(combineReducers({ 17 | dog: dogReducer, 18 | })); 19 | }); 20 | describe('makeBark', () => { 21 | it('should make hasBarked go from false to true', () => { 22 | store.getState().getIn(['dog', 'hasBarked']).should.be.false; 23 | store.dispatch(makeBark()); 24 | store.getState().getIn(['dog', 'hasBarked']).should.be.true; 25 | }); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/src/test/shared/dog-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, no-console */ 2 | 3 | import chai from 'chai'; 4 | import { stub } from 'sinon'; 5 | import sinonChai from 'sinon-chai'; 6 | import { describe, it } from 'mocha'; 7 | import Dog from '../../shared/dog'; 8 | 9 | chai.should(); 10 | chai.use(sinonChai); 11 | 12 | describe('Shared', () => { 13 | describe('Dog', () => { 14 | describe('barkInConsole', () => { 15 | it('should print a bark string with its name', () => { 16 | stub(console, 'log'); 17 | new Dog('Test Toby').barkInConsole(); 18 | console.log.should.have.been.calledWith('Wah wah, I am Test Toby'); 19 | console.log.restore(); 20 | }); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tutorial/11-testing-mocha-chai-sinon/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | output: { 3 | filename: 'client-bundle.js', 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.jsx?$/, 10 | loader: 'babel-loader', 11 | exclude: [/node_modules/], 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.jsx'], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tutorial/12-flow/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | .*/node_modules/gulp-flowtype/.* 4 | -------------------------------------------------------------------------------- /tutorial/12-flow/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/12-flow/README.md: -------------------------------------------------------------------------------- 1 | # 12 - Type Checking con Flow 2 | 3 | [Flow](https://flowtype.org/) é un controllore di tipi di dato statico. Riconosce le inconsistenze nei tipi di dato del tuo codice e puoi aggiungere delle dichiarazioni esplicite dei tipi di dato da utilizzare tramite le sue annotazioni. 4 | 5 | - Per fare in modo che Babel comprenda e rimuova le annotazioni di Flow durante il processo di transpiling, installa il preset di Flow per Babel eseguendo `yarn add --dev babel-preset-flow`. Poi, aggiungi `"flow"` alla voce `babel.presets` nel `package.json`. 6 | 7 | - Crea un file vuoto `.flowconfig` nella cartella di base del tuo progetto 8 | 9 | - Esegui `yarn add --dev gulp-flowtype` per installare il plugin di Gulp per Flow, e aggiungi `flow()` nel task `lint`: 10 | 11 | ```javascript 12 | import flow from 'gulp-flowtype'; 13 | 14 | // [...] 15 | 16 | gulp.task('lint', () => 17 | gulp.src([ 18 | paths.allSrcJs, 19 | paths.gulpFile, 20 | paths.webpackFile, 21 | ]) 22 | .pipe(eslint()) 23 | .pipe(eslint.format()) 24 | .pipe(eslint.failAfterError()) 25 | .pipe(flow({ abort: true })) // Add Flow here 26 | ); 27 | ``` 28 | 29 | L'opzione `abort` serve ad interrompere il task Gulp quando Flow riscontra un problema. 30 | 31 | Bene, dovremmo essere in grado di eseguire Flow. 32 | 33 | - Aggiungi le annotazioni di Flow nel file `src/shared/dog.js` in questo modo: 34 | 35 | ```javascript 36 | // @flow 37 | 38 | class Dog { 39 | name: string; 40 | 41 | constructor(name: string) { 42 | this.name = name; 43 | } 44 | 45 | bark(): string { 46 | return `Wah wah, I am ${this.name}`; 47 | } 48 | 49 | barkInConsole() { 50 | /* eslint-disable no-console */ 51 | console.log(this.bark()); 52 | /* eslint-enable no-console */ 53 | } 54 | 55 | } 56 | 57 | export default Dog; 58 | ``` 59 | 60 | Il commento `// @flow` dice a Flow che vogliamo verificare i tipi di dato in questo file. Per il resto, le annotazioni di Flow si fanno con un simbolo di duepunti sopo il nome di un parametro o una variabile. Controlla la documentazione per avere altri dettagli. 61 | 62 | Adesso se esegui `yarn start`, Flow funzionerá correttamente, ma ESLint si lamenterá della sintassi non standard che stiamo utilizzando. Siccome il parser di Babel é pronto per macinare le annotazioni di Flow grazie al plugin `babel-preset-flow` che abbiamo installato, sarebbe bello se ESLint potesse fare riferimento al parser di Babel invece di cercare di comprendere le annotazioni di Flow per conto suo. Questo é possibile utilizzando il pacchetto `babel-eslint`. Facciamolo. 63 | 64 | - Esegui `yarn add --dev babel-eslint` 65 | 66 | - In `package.json`, alla voce `eslintConfig`, aggiungi questa proprietá: `"parser": "babel-eslint"` 67 | 68 | `yarn start` adesso dovrebbe eseguire sia il lint che il typecheck del codice. 69 | 70 | Adesso che ESLint e Babel condividono lo stesso parser, possiamo fare in modo che ESLint esegua il lint delle nostre annotazioni Flow tramite il plugin `eslint-plugin-flowtype`. 71 | 72 | - Esegui `yarn add --dev eslint-plugin-flowtype` e aggiungi `"flowtype"` alla voce `eslintConfig.plugins` in `package.json`, e aggiungi `"plugin:flowtype/recommended"` alla voce `eslintConfig.extends` in un array vicino a `"airbnb"`. 73 | 74 | Adesso se scrivi `name:string` come annotazione, ESLint dovrebbe ad esempio lamentarsi del fatto che hai dimenticato uno spazio dopo il duepunti. 75 | 76 | **Nota**: La proprietá `"parser": "babel-eslint"` che ti ho fatto scrivere in `package.json` é inclusa nella configurazione `"plugin:flowtype/recommended"`, puoi quindi rimuoverla per ottimizzare il file `package.json`. Se la lasci lí peró rendi il tutto piú esplicito, dipende quindi da te. Siccome questa guida si riferisce alla configurazione minimale, io lo tolgo. 77 | 78 | - Adesso puoi aggiungere `// @flow` in ogni file `.js` e `.jsx` nella cartella `src`, eseguire `yarn test` o `yarn start`, aggiungere annotazioni come ti chiede di fare Flow. 79 | 80 | Un caso non intuitivo é questo, per `src/client/component/message.jsx`: 81 | 82 | ```javascript 83 | const Message = ({ message }: { message: string }) =>
{message}
; 84 | ``` 85 | 86 | Come puoi vedere, quando destrutturi i parametri delle funzioni, devi annotare le proprietá estratte utilizzando una sorta di oggetto. 87 | 88 | Un altro caso che incontrerai é quello in `src/client/reducers/dog-reducer.js`, Flow si lamenterá che Immutable non ha un export di default. Questo problema é discusso nel [#863 su Immutable](https://github.com/facebook/immutable-js/issues/863), che mostra 2 workaround: 89 | 90 | ```javascript 91 | import { Map as ImmutableMap } from 'immutable'; 92 | // or 93 | import * as Immutable from 'immutable'; 94 | ``` 95 | 96 | Fino a quando Immutable non risolve ufficialmente questo problema, scegli semplicemente quello che ti sembra migliore mentre importi componenti Immutable. Io personalmente ho scelto `import * as Immutable from 'immutable'` siccome é piú corto e non richiederá di fare un refactoring del codice quando il problema verrá risolto. 97 | 98 | **Nota**: Se Flow riscontra errori nei controlli dei tipi nella cartella `node_modules`, aggiungi una sezione `[ignore]` nel `.flowconfig` per ignorare specificamente i pacchetti che causano gli errori (non ignorare completamente la cartella `node_modules`). Potrebbe assomigliare a qualcosa come: 99 | 100 | ```flowconfig 101 | [ignore] 102 | 103 | .*/node_modules/gulp-flowtype/.* 104 | ``` 105 | 106 | Nel mio caso, il plugin `linter-flow` per Atom riconosceva degli errori nella cartella `node_modules/gulp-flowtype`, che contiene annotazioni per Flow del tipo `// @flow`. 107 | 108 | Adesso hai un codice irrobustito da lint, typecheck, e test, ottimo lavoro! 109 | 110 | Torna alla [sezione precedente](/tutorial/11-testing-mocha-chai-sinon) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 111 | -------------------------------------------------------------------------------- /tutorial/12-flow/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/12-flow/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import flow from 'gulp-flowtype'; 7 | import mocha from 'gulp-mocha'; 8 | import del from 'del'; 9 | import webpack from 'webpack-stream'; 10 | import webpackConfig from './webpack.config.babel'; 11 | 12 | const paths = { 13 | allSrcJs: 'src/**/*.js?(x)', 14 | serverSrcJs: 'src/server/**/*.js?(x)', 15 | sharedSrcJs: 'src/shared/**/*.js?(x)', 16 | allLibTests: 'lib/test/**/*.js', 17 | clientEntryPoint: 'src/client/app.jsx', 18 | clientBundle: 'dist/client-bundle.js?(.map)', 19 | gulpFile: 'gulpfile.babel.js', 20 | webpackFile: 'webpack.config.babel.js', 21 | libDir: 'lib', 22 | distDir: 'dist', 23 | }; 24 | 25 | gulp.task('lint', () => 26 | gulp.src([ 27 | paths.allSrcJs, 28 | paths.gulpFile, 29 | paths.webpackFile, 30 | ]) 31 | .pipe(eslint()) 32 | .pipe(eslint.format()) 33 | .pipe(eslint.failAfterError()) 34 | .pipe(flow({ abort: true })) 35 | ); 36 | 37 | gulp.task('clean', () => del([ 38 | paths.libDir, 39 | paths.clientBundle, 40 | ])); 41 | 42 | gulp.task('build', ['lint', 'clean'], () => 43 | gulp.src(paths.allSrcJs) 44 | .pipe(babel()) 45 | .pipe(gulp.dest(paths.libDir)) 46 | ); 47 | 48 | gulp.task('test', ['build'], () => 49 | gulp.src(paths.allLibTests) 50 | .pipe(mocha()) 51 | ); 52 | 53 | gulp.task('main', ['test'], () => 54 | gulp.src(paths.clientEntryPoint) 55 | .pipe(webpack(webpackConfig)) 56 | .pipe(gulp.dest(paths.distDir)) 57 | ); 58 | 59 | gulp.task('watch', () => { 60 | gulp.watch(paths.allSrcJs, ['main']); 61 | }); 62 | 63 | gulp.task('default', ['watch', 'main']); 64 | -------------------------------------------------------------------------------- /tutorial/12-flow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "gulp test", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": [ 12 | "airbnb", 13 | "plugin:flowtype/recommended" 14 | ], 15 | "parser": "babel-eslint", 16 | "plugins": [ 17 | "import", 18 | "flowtype" 19 | ], 20 | "env": { 21 | "browser": true 22 | }, 23 | "rules": { 24 | "new-cap": [ 25 | 2, 26 | { 27 | "capIsNewExceptions": [ 28 | "Map", 29 | "List" 30 | ] 31 | } 32 | ] 33 | } 34 | }, 35 | "babel": { 36 | "presets": [ 37 | "latest", 38 | "react", 39 | "flow" 40 | ], 41 | "compact": false 42 | }, 43 | "dependencies": { 44 | "babel-polyfill": "^6.16.0", 45 | "immutable": "^3.8.1", 46 | "react": "^15.3.2", 47 | "react-dom": "^15.3.2", 48 | "react-redux": "^4.4.5", 49 | "redux": "^3.6.0", 50 | "redux-actions": "^0.12.0", 51 | "redux-immutable": "^3.0.8" 52 | }, 53 | "devDependencies": { 54 | "babel-eslint": "^7.0.0", 55 | "babel-loader": "^6.2.5", 56 | "babel-preset-flow": "^1.0.0", 57 | "babel-preset-latest": "^6.16.0", 58 | "babel-preset-react": "^6.16.0", 59 | "chai": "^3.5.0", 60 | "del": "^2.2.2", 61 | "eslint": "^3.8.1", 62 | "eslint-config-airbnb": "^12.0.0", 63 | "eslint-plugin-flowtype": "^2.21.0", 64 | "eslint-plugin-import": "^2.0.1", 65 | "eslint-plugin-jsx-a11y": "^2.2.3", 66 | "eslint-plugin-react": "^6.4.1", 67 | "gulp": "^3.9.1", 68 | "gulp-babel": "^6.1.2", 69 | "gulp-eslint": "^3.0.1", 70 | "gulp-flowtype": "^1.0.0", 71 | "gulp-mocha": "^3.0.1", 72 | "mocha": "^3.1.2", 73 | "sinon": "^1.17.6", 74 | "sinon-chai": "^2.8.0", 75 | "webpack-stream": "^3.2.0" 76 | }, 77 | "repository": "verekia/js-stack-from-scratch", 78 | "author": "Jonathan Verrecchia - @verekia", 79 | "license": "MIT" 80 | } 81 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/actions/dog-actions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createAction } from 'redux-actions'; 4 | 5 | export const MAKE_BARK = 'MAKE_BARK'; 6 | export const makeBark = createAction(MAKE_BARK, () => true); 7 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import 'babel-polyfill'; 4 | 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import { createStore } from 'redux'; 8 | import { Provider } from 'react-redux'; 9 | import { combineReducers } from 'redux-immutable'; 10 | import dogReducer from './reducers/dog-reducer'; 11 | import BarkMessage from './containers/bark-message'; 12 | import BarkButton from './containers/bark-button'; 13 | 14 | const store = createStore(combineReducers({ 15 | dog: dogReducer, 16 | })); 17 | 18 | ReactDOM.render( 19 | 20 |
21 | 22 | 23 |
24 |
25 | , document.querySelector('.app') 26 | ); 27 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/components/button.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { PropTypes } from 'react'; 4 | 5 | const Button = ({ action, actionLabel }: { action: Function, actionLabel: string}) => 6 | ; 7 | 8 | Button.propTypes = { 9 | action: PropTypes.func.isRequired, 10 | actionLabel: PropTypes.string.isRequired, 11 | }; 12 | 13 | export default Button; 14 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/components/message.jsx: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import React, { PropTypes } from 'react'; 4 | 5 | const Message = ({ message }: { message: string }) =>
{message}
; 6 | 7 | Message.propTypes = { 8 | message: PropTypes.string.isRequired, 9 | }; 10 | 11 | export default Message; 12 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/containers/bark-button.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { connect } from 'react-redux'; 4 | import Button from '../components/button'; 5 | import { makeBark } from '../actions/dog-actions'; 6 | 7 | const mapDispatchToProps = dispatch => ({ 8 | action: () => { dispatch(makeBark()); }, 9 | actionLabel: 'Bark', 10 | }); 11 | 12 | export default connect(null, mapDispatchToProps)(Button); 13 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/containers/bark-message.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { connect } from 'react-redux'; 4 | import Message from '../components/message'; 5 | 6 | const mapStateToProps = state => ({ 7 | message: state.getIn(['dog', 'hasBarked']) ? 'The dog barked' : 'The dog did not bark', 8 | }); 9 | 10 | export default connect(mapStateToProps)(Message); 11 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/client/reducers/dog-reducer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as Immutable from 'immutable'; 4 | import { MAKE_BARK } from '../actions/dog-actions'; 5 | 6 | const initialState = Immutable.Map({ 7 | hasBarked: false, 8 | }); 9 | 10 | const dogReducer = (state: Object = initialState, action: Object) => { 11 | switch (action.type) { 12 | case MAKE_BARK: 13 | return state.set('hasBarked', action.payload); 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | export default dogReducer; 20 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/server/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* eslint-disable no-console */ 4 | 5 | import Dog from '../shared/dog'; 6 | 7 | const toby = new Dog('Toby'); 8 | 9 | console.log(toby.bark()); 10 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/shared/dog.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | class Dog { 4 | name: string; 5 | 6 | constructor(name: string) { 7 | this.name = name; 8 | } 9 | 10 | bark(): string { 11 | return `Wah wah, I am ${this.name}`; 12 | } 13 | 14 | barkInConsole() { 15 | /* eslint-disable no-console */ 16 | console.log(this.bark()); 17 | /* eslint-enable no-console */ 18 | } 19 | 20 | } 21 | 22 | export default Dog; 23 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/test/client/state-test.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* eslint-disable import/no-extraneous-dependencies, no-unused-expressions */ 4 | 5 | import { createStore } from 'redux'; 6 | import { combineReducers } from 'redux-immutable'; 7 | import { should } from 'chai'; 8 | import { describe, it, beforeEach } from 'mocha'; 9 | import dogReducer from '../../client/reducers/dog-reducer'; 10 | import { makeBark } from '../../client/actions/dog-actions'; 11 | 12 | should(); 13 | let store; 14 | 15 | describe('App State', () => { 16 | describe('Dog', () => { 17 | beforeEach(() => { 18 | store = createStore(combineReducers({ 19 | dog: dogReducer, 20 | })); 21 | }); 22 | describe('makeBark', () => { 23 | it('should make hasBarked go from false to true', () => { 24 | store.getState().getIn(['dog', 'hasBarked']).should.be.false; 25 | store.dispatch(makeBark()); 26 | store.getState().getIn(['dog', 'hasBarked']).should.be.true; 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tutorial/12-flow/src/test/shared/dog-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, no-console */ 2 | 3 | import chai from 'chai'; 4 | import { stub } from 'sinon'; 5 | import sinonChai from 'sinon-chai'; 6 | import { describe, it } from 'mocha'; 7 | import Dog from '../../shared/dog'; 8 | 9 | chai.should(); 10 | chai.use(sinonChai); 11 | 12 | describe('Shared', () => { 13 | describe('Dog', () => { 14 | describe('barkInConsole', () => { 15 | it('should print a bark string with its name', () => { 16 | stub(console, 'log'); 17 | new Dog('Test Toby').barkInConsole(); 18 | console.log.should.have.been.calledWith('Wah wah, I am Test Toby'); 19 | console.log.restore(); 20 | }); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tutorial/12-flow/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | output: { 3 | filename: 'client-bundle.js', 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.jsx?$/, 10 | loader: 'babel-loader', 11 | exclude: [/node_modules/], 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.jsx'], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tutorial/2-packages/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | -------------------------------------------------------------------------------- /tutorial/2-packages/README.md: -------------------------------------------------------------------------------- 1 | # 2 - Installare ed usare un pacchetto 2 | 3 | In questa sezione imparerai come installare ed utilizzare un pacchetto. Un "pacchetto" è semplicemente del codice che qualcuno ha scritto e messo a disposizione, e che tu puoi sfruttare all'interno delle tue applicazioni. In questo esempio utilizzeremo un pacchetto per la manipolazione dei colori. 4 | 5 | - Installa il pacchetto chiamato `color` utilizzando il comando `yarn add color`. 6 | 7 | Apri `package.json` per vedere come Yarn ha automaticamente aggiunto `color` nella sezione `dependencies`, che contiene le "dipendenze" esterne della tua applicazione. 8 | 9 | La cartella `node_modules` è stata creata per salvare i pacchetti installati. 10 | 11 | - Aggiungi `node_modules/` al tuo file `.gitignore` (ed esegui `git init` per creare un nuovo repository git, se non l'hai ancora fatto). 12 | 13 | Ti accorgerai inoltre che Yarn ha creato anche un file `yarn.lock`. Dovresti assicurarti che questo file venga aggiunto al tuo repository git, in questo modo tutti i collaboratori del tuo progetto utilizzeranno la stessa versione del pacchetto. Se stai continuando ad utilizzare NPM invece di Yarn, l'equivalente di questo file è lo *shrinkwrap*. 14 | 15 | - Aggiungi `const Color = require('color');` nel file `index.js` 16 | - Puoi utilizzare il pacchetto in questo modo: `const redHexa = Color({r: 255, g: 0, b: 0}).hexString();` 17 | - Aggiungi `console.log(redHexa)`. 18 | - Se esegui `yarn start` dovresti ottenere `#FF0000`. 19 | 20 | Complimenti, hai installato ed utilizzato un pacchetto esterno! 21 | 22 | Abbiamo utilizzato il pacchetto `color` come esempio di installazione di un semplice pacchetto. Nelle sezioni successive non lo utilizzeremo più, puoi quindi disinstallarlo così: 23 | 24 | - Esegui `yarn remove color` 25 | 26 | **Nota**: Ci sono due tipi differenti di dipendenze, `"dependencies"` e `"devDependencies"`. `"dependencies"` è più generale di `"devDependencies"`, che contiene pacchetti utilizzati unicamente durante la fase di sviluppo, non in produzione (tipicamente, pacchetti per creare le build, linters, etc). Per le `"devDependencies"`, utilizzeremo il comando `yarn add --dev [package]`. 27 | 28 | Prossima sezione: [3 - Configurazione di ES6 con Babel e Gulp](/tutorial/3-es6-babel-gulp) 29 | 30 | Torna alla [sezione precedente](/tutorial/1-node-npm-yarn-package-json) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 31 | -------------------------------------------------------------------------------- /tutorial/2-packages/index.js: -------------------------------------------------------------------------------- 1 | const Color = require('color'); 2 | 3 | const redHexa = Color({r: 255, g: 0, b: 0}).hexString(); 4 | 5 | console.log(redHexa); 6 | -------------------------------------------------------------------------------- /tutorial/2-packages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "node .", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "yarn start" 9 | }, 10 | "dependencies": { 11 | "color": "^0.11.3" 12 | }, 13 | "repository": "verekia/js-stack-from-scratch", 14 | "author": "Jonathan Verrecchia - @verekia", 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /tutorial/2-packages/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | clone@^1.0.2: 4 | version "1.0.2" 5 | resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" 6 | 7 | color-convert@^1.3.0: 8 | version "1.5.0" 9 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.5.0.tgz#7a2b4efb4488df85bca6443cb038b7100fbe7de1" 10 | 11 | color-name@^1.0.0: 12 | version "1.1.1" 13 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" 14 | 15 | color-string@^0.3.0: 16 | version "0.3.0" 17 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" 18 | dependencies: 19 | color-name "^1.0.0" 20 | 21 | color@^0.11.3: 22 | version "0.11.3" 23 | resolved "https://registry.yarnpkg.com/color/-/color-0.11.3.tgz#4bad1d0d52499dd00dbd6f0868442467e49394e6" 24 | dependencies: 25 | clone "^1.0.2" 26 | color-convert "^1.3.0" 27 | color-string "^0.3.0" 28 | 29 | -------------------------------------------------------------------------------- /tutorial/3-es6-babel-gulp/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | -------------------------------------------------------------------------------- /tutorial/3-es6-babel-gulp/README.md: -------------------------------------------------------------------------------- 1 | # 3 - Configurazione di ES6 con Babel e Gulp 2 | 3 | Adesso utilizzeremo la nuova sintassi di ES6, che rappresenta un notevole miglioramento rispetto alla "vecchia" sintassi ES5. Tutti i browser sono in grado di comprendere la sintassi ES5, ma per il momento non supportano ancora ES6. Utilizzeremo un tool chiamato Babel per trasformere il codice ES6 in codice compatibile con ES5. Per eseguire Babel, utilizzeremo Gulp, un task runner. È simile ai comandi presenti nella sezione `scripts` del `package.json`, ma scrivere i tuoi task in un file JS è più facile e pulito rispetto e scriverli in un file JSON, installeremo quindi Gulp, ed il suo plugin per Babel: 4 | 5 | - Esegui `yarn add --dev gulp` 6 | - Esegui `yarn add --dev gulp-babel` 7 | - Esegui `yarn add --dev babel-preset-latest` 8 | - Esegui `yarn add --dev del` (per il task `clean` come vedrai fra poco) 9 | - Nel `package.json`, aggiungi un campo `babel` per la configurazione di Babel. Fagli utilizzare l'ultima versione di Babel inserendo: 10 | 11 | ```json 12 | "babel": { 13 | "presets": [ 14 | "latest" 15 | ] 16 | }, 17 | ``` 18 | 19 | **Nota**: Un file `.babelrc` nella cartella principale del tuo progetto può essere utilizzato al posto del campo `babel` nel `package.json`. La cartella principale del tuo progetto diventerà sempre più piena con l'avanzare dello sviluppo, cerca quindi di utilizzare `package.json` per mantenere la configurazione di Babel. 20 | 21 | - Sposta il file `index.js` in una nuova cartella `src`. Là dentro è dove scriverai tutto il tuo codice ES6. La cartella `lib` conterrà il codice ricompilato in ES5. Gulp e Babel si occuperanno di creare il tutto. Rimuovi tutti il codice precedente relativo a `color`-related nel file `index.js`, e sostituiscilo con: 22 | 23 | ```javascript 24 | const str = 'ES6'; 25 | console.log(`Hello ${str}`); 26 | ``` 27 | 28 | Stiamo utilizzando un *modello* (template) di stringa, si tratta di una nuova funzionalità di ES6 che permette di inserire variabili direttamente all'interno di una stringa, senza concatenazioni, utilizzando `${}`. Fai attenzione che le stringhe template vengono create utilizzando gli **apici inversi**. 29 | 30 | - Crea un file `gulpfile.js` contenente: 31 | 32 | ```javascript 33 | const gulp = require('gulp'); 34 | const babel = require('gulp-babel'); 35 | const del = require('del'); 36 | const exec = require('child_process').exec; 37 | 38 | const paths = { 39 | allSrcJs: 'src/**/*.js', 40 | libDir: 'lib', 41 | }; 42 | 43 | gulp.task('clean', () => { 44 | return del(paths.libDir); 45 | }); 46 | 47 | gulp.task('build', ['clean'], () => { 48 | return gulp.src(paths.allSrcJs) 49 | .pipe(babel()) 50 | .pipe(gulp.dest(paths.libDir)); 51 | }); 52 | 53 | gulp.task('main', ['build'], (callback) => { 54 | exec(`node ${paths.libDir}`, (error, stdout) => { 55 | console.log(stdout); 56 | return callback(error); 57 | }); 58 | }); 59 | 60 | gulp.task('watch', () => { 61 | gulp.watch(paths.allSrcJs, ['main']); 62 | }); 63 | 64 | gulp.task('default', ['watch', 'main']); 65 | 66 | ``` 67 | 68 | Cerchiamo di capire il contenuto di questo file. 69 | 70 | Le API di Gulp sono abbastanza lineari. Definiscono dei `gulp.task`, che possono fare riferimento a dei file sorgente `gulp.src`, applicandogli una catena di modificatori tramite `.pipe()` (ad esempio nel nostro caso `babel()`) e inserisce il file di outputi in `gulp.dest`. Può inoltre utilizzare `gulp.watch` per controllare i cambiamenti nei file. I task i Gulppossono eseguire degli altri task preparatori, utilizzando un array in questo formato (es: `['build']`) come secondo parametro di `gulp.task`. Fai riferimento alla [documentazione](https://github.com/gulpjs/gulp) per una spiegazione più articolata. 71 | 72 | Prima definiamo un oggetto `paths` per memorizzare tutti i percorsi dei file e mantenere una configurazione pulita. 73 | 74 | Definiamo poi 5 task: `build`, `clean`, `main`, `watch`, e `default`. 75 | 76 | - `build` è dove viene chiamato Babel per trasformare tutti i nostri file sorgente nella cartella `src` e scrivere quelli trasformati in `lib`. 77 | - `clean` cancella tutti i file autogenerati in `lib`, prima di eseguire un task `build`. Questo è utile per ripulire tutti i file compilati che non sono più necessari, ad esempio perchè gli originali in `src` sono stati cancellati o rinominati , o per assicurarci che la cartella `lib` sia sempre sincronizzata con `src` nel caso in cui la build fallisca. Utilizziamo il pacchetto `del` per cancellare i file in un modo che si integra bene con il flusso di processamento di Gulp (questo è il metodo [raccomandato](https://github.com/gulpjs/gulp/blob/master/docs/recipes/delete-files-folder.md) per cancellare file con Gulp). 78 | - `main` è l'equivalente di `node .` che abbiamo utilizzato nel capitolo precedente, con l'eccezione che questa volta vogliamo eseguirlo sul file `lib/index.js`. Siccome `index.js` è il file che Node cerca di default, possiamo semplicemente scrivere `node lib` (utilizziamo la variabile `libDir` per mantenere la configurazione pulita). `require('child_process').exec` e `exec` sono delle funzioni native di Node per l'esecuzione di comandi su shell. Redirigiamo `stdout` su `console.log()` ritorniamo eventuali errori tramite la callback di `gulp.task`. Non preoccuparti se non capisci nei dettagli quello che sta succedendo, ricordati che è semplicemente l'equivalente di eseguire `node lib`. 79 | - `watch` esegue il task `main` quando vengono riscontrate delle modifiche nei file elencati. 80 | - `default` è un task specifico che viene eseguito se esegui semplicemente `gulp` da terminale. Nel nostro caso vogliamo fargli eseguire sia `watch` che `main` (per la prima esecuzione). 81 | 82 | **Nota**: Potresti chiederti perchè stiamo utilizzando direttamente del codice ES6 nel file Gulp, senza convertirlo in ES5 con Babel. È perchè stiamo utilizzando una versione di Node che supporta nativamente ES6 (assicurati di utilizzare Node > 6.5.0 con il comando `node -v`). 83 | 84 | OK! Vediamo se funziona tutto. 85 | 86 | - Nel `package.json`, cambia lo script `start` con: `"start": "gulp"`. 87 | - Esegui `yarn start`. Dovrebbe scrivere "Hello ES6" ed iniziare a controllare le modifiche sui file. Prova ad inserire del codice non funzionante in `src/index.js` per verificare che Gulp ti mostra automaticamente l'errore appena salvi il file. 88 | 89 | - Aggiungi `/lib/` al tuo `.gitignore` 90 | 91 | Prossima sezione: [4 - Utilizzare la sintassi ES6 con una classe](/tutorial/4-es6-syntax-class) 92 | 93 | Torna alla [sezione precedente](/tutorial/2-packages) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 94 | -------------------------------------------------------------------------------- /tutorial/3-es6-babel-gulp/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const babel = require('gulp-babel'); 3 | const del = require('del'); 4 | const exec = require('child_process').exec; 5 | 6 | const paths = { 7 | allSrcJs: 'src/**/*.js', 8 | libDir: 'lib', 9 | }; 10 | 11 | gulp.task('clean', () => { 12 | return del(paths.libDir); 13 | }); 14 | 15 | gulp.task('build', ['clean'], () => { 16 | return gulp.src(paths.allSrcJs) 17 | .pipe(babel()) 18 | .pipe(gulp.dest(paths.libDir)); 19 | }); 20 | 21 | gulp.task('main', ['build'], (callback) => { 22 | exec(`node ${paths.libDir}`, (error, stdout) => { 23 | console.log(stdout); 24 | return callback(error); 25 | }); 26 | }); 27 | 28 | gulp.task('watch', () => { 29 | gulp.watch(paths.allSrcJs, ['main']); 30 | }); 31 | 32 | gulp.task('default', ['watch', 'main']); 33 | -------------------------------------------------------------------------------- /tutorial/3-es6-babel-gulp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "babel": { 11 | "presets": [ 12 | "latest" 13 | ] 14 | }, 15 | "devDependencies": { 16 | "babel-preset-latest": "^6.16.0", 17 | "del": "^2.2.2", 18 | "gulp": "^3.9.1", 19 | "gulp-babel": "^6.1.2" 20 | }, 21 | "repository": "verekia/js-stack-from-scratch", 22 | "author": "Jonathan Verrecchia - @verekia", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /tutorial/3-es6-babel-gulp/src/index.js: -------------------------------------------------------------------------------- 1 | const str = 'ES6 now'; 2 | console.log(`Hello ${str}`); 3 | -------------------------------------------------------------------------------- /tutorial/4-es6-syntax-class/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | -------------------------------------------------------------------------------- /tutorial/4-es6-syntax-class/README.md: -------------------------------------------------------------------------------- 1 | # 4 - Utilizzare la sintassi ES6 con una classe 2 | 3 | - Crea un nuovo file, `src/dog.js`, contenente la seguente classe ES6: 4 | 5 | ```javascript 6 | class Dog { 7 | constructor(name) { 8 | this.name = name; 9 | } 10 | 11 | bark() { 12 | return `Wah wah, I am ${this.name}`; 13 | } 14 | } 15 | 16 | module.exports = Dog; 17 | ``` 18 | 19 | Non dovrebbe sorprenderti se hai già fatto della programmazione ad oggetti in un qualsiasi linguaggio. È comunque abbastanza recente nell'ecosistema Javascript. La classe viene esposta al mondo esterno tramite l'assegnazione a `module.exports`. 20 | 21 | Tipicamente il codice ES6 utilizza le classi, `const` e `let`, "modelli di stringhe" (utilizzando gli apici inversi) ad esempio `bark()`, e le arrow function (`(param) => { console.log('Hi'); }`), anche se non ne utilizziamo nessuna in questo semplice esempio. 22 | 23 | In `src/index.js`, scrivi questo: 24 | 25 | ```javascript 26 | const Dog = require('./dog'); 27 | 28 | const toby = new Dog('Toby'); 29 | 30 | console.log(toby.bark()); 31 | ``` 32 | 33 | Come puoi vedere, a differenza del pacchetto `color` che abbiamo utilizzato in precedenza, quando utilizziamo un modulo scritto da noi utilizziamo il prefisso `./` nella funzione `require()`. 34 | 35 | - Esegui `yarn start` e dovresti ottenere 'Wah wah, I am Toby'. 36 | 37 | - Dai un'occhiata al codice generato in `lib` per vedere com'è il codice generato (`var` al posto di `const` ad esempio). 38 | 39 | Prossima sezione: [5 - La sintassi ES6 per i moduli](/tutorial/5-es6-modules-syntax) 40 | 41 | Torna alla [sezione precedente](/tutorial/3-es6-babel-gulp) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 42 | -------------------------------------------------------------------------------- /tutorial/4-es6-syntax-class/gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const babel = require('gulp-babel'); 3 | const del = require('del'); 4 | const exec = require('child_process').exec; 5 | 6 | const paths = { 7 | allSrcJs: 'src/**/*.js', 8 | libDir: 'lib', 9 | }; 10 | 11 | gulp.task('clean', () => { 12 | return del(paths.libDir); 13 | }); 14 | 15 | gulp.task('build', ['clean'], () => { 16 | return gulp.src(paths.allSrcJs) 17 | .pipe(babel()) 18 | .pipe(gulp.dest(paths.libDir)); 19 | }); 20 | 21 | gulp.task('main', ['build'], (callback) => { 22 | exec(`node ${paths.libDir}`, (error, stdout) => { 23 | console.log(stdout); 24 | return callback(error); 25 | }); 26 | }); 27 | 28 | gulp.task('watch', () => { 29 | gulp.watch(paths.allSrcJs, ['main']); 30 | }); 31 | 32 | gulp.task('default', ['watch', 'main']); 33 | -------------------------------------------------------------------------------- /tutorial/4-es6-syntax-class/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "babel": { 11 | "presets": [ 12 | "latest" 13 | ] 14 | }, 15 | "devDependencies": { 16 | "babel-preset-latest": "^6.16.0", 17 | "del": "^2.2.2", 18 | "gulp": "^3.9.1", 19 | "gulp-babel": "^6.1.2" 20 | }, 21 | "repository": "verekia/js-stack-from-scratch", 22 | "author": "Jonathan Verrecchia - @verekia", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /tutorial/4-es6-syntax-class/src/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | module.exports = Dog; 13 | -------------------------------------------------------------------------------- /tutorial/4-es6-syntax-class/src/index.js: -------------------------------------------------------------------------------- 1 | const Dog = require('./dog'); 2 | 3 | const toby = new Dog('Toby'); 4 | 5 | console.log(toby.bark()); 6 | -------------------------------------------------------------------------------- /tutorial/5-es6-modules-syntax/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | -------------------------------------------------------------------------------- /tutorial/5-es6-modules-syntax/README.md: -------------------------------------------------------------------------------- 1 | # 5 - La sintassi ES6 per i moduli 2 | 3 | Sostituiamo semplicemente `const Dog = require('./dog')` con `import Dog from './dog'`, che è la nuova sintassi ES6 per i modili (a deifferenza della sintassi "CommonJS"). 4 | 5 | In `dog.js`, sostituiamo inoltre `module.exports = Dog` con `export default Dog`. 6 | 7 | Nota che in `dog.js`, il nome `Dog` è usato unicamente nell'`export`. Sarebbe quindi possibile esportare direttamente una funzione anonima utilizzando la seguente sintassi: 8 | 9 | ```javascript 10 | export default class { 11 | constructor(name) { 12 | this.name = name; 13 | } 14 | 15 | bark() { 16 | return `Wah wah, I am ${this.name}`; 17 | } 18 | } 19 | ``` 20 | 21 | Puoi quindi immaginare che il nome 'Dog' usato nell'`import` in `index.js` è a tua discrezione. Funzionerebbe ugualmente: 22 | 23 | ```javascript 24 | import Cat from './dog'; 25 | 26 | const toby = new Cat('Toby'); 27 | ``` 28 | 29 | Ovviamente la maggiorparte delle volte utilizzerai lo stesso nome utilizzato dalle classe/modulo che stai importando. 30 | Un esempio in cui non lo facciamo è `const babel = require('gulp-babel')` nel nostro file Gulp. 31 | 32 | Allora cosa sono questi `require()` nel nostro `gulpfile.js`? Possiamo sostituirli con `import`? L'ultima versione di Node supporta la maggiorparte delle feature di ES6, ma non ancora i moduli. Fortunatamente per noi Gulp può utilizzare Babel per aiutarci. Se rinominiamo `gulpfile.js` in `gulpfile.babel.js`, Babel si occuperà di passare i moduli in `import`a Gulp. 33 | 34 | - Rinomina `gulpfile.js` come `gulpfile.babel.js` 35 | 36 | - Sostituisci i `require()` con: 37 | 38 | ```javascript 39 | import gulp from 'gulp'; 40 | import babel from 'gulp-babel'; 41 | import del from 'del'; 42 | import { exec } from 'child_process'; 43 | ``` 44 | 45 | Nota il modo in cui abbiamo estatto la funzione `exec` direttamente da `child_process`. Abbastanza elegante! 46 | 47 | - `yarn start` dovrebbe continuare a scrivere "Wah wah, I am Toby". 48 | 49 | Prossima sezione: [6 - ESLint](/tutorial/6-eslint) 50 | 51 | Torna alla [sezione precedente](/tutorial/4-es6-syntax-class) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 52 | -------------------------------------------------------------------------------- /tutorial/5-es6-modules-syntax/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import babel from 'gulp-babel'; 3 | import del from 'del'; 4 | import { exec } from 'child_process'; 5 | 6 | const paths = { 7 | allSrcJs: 'src/**/*.js', 8 | libDir: 'lib', 9 | }; 10 | 11 | gulp.task('clean', () => { 12 | return del(paths.libDir); 13 | }); 14 | 15 | gulp.task('build', ['clean'], () => { 16 | return gulp.src(paths.allSrcJs) 17 | .pipe(babel()) 18 | .pipe(gulp.dest(paths.libDir)); 19 | }); 20 | 21 | gulp.task('main', ['build'], (callback) => { 22 | exec(`node ${paths.libDir}`, (error, stdout) => { 23 | console.log(stdout); 24 | return callback(error); 25 | }); 26 | }); 27 | 28 | gulp.task('watch', () => { 29 | gulp.watch(paths.allSrcJs, ['main']); 30 | }); 31 | 32 | gulp.task('default', ['watch', 'main']); 33 | -------------------------------------------------------------------------------- /tutorial/5-es6-modules-syntax/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "babel": { 11 | "presets": [ 12 | "latest" 13 | ] 14 | }, 15 | "devDependencies": { 16 | "babel-preset-latest": "^6.16.0", 17 | "del": "^2.2.2", 18 | "gulp": "^3.9.1", 19 | "gulp-babel": "^6.1.2" 20 | }, 21 | "repository": "verekia/js-stack-from-scratch", 22 | "author": "Jonathan Verrecchia - @verekia", 23 | "license": "MIT" 24 | } 25 | -------------------------------------------------------------------------------- /tutorial/5-es6-modules-syntax/src/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | export default Dog; 13 | -------------------------------------------------------------------------------- /tutorial/5-es6-modules-syntax/src/index.js: -------------------------------------------------------------------------------- 1 | import Dog from './dog'; 2 | 3 | const toby = new Dog('Toby'); 4 | 5 | console.log(toby.bark()); 6 | -------------------------------------------------------------------------------- /tutorial/6-eslint/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | -------------------------------------------------------------------------------- /tutorial/6-eslint/README.md: -------------------------------------------------------------------------------- 1 | # 6 - ESLint 2 | 3 | Utilizzeremo un linter per controllare il nostro codice contro potenziali problemi. ESLint è il linter di riferimento per il codice ES6. Invece di configurare da soli li regole per il nostro codice, utilizzeremo la configurazione creata da Airbnb. Questa configurazione utilizza alcuni plugin esterni, dobbiamo quindi installarli perchè tutto funzioni correttamente. 4 | 5 | - Esegui `yarn add --dev eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react` 6 | 7 | Come puoi vedere, è possibile installare vari pacchetti contemporaneamente con un unico comando. Tutti verranno aggiunti al file `package.json`, come di consueto. 8 | 9 | Nel `package.json`, aggiungi un campo `eslintConfig`: 10 | 11 | ```json 12 | "eslintConfig": { 13 | "extends": "airbnb", 14 | "plugins": [ 15 | "import" 16 | ] 17 | }, 18 | ``` 19 | 20 | La sezione `plugins` serve a comunicare a ESLint che vogliamo utilizzare la sintassi ES6 per gli import. 21 | 22 | **Nota**: Un file `.eslintrc.js`, `.eslintrc.json`, oppure `. eslintrc.yaml` inserito nella cartella base del progetto può essere utilizzato in alternativa alla sezione `eslintConfig` nel `package.json`. Come per la configurazione di Babel stiamo cercando di mantenere pulita la cartella del progetto, tuttavia se hai una configurazione di ESLint complessa puoi considerare la creazione di questo file come alternativa. 23 | 24 | Creiamo adesso un task Gulp che eseguirà ESLint per noi. Installiamo il plugin ESLint per Gulp: 25 | 26 | - Esegui `yarn add --dev gulp-eslint` 27 | 28 | Aggiungi questo task nel `gulpfile.babel.js`: 29 | 30 | ```javascript 31 | import eslint from 'gulp-eslint'; 32 | 33 | const paths = { 34 | allSrcJs: 'src/**/*.js', 35 | gulpFile: 'gulpfile.babel.js', 36 | libDir: 'lib', 37 | }; 38 | 39 | // [...] 40 | 41 | gulp.task('lint', () => { 42 | return gulp.src([ 43 | paths.allSrcJs, 44 | paths.gulpFile, 45 | ]) 46 | .pipe(eslint()) 47 | .pipe(eslint.format()) 48 | .pipe(eslint.failAfterError()); 49 | }); 50 | ``` 51 | 52 | Stiamo dicendo a Gulp che per questo task vogliamo includere `gulpfile.babel.js` e i file JS contenuti in `src`. 53 | 54 | Modifica il task `build` inserendo `lint` come prerequisito, in questo modo: 55 | 56 | ```javascript 57 | gulp.task('build', ['lint', 'clean'], () => { 58 | // ... 59 | }); 60 | ``` 61 | 62 | - Esegui `yarn start`, e dovresti vedere numerosi errori di linting in questo Gulpfile, e un warning per aver utilizzato `console.log()` in `index.js`. 63 | 64 | Un problema che vedrai è `'gulp' should be listed in the project's dependencies, not devDependencies (import/no-extraneous-dependencies)`. In questo caso si tratta di un falso problema. ESLint non è in graado di riconoscere quali file JS fanno parte della nostra build, e quali no, dovremo aiutarlo un po' utilizzando degli appositi commenti all'interno del codice. Nel `gulpfile.babel.js`, all'inizio del file aggiungi: 65 | 66 | ```javascript 67 | /* eslint-disable import/no-extraneous-dependencies */ 68 | ``` 69 | 70 | In questo modo ESLint non applicherà la regola `import/no-extraneous-dependencies` per questo file. 71 | 72 | Adesso ci rimane la segnalazione di `Unexpected block statement surrounding arrow body (arrow-body-style)`. Questa è molto utile. ESLint ci sta dicendo che esiste un modo migliore per scrivere: 73 | 74 | ```javascript 75 | () => { 76 | return 1; 77 | } 78 | ``` 79 | 80 | Dovrebbe essere sostituito con: 81 | 82 | ```javascript 83 | () => 1 84 | ``` 85 | 86 | Perchè quando una funzione contiene unicamente un return, in ES6 puoi omettere le parentesi, la parole "return" ed il punto e virgola. 87 | 88 | Aggiorniamo quindi il file Gulp: 89 | 90 | ```javascript 91 | gulp.task('lint', () => 92 | gulp.src([ 93 | paths.allSrcJs, 94 | paths.gulpFile, 95 | ]) 96 | .pipe(eslint()) 97 | .pipe(eslint.format()) 98 | .pipe(eslint.failAfterError()) 99 | ); 100 | 101 | gulp.task('clean', () => del(paths.libDir)); 102 | 103 | gulp.task('build', ['lint', 'clean'], () => 104 | gulp.src(paths.allSrcJs) 105 | .pipe(babel()) 106 | .pipe(gulp.dest(paths.libDir)) 107 | ); 108 | ``` 109 | 110 | L'ultima segnalazione rimasta si riferisce all'uso di `console.log()`. Diciamo che vogliamo veramente utilizzare la `console.log()` in `index.js` senza ricevere un warning. Come puoi immaginare, inseriremo `/* eslint-disable no-console */` all'inizio del nostro `index.js`. 111 | 112 | - Esegui `yarn start` e saremo nuovamente a posto. 113 | 114 | **Nota**: Questa sezione ti permette di configurare ESLint nella console. È utile poter eseguire il lint durante la build / prima di eseguire il push del codice, ma probabilmente vorrei integrarlo anche nel tuo IDE. NON utilizzare il linter nativo del tuo IDE quando usi ES6. Configuralo in modo che il linter da eseguire sia quello contenuto all'interno della cartella `node_modules`. In questo modo può utilizzare tutte le configurazioni del tuo progetto, il preset di Airbnb, etc. In caso contrario utilizzesti un linter generico. 115 | 116 | Prossima sezione: [7 - App Client con Webpack](/tutorial/7-client-webpack) 117 | 118 | Torna alla [sezione precedente](/tutorial/5-es6-modules-syntax) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 119 | -------------------------------------------------------------------------------- /tutorial/6-eslint/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, no-console */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import del from 'del'; 7 | import { exec } from 'child_process'; 8 | 9 | const paths = { 10 | allSrcJs: 'src/**/*.js', 11 | gulpFile: 'gulpfile.babel.js', 12 | libDir: 'lib', 13 | }; 14 | 15 | gulp.task('lint', () => 16 | gulp.src([ 17 | paths.allSrcJs, 18 | paths.gulpFile, 19 | ]) 20 | .pipe(eslint()) 21 | .pipe(eslint.format()) 22 | .pipe(eslint.failAfterError()) 23 | ); 24 | 25 | gulp.task('clean', () => del(paths.libDir)); 26 | 27 | gulp.task('build', ['lint', 'clean'], () => 28 | gulp.src(paths.allSrcJs) 29 | .pipe(babel()) 30 | .pipe(gulp.dest(paths.libDir)) 31 | ); 32 | 33 | gulp.task('main', ['build'], (callback) => { 34 | exec(`node ${paths.libDir}`, (error, stdout) => { 35 | console.log(stdout); 36 | return callback(error); 37 | }); 38 | }); 39 | 40 | gulp.task('watch', () => { 41 | gulp.watch(paths.allSrcJs, ['main']); 42 | }); 43 | 44 | gulp.task('default', ['watch', 'main']); 45 | -------------------------------------------------------------------------------- /tutorial/6-eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ] 15 | }, 16 | "babel": { 17 | "presets": [ 18 | "latest" 19 | ] 20 | }, 21 | "devDependencies": { 22 | "babel-preset-latest": "^6.16.0", 23 | "del": "^2.2.2", 24 | "eslint": "^3.8.1", 25 | "eslint-config-airbnb": "^12.0.0", 26 | "eslint-plugin-import": "^2.0.1", 27 | "eslint-plugin-jsx-a11y": "^2.2.3", 28 | "eslint-plugin-react": "^6.4.1", 29 | "gulp": "^3.9.1", 30 | "gulp-babel": "^6.1.2", 31 | "gulp-eslint": "^3.0.1" 32 | }, 33 | "repository": "verekia/js-stack-from-scratch", 34 | "author": "Jonathan Verrecchia - @verekia", 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /tutorial/6-eslint/src/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | export default Dog; 13 | -------------------------------------------------------------------------------- /tutorial/6-eslint/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Dog from './dog'; 4 | 5 | const toby = new Dog('Toby'); 6 | 7 | console.log(toby.bark()); 8 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/README.md: -------------------------------------------------------------------------------- 1 | # 7 - App Client con Webpack 2 | 3 | ## Struttura della nostra app 4 | 5 | - Crea una cartella `dist` nella cartella principale del progetto, e aggiungi il seguente `index.html` al suo interno: 6 | 7 | ```html 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | ``` 18 | 19 | Nella cartella `src`, crea le seguenti cartelle: `server`, `shared`, `client`, e sposta il file `index.js` che stai utilizzando all'interno di `server`, e `dog.js` dentro `shared`. Crea `app.js` in `client`. 20 | 21 | Per il momento non creeremo nessun backend in Node, ma questa suddivisione ti aiuterà a capire meglio come verranno struttirati i vari moduli. Dovrai sostituire `import Dog from './dog';` in `server/index.js` con `import Dog from '../shared/dog';` altrimenti ESLint si accorgerà che alcuni moduli non sono presenti. 22 | 23 | Scrivi questo codice in `client/app.js`: 24 | 25 | ```javascript 26 | import Dog from '../shared/dog'; 27 | 28 | const browserToby = new Dog('Browser Toby'); 29 | 30 | document.querySelector('.app').innerText = browserToby.bark(); 31 | ``` 32 | 33 | Aggiungi all'interno di `package.json`, in `eslintConfig`: 34 | 35 | ```json 36 | "env": { 37 | "browser": true 38 | } 39 | ``` 40 | 41 | In questo modo potremo utilizzare variabili come `window` o `document`, che sono sempre accessibili nel browser, senza che ESLint si lamenti. 42 | 43 | Se vuoi utilizzare alcune delle funzionalità più recenti di ES nel tuo client, come le `Promise`, dovrei includere i [Polyfill di Babel](https://babeljs.io/docs/usage/polyfill/) nel tuo codice. 44 | 45 | - Esegui `yarn add babel-polyfill` 46 | 47 | E in `app.js`, prima di qualunque altra cosa, aggiungi: 48 | 49 | ```javascript 50 | import 'babel-polyfill'; 51 | ``` 52 | 53 | Includere i polyfill aumenterà la dimensione finale tuo client, quindi aggiungili solo se devi utilizzare le funzionalità che ricopre. Per fornire un codice robusto in questa guida, io li includo, appariranno in alcuni esempi nei prossimi capitoli. 54 | 55 | ## Webpack 56 | 57 | In ambiente Node, puoi liberamente fare `import` di differenti file e Node si occuperà di trovare questi file all'interno del tuo filesystem. In un browser, non c'è filesystem, quindi i tuoi `import` non puntano a nessun file. Per fare in modo che `app.js` riesca ad avere accesso a tutti moduli di cui necessita, andremo ad impacchettare tutte le dipendenze all'interno di un unico file. Webpack è uno strumento che ci permette di fare questo. 58 | 59 | Webpack utilizza un file di configurazione, come Gulp, chiamato `webpack.config.js`. È possibile utilizzare import ed export ES6 al suo interno sfruttando Babel, esattamente come abbiamo fatto per Gulp, chiamando quindi il file `webpack.config.babel.js`. 60 | 61 | - Crea un file `webpack.config.babel.js` vuoto 62 | 63 | - Mentre ci sei, aggiungi `webpack.config.babel.js` al task `lint` di Gulp assieme ad alcune costanti di `paths`: 64 | 65 | ```javascript 66 | const paths = { 67 | allSrcJs: 'src/**/*.js', 68 | gulpFile: 'gulpfile.babel.js', 69 | webpackFile: 'webpack.config.babel.js', 70 | libDir: 'lib', 71 | distDir: 'dist', 72 | }; 73 | 74 | // [...] 75 | 76 | gulp.task('lint', () => 77 | gulp.src([ 78 | paths.allSrcJs, 79 | paths.gulpFile, 80 | paths.webpackFile, 81 | ]) 82 | .pipe(eslint()) 83 | .pipe(eslint.format()) 84 | .pipe(eslint.failAfterError()) 85 | ); 86 | ``` 87 | 88 | Dobbiamo spiegare a Webpack come processare file ES6 tramite Babel (come abbiamo fatto per Gulp e `gulp-babel`). In Webpack, quando devi processare dei file che non sono i classici JavaScript standard, dovrai utilizzare dei *loader*. Installiamo il loader di Babel per Webpack: 89 | 90 | - Esegui `yarn add --dev babel-loader` 91 | 92 | - Aggiungi quanto segue in `webpack.config.babel.js`: 93 | 94 | ```javascript 95 | export default { 96 | output: { 97 | filename: 'client-bundle.js', 98 | }, 99 | devtool: 'source-map', 100 | module: { 101 | loaders: [ 102 | { 103 | test: /\.jsx?$/, 104 | loader: 'babel-loader', 105 | exclude: [/node_modules/], 106 | }, 107 | ], 108 | }, 109 | resolve: { 110 | extensions: ['', '.js', '.jsx'], 111 | }, 112 | }; 113 | ``` 114 | 115 | Analizziamo: 116 | 117 | Questo file deve fare `export` di cose in modo che Webpack possa accedervi. `output.filename` è il nome del file che vogliamo generare. `devtool: 'source-map'` attiverà le source map per permettere un debug più agevole nel browser. In `module.loaders`, abbiamo un `test`, che è l'espressione regolare (regex) JavaScript regex che verrà utilizzata per definire quali file verranno processati tramite `babel-loader`. Siccome utilizzeremo sia file di tipo `.js` che `.jsx` (per React) nei prossimi capitoli, applichiamo questa regex: `/\.jsx?$/`. La cartella `node_modules` viene esclusa perchè non dovremo fare nessuna manipolazione ai moduli presenti la dentro. In questo modo, quando il tuo codice fa degli `import` di pacchetti presenti in `node_modules`, Babel non si preoccuperà di processare tutti i file, in che riducerà i tempi di build. La sezione `resolve` serve a spiegare a Webpack quale tipi di file vogliamo poter `import`are nel nostro codice utilizzando dei percorsi semplici senza parte finale, ad esempio: `import Foo from './foo'` dove `foo` potrebbe essere `foo.js` oppure `foo.jsx`. 118 | 119 | Okay adesso abbiamo configurato Webpack ma ci serve ancora una maniera per *eseguirlo*. 120 | 121 | ## Integrare Webpack in Gulp 122 | 123 | Webpack può svolgere molti compiti. Può anche rimpiazzare completamente Gulp se il tuo è un progetto principalmente client-side. Gulp, essendo un tool più generico, è migliore per svolgere compiti quali linting, test, e task di back-end. È inoltre più intuitivo e semplice da capire per chi sta iniziando rispetto alla configurazione di Webpack. Abbiamo già creato una solida configurazione di Gulp, integrare Webpack sarà quindi un gioco da ragazzi. 124 | 125 | Creiamo il task di Gulp per eseguire Webpack. Apri `gulpfile.babel.js`. 126 | 127 | Non abbiamo più bisogno che il task `main` esegua `node lib/` perchè apriremo `index.html` per eseguire la nostra app. 128 | 129 | - Rimuovi `import { exec } from 'child_process'`. 130 | 131 | In modo simile ai plugin di Gulp, il modulo `webpack-stream` ci permette di integrare molto semplicemente Webpack all'interno di Gulp. 132 | 133 | - Installa il modulo con: `yarn add --dev webpack-stream` 134 | 135 | - Aggiungi i seguenti `import`: 136 | 137 | ```javascript 138 | import webpack from 'webpack-stream'; 139 | import webpackConfig from './webpack.config.babel'; 140 | ``` 141 | 142 | La seconda linea prende semplicemente la configurazione di webpack. 143 | 144 | Come ho già detto, nei prossimi capitoli utilizzeremo dei file `.jsx` (lato client, e successivamente anche lato server), iniziamo a configurarli adesso in modo da partire preparati. 145 | 146 | - Cambia le costanti in questo modo: 147 | 148 | ```javascript 149 | const paths = { 150 | allSrcJs: 'src/**/*.js?(x)', 151 | serverSrcJs: 'src/server/**/*.js?(x)', 152 | sharedSrcJs: 'src/shared/**/*.js?(x)', 153 | clientEntryPoint: 'src/client/app.js', 154 | gulpFile: 'gulpfile.babel.js', 155 | webpackFile: 'webpack.config.babel.js', 156 | libDir: 'lib', 157 | distDir: 'dist', 158 | }; 159 | ``` 160 | 161 | Il `.js?(x)` è semplicemente un pattern per considerare sia i file `.js` che `.jsx`. 162 | 163 | Adesso abbiamo delle costanti per le varie sezioni dell'app ed un entrypoint da cui iniziare l'esecuzione. 164 | 165 | - Modifica il task `main` in questo modo: 166 | 167 | ```javascript 168 | gulp.task('main', ['lint', 'clean'], () => 169 | gulp.src(paths.clientEntryPoint) 170 | .pipe(webpack(webpackConfig)) 171 | .pipe(gulp.dest(paths.distDir)) 172 | ); 173 | ``` 174 | 175 | **Nota**: Il nostro task `build` attualmente transpila il codice ES6 a ES5 per ogni file `.js` contenuti in `src`. Adesso che abbiamo suddiviso il nostro codice in `server`, `shared`, e `client`, possiamo fare in modo che questo task compili unicamente `server` e `shared` (visto che Webpacksi occupa di `client`). Tuttavia, nel capitolo dedicato al Testing, avremo bisogno di far compilare a Gulp anche la parte `client` in modo da testarlo al di fuori di Webpack. Quindi, fino a quando non raggiungerai quel capitolo, alcune fasi di build saranno duplicate. Sonon sicuro che possiamo essere tutti d'accordo che non ci sono problemi. Effettivamente non utilizzeremo più il task `build` e la cartella `lib` fino a reggiungere quel capitolo, perchè per il momento ci occuperemo unicamente di impacchettare il codice client. 176 | 177 | - Esegui `yarn start`, dovresti vedere Webpack che costruisce il file `client-bundle.js`. Aprendo `index.html`nel browser dovresti vederey "Wah wah, I am Browser Toby". 178 | 179 | Un'ultima cosa: a differenza della cartella `lib` i file `dist/client-bundle.js` e `dist/client-bundle.js.map` non vengono cancellati dal nostro task `clean` prima di ogni build. 180 | 181 | - Aggiungi `clientBundle: 'dist/client-bundle.js?(.map)'` alla sezione `paths` e modifica il task `clean` in questo modo: 182 | 183 | ```javascript 184 | gulp.task('clean', () => del([ 185 | paths.libDir, 186 | paths.clientBundle, 187 | ])); 188 | ``` 189 | 190 | - Aggiungi `/dist/client-bundle.js*` al file `.gitignore`: 191 | 192 | Prossima sezione: [8 - React](/tutorial/8-react) 193 | 194 | Torna alla [sezione precedente](/tutorial/6-eslint) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 195 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import del from 'del'; 7 | import webpack from 'webpack-stream'; 8 | import webpackConfig from './webpack.config.babel'; 9 | 10 | const paths = { 11 | allSrcJs: 'src/**/*.js?(x)', 12 | serverSrcJs: 'src/server/**/*.js?(x)', 13 | sharedSrcJs: 'src/shared/**/*.js?(x)', 14 | clientEntryPoint: 'src/client/app.js', 15 | clientBundle: 'dist/client-bundle.js?(.map)', 16 | gulpFile: 'gulpfile.babel.js', 17 | webpackFile: 'webpack.config.babel.js', 18 | libDir: 'lib', 19 | distDir: 'dist', 20 | }; 21 | 22 | gulp.task('lint', () => 23 | gulp.src([ 24 | paths.allSrcJs, 25 | paths.gulpFile, 26 | paths.webpackFile, 27 | ]) 28 | .pipe(eslint()) 29 | .pipe(eslint.format()) 30 | .pipe(eslint.failAfterError()) 31 | ); 32 | 33 | gulp.task('clean', () => del([ 34 | paths.libDir, 35 | paths.clientBundle, 36 | ])); 37 | 38 | gulp.task('build', ['lint', 'clean'], () => 39 | gulp.src(paths.allSrcJs) 40 | .pipe(babel()) 41 | .pipe(gulp.dest(paths.libDir)) 42 | ); 43 | 44 | gulp.task('main', ['lint', 'clean'], () => 45 | gulp.src(paths.clientEntryPoint) 46 | .pipe(webpack(webpackConfig)) 47 | .pipe(gulp.dest(paths.distDir)) 48 | ); 49 | 50 | gulp.task('watch', () => { 51 | gulp.watch(paths.allSrcJs, ['main']); 52 | }); 53 | 54 | gulp.task('default', ['watch', 'main']); 55 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ], 15 | "env": { 16 | "browser": true 17 | } 18 | }, 19 | "babel": { 20 | "presets": [ 21 | "latest" 22 | ] 23 | }, 24 | "devDependencies": { 25 | "babel-loader": "^6.2.5", 26 | "babel-preset-latest": "^6.16.0", 27 | "del": "^2.2.2", 28 | "eslint": "^3.8.1", 29 | "eslint-config-airbnb": "^12.0.0", 30 | "eslint-plugin-import": "^2.0.1", 31 | "eslint-plugin-jsx-a11y": "^2.2.2", 32 | "eslint-plugin-react": "^6.4.1", 33 | "gulp": "^3.9.1", 34 | "gulp-babel": "^6.1.2", 35 | "gulp-eslint": "^3.0.1", 36 | "webpack-stream": "^3.2.0" 37 | }, 38 | "dependencies": { 39 | "babel-polyfill": "^6.16.0" 40 | }, 41 | "repository": "verekia/js-stack-from-scratch", 42 | "author": "Jonathan Verrecchia - @verekia", 43 | "license": "MIT" 44 | } 45 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/src/client/app.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import Dog from '../shared/dog'; 4 | 5 | const browserToby = new Dog('Browser Toby'); 6 | 7 | document.querySelector('.app').innerText = browserToby.bark(); 8 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/src/server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Dog from '../shared/dog'; 4 | 5 | const toby = new Dog('Toby'); 6 | 7 | console.log(toby.bark()); 8 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/src/shared/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | export default Dog; 13 | -------------------------------------------------------------------------------- /tutorial/7-client-webpack/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | output: { 3 | filename: 'client-bundle.js', 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.jsx?$/, 10 | loader: 'babel-loader', 11 | exclude: [/node_modules/], 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.jsx'], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tutorial/8-react/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/8-react/README.md: -------------------------------------------------------------------------------- 1 | # 8 - React 2 | 3 | Adesso utilizzeremo React per la visualizzazione della nostra app. 4 | 5 | Prima di tutto installiamo React e ReactDOM: 6 | 7 | - Esegui `yarn add react react-dom` 8 | 9 | Questi due pacchetti vanno nella sezione `"dependencies"` e non `"devDependencies"` perche a differenza dei tool utilizzati per lo sviluppo, il client in produzione ne avra bisogno. 10 | 11 | Rinominiamo il nostro file `src/client/app.js` in `src/client/app.jsx` e scriviamoci un po' di codice React e JSX: 12 | 13 | ```javascript 14 | import 'babel-polyfill'; 15 | 16 | import React, { PropTypes } from 'react'; 17 | import ReactDOM from 'react-dom'; 18 | import Dog from '../shared/dog'; 19 | 20 | const dogBark = new Dog('Browser Toby').bark(); 21 | 22 | const App = props => ( 23 |
24 | The dog says: {props.message} 25 |
26 | ); 27 | 28 | App.propTypes = { 29 | message: PropTypes.string.isRequired, 30 | }; 31 | 32 | ReactDOM.render(, document.querySelector('.app')); 33 | ``` 34 | 35 | **Nota**: Se non sei pratico di React o le PropTypes, studia prima il funzionamento di React e ritorna a seguire questo tutorial dopo. Nei capitoli successivi utilizzeremo parecchio React, assicurati quindi di riuscire a seguire quello che faremo. 36 | 37 | Nel Gulpfile, cambia il valore di `clientEntryPoint` modificandone l'estensione a `.jsx`: 38 | 39 | ```javascript 40 | clientEntryPoint: 'src/client/app.jsx', 41 | ``` 42 | 43 | Siccome utilizzeremo la sintassi JSX, dobbiamo dire a Babel che deve occuparsi anche di questa conversione. 44 | Installa il preset React Babel, che permettera a Babel di convertire la sintassi JSX: 45 | `yarn add --dev babel-preset-react` e cambia la voce `babel` nel `package.json` in questo modo: 46 | 47 | ```json 48 | "babel": { 49 | "presets": [ 50 | "latest", 51 | "react" 52 | ] 53 | }, 54 | ``` 55 | 56 | Adesso, dopo aver eseguito `yarn start`, se apriamo `index.html` dovremmo vedere la scritta "The dog says: Wah wah, I am Browser Toby" inserita da React. 57 | 58 | Prossima sezione: [9 - Redux](/tutorial/9-redux) 59 | 60 | Torna alla [sezione precedente](/tutorial/7-client-webpack) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 61 | -------------------------------------------------------------------------------- /tutorial/8-react/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/8-react/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import del from 'del'; 7 | import webpack from 'webpack-stream'; 8 | import webpackConfig from './webpack.config.babel'; 9 | 10 | const paths = { 11 | allSrcJs: 'src/**/*.js?(x)', 12 | serverSrcJs: 'src/server/**/*.js?(x)', 13 | sharedSrcJs: 'src/shared/**/*.js?(x)', 14 | clientEntryPoint: 'src/client/app.jsx', 15 | clientBundle: 'dist/client-bundle.js?(.map)', 16 | gulpFile: 'gulpfile.babel.js', 17 | webpackFile: 'webpack.config.babel.js', 18 | libDir: 'lib', 19 | distDir: 'dist', 20 | }; 21 | 22 | gulp.task('lint', () => 23 | gulp.src([ 24 | paths.allSrcJs, 25 | paths.gulpFile, 26 | paths.webpackFile, 27 | ]) 28 | .pipe(eslint()) 29 | .pipe(eslint.format()) 30 | .pipe(eslint.failAfterError()) 31 | ); 32 | 33 | gulp.task('clean', () => del([ 34 | paths.libDir, 35 | paths.clientBundle, 36 | ])); 37 | 38 | gulp.task('build', ['lint', 'clean'], () => 39 | gulp.src(paths.allSrcJs) 40 | .pipe(babel()) 41 | .pipe(gulp.dest(paths.libDir)) 42 | ); 43 | 44 | gulp.task('main', ['lint', 'clean'], () => 45 | gulp.src(paths.clientEntryPoint) 46 | .pipe(webpack(webpackConfig)) 47 | .pipe(gulp.dest(paths.distDir)) 48 | ); 49 | 50 | gulp.task('watch', () => { 51 | gulp.watch(paths.allSrcJs, ['main']); 52 | }); 53 | 54 | gulp.task('default', ['watch', 'main']); 55 | -------------------------------------------------------------------------------- /tutorial/8-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ], 15 | "env": { 16 | "browser": true 17 | } 18 | }, 19 | "babel": { 20 | "presets": [ 21 | "latest", 22 | "react" 23 | ] 24 | }, 25 | "dependencies": { 26 | "babel-polyfill": "^6.16.0", 27 | "react": "^15.3.2", 28 | "react-dom": "^15.3.2" 29 | }, 30 | "devDependencies": { 31 | "babel-loader": "^6.2.5", 32 | "babel-preset-latest": "^6.16.0", 33 | "babel-preset-react": "^6.16.0", 34 | "del": "^2.2.2", 35 | "eslint": "^3.8.1", 36 | "eslint-config-airbnb": "^12.0.0", 37 | "eslint-plugin-import": "^2.0.1", 38 | "eslint-plugin-jsx-a11y": "^2.2.3", 39 | "eslint-plugin-react": "^6.4.1", 40 | "gulp": "^3.9.1", 41 | "gulp-babel": "^6.1.2", 42 | "gulp-eslint": "^3.0.1", 43 | "webpack-stream": "^3.2.0" 44 | }, 45 | "repository": "verekia/js-stack-from-scratch", 46 | "author": "Jonathan Verrecchia - @verekia", 47 | "license": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /tutorial/8-react/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import React, { PropTypes } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import Dog from '../shared/dog'; 6 | 7 | const dogBark = new Dog('Browser Toby').bark(); 8 | 9 | const App = props => ( 10 |
11 | The dog says: {props.message} 12 |
13 | ); 14 | 15 | App.propTypes = { 16 | message: PropTypes.string.isRequired, 17 | }; 18 | 19 | ReactDOM.render(, document.querySelector('.app')); 20 | -------------------------------------------------------------------------------- /tutorial/8-react/src/server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Dog from '../shared/dog'; 4 | 5 | const toby = new Dog('Toby'); 6 | 7 | console.log(toby.bark()); 8 | -------------------------------------------------------------------------------- /tutorial/8-react/src/shared/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | export default Dog; 13 | -------------------------------------------------------------------------------- /tutorial/8-react/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | output: { 3 | filename: 'client-bundle.js', 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.jsx?$/, 10 | loader: 'babel-loader', 11 | exclude: [/node_modules/], 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.jsx'], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tutorial/9-redux/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | /lib/ 5 | /dist/client-bundle.js* 6 | -------------------------------------------------------------------------------- /tutorial/9-redux/README.md: -------------------------------------------------------------------------------- 1 | # 9 - Redux 2 | 3 | In questo capitolo (che é il piú difficile finora) aggiungeremo [Redux](http://redux.js.org/) alla nostra applicazione e lo collegheremo a React. Redux controlla lo stato della tua applicazione. É composto da uno **store** che e'un oggetto Javascript che rappresenta lo stato dell'app, **azioni** che sono tipicamente scatenate dagli utenti e **reducers** che possono essere visti come handler delle azioni. I reducer modificano lo stato dell'applicazione (lo *store*) e quando lo stato dell'applicazione viene modificato delle cose succedono nell'app. Una buona rappresentazione grafica di Redux é disponibile [qua](http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#/9). 4 | 5 | Per dimostrare come utilizzare Redux nel modo piú semplice possibile, la lostra app consiste in un messaggio ed un bottone. Il messaggio indica se il cane ha abbaiato o no (inizialmente lo stato é impostato su no) ed il pulsante fa abbaiare il cane, aggiornando di conseguenza il messaggio. 6 | 7 | Ci serviremo di due pacchetti: `redux` e `react-redux`. 8 | 9 | - Esegui `yarn add redux react-redux`. 10 | 11 | Iniziamo creando due cartelle: `src/client/actions` e `src/client/reducers`. 12 | 13 | - In `actions`, crea `dog-actions.js`: 14 | 15 | ```javascript 16 | export const MAKE_BARK = 'MAKE_BARK'; 17 | 18 | export const makeBark = () => ({ 19 | type: MAKE_BARK, 20 | payload: true, 21 | }); 22 | ``` 23 | 24 | Qua definiremo un tipo di azione, `MAKE_BARK`, e una funzione (anche conosciuta come *action creator*) che scatena un'azione `MAKE_BARK` chiamata `makeBark`. Entrambe sono esportate perché ci serviranno in altri file. Questa azione implementa una modello di [Azione Flux Standard](https://github.com/acdlite/flux-standard-action), ecco perché ha degli attributi `type` e `payload`. 25 | 26 | - In `reducers`, crea `dog-reducer.js`: 27 | 28 | ```javascript 29 | import { MAKE_BARK } from '../actions/dog-actions'; 30 | 31 | const initialState = { 32 | hasBarked: false, 33 | }; 34 | 35 | const dogReducer = (state = initialState, action) => { 36 | switch (action.type) { 37 | case MAKE_BARK: 38 | return { hasBarked: action.payload }; 39 | default: 40 | return state; 41 | } 42 | }; 43 | 44 | export default dogReducer; 45 | ``` 46 | 47 | Qua definiamo lo stato iniziale della nostra app, che é un oggetto contenente la proprietá `hasBarked` impostata a `false`, e `dogReducer`, che é la funzione responsabile di alterare lo stato in base all'azione che é stata eseguita. Lo stato non puó essere modificato in questa funzione, occorre effettuare il return di un nuovo oggetto di stato. 48 | 49 | - Modifichiamo `app.jsx` per creare lo *store*. Puoi sostituire il file precedente con questo: 50 | 51 | ```javascript 52 | import React from 'react'; 53 | import ReactDOM from 'react-dom'; 54 | import { createStore, combineReducers } from 'redux'; 55 | import { Provider } from 'react-redux'; 56 | import dogReducer from './reducers/dog-reducer'; 57 | import BarkMessage from './containers/bark-message'; 58 | import BarkButton from './containers/bark-button'; 59 | 60 | const store = createStore(combineReducers({ 61 | dog: dogReducer, 62 | })); 63 | 64 | ReactDOM.render( 65 | 66 |
67 | 68 | 69 |
70 |
71 | , document.querySelector('.app') 72 | ); 73 | ``` 74 | 75 | Il nostro store é creato tramite la funzione Redux `createStore`, é abbastanza esplicito. L'oggetto store é assemblato combinando tutti i reducer (nel nostro caso solo uno) utilizzando la funzione di Redux `combineReducers`. Qua viene assegnato un nome ad ogni reducer, e chiameremo il nostro `dog`. 76 | 77 | Questo é tutto per la parte di Redux puro. 78 | 79 | Adesso agganceremo Redux con React utilizzando `react-redux`. Per fare in modo che `react-redux` passi lo store alla nostra app React, dobbiamo incapsulare il tutto in un componente ``. Questo componente deve avere un unico figlio, quindi abbiamo creato un `
`, e questo `
` contiene i due elementi principali della nostra app: `BarkMessage` e `BarkButton`. 80 | 81 | Come puoi vedere dalla sezione `import`, `BarkMessage` e `BarkButton` sono importati da una cartella `containers`. Adesso é il momento buono per introdurre i concetti di **Components** e **Containers**. 82 | 83 | I *Components* sono componenti React *stupidi*, nel senso che non conoscono nulla dello stato di Redux. I *Containers* sono componenti *intelligenti* che sono a conoscenza dello stato e verranno utilizzati per *collegare* i nostri componenti stupidi. 84 | 85 | - Crea 2 cartelle, `src/client/components` e `src/client/containers`. 86 | 87 | - In `components`, crea questi file: 88 | 89 | **button.jsx** 90 | 91 | ```javascript 92 | import React, { PropTypes } from 'react'; 93 | 94 | const Button = ({ action, actionLabel }) => ; 95 | 96 | Button.propTypes = { 97 | action: PropTypes.func.isRequired, 98 | actionLabel: PropTypes.string.isRequired, 99 | }; 100 | 101 | export default Button; 102 | ``` 103 | 104 | e **message.jsx**: 105 | 106 | ```javascript 107 | import React, { PropTypes } from 'react'; 108 | 109 | const Message = ({ message }) =>
{message}
; 110 | 111 | Message.propTypes = { 112 | message: PropTypes.string.isRequired, 113 | }; 114 | 115 | export default Message; 116 | 117 | ``` 118 | 119 | Questi sono esempi di componenti *stupidi*. Non contengono una logica, e mostrano semplicemente quello che gli viene chiesto di mostrare tramite le **props** di React. La differenza principale tra `button.jsx` e `message.jsx` é che `Button` contiene un'**action** fra le sue props. Questa azione é collegata all'evento `onClick`. Nel contesto della nostra applicazione, la label del `Button` non cambierá mai, tuttavia, il componente `Message` rifletterá lo stato dell'app e varierá di conseguenza. 120 | 121 | Ripeto: i *components* non sanno nulla delle **actions** di Redux o dello **stato** dell'app, per questo motivo creeremo dei **containers** che forniranno le *azioni* e i *dati* a questi due componenti stupidi. 122 | 123 | - In `containers`, crea questi file: 124 | 125 | **bark-button.js** 126 | 127 | ```javascript 128 | import { connect } from 'react-redux'; 129 | import Button from '../components/button'; 130 | import { makeBark } from '../actions/dog-actions'; 131 | 132 | const mapDispatchToProps = dispatch => ({ 133 | action: () => { dispatch(makeBark()); }, 134 | actionLabel: 'Bark', 135 | }); 136 | 137 | export default connect(null, mapDispatchToProps)(Button); 138 | ``` 139 | 140 | e **bark-message.js**: 141 | 142 | ```javascript 143 | import { connect } from 'react-redux'; 144 | import Message from '../components/message'; 145 | 146 | const mapStateToProps = state => ({ 147 | message: state.dog.hasBarked ? 'The dog barked' : 'The dog did not bark', 148 | }); 149 | 150 | export default connect(mapStateToProps)(Message); 151 | ``` 152 | 153 | `BarkButton` collegherá il `Button` con l'azione `makeBark` ed il metodo `dispatch` di React, e `BarkMessage` collegherá lo stato dell'applicazione con `Message`. Quando lo stato cambia, `Message` verrá automaticamente ricaricato con la prop `message` corretta. Questi collegamenti sono fatti tramite la funzione `connect` di `react-redux`. 154 | 155 | - Puoi eseguire `yarn start` ed aprire `index.html`. Dovresti vedere "The dog did not bark" ed un bottone. Quando clicki sul bottone, il messaggio dovrebbe diventare "The dog barked". 156 | 157 | Prossima sezione: [10 - Immutable JS e Migliorie Redux](/tutorial/10-immutable-redux-improvements) 158 | 159 | Torna alla [sezione precedente](/tutorial/8-react) o all'[indice](https://github.com/fbertone/js-stack-from-scratch). 160 | -------------------------------------------------------------------------------- /tutorial/9-redux/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tutorial/9-redux/gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import eslint from 'gulp-eslint'; 6 | import del from 'del'; 7 | import webpack from 'webpack-stream'; 8 | import webpackConfig from './webpack.config.babel'; 9 | 10 | const paths = { 11 | allSrcJs: 'src/**/*.js?(x)', 12 | serverSrcJs: 'src/server/**/*.js?(x)', 13 | sharedSrcJs: 'src/shared/**/*.js?(x)', 14 | clientEntryPoint: 'src/client/app.jsx', 15 | clientBundle: 'dist/client-bundle.js?(.map)', 16 | gulpFile: 'gulpfile.babel.js', 17 | webpackFile: 'webpack.config.babel.js', 18 | libDir: 'lib', 19 | distDir: 'dist', 20 | }; 21 | 22 | gulp.task('lint', () => 23 | gulp.src([ 24 | paths.allSrcJs, 25 | paths.gulpFile, 26 | paths.webpackFile, 27 | ]) 28 | .pipe(eslint()) 29 | .pipe(eslint.format()) 30 | .pipe(eslint.failAfterError()) 31 | ); 32 | 33 | gulp.task('clean', () => del([ 34 | paths.libDir, 35 | paths.clientBundle, 36 | ])); 37 | 38 | gulp.task('build', ['lint', 'clean'], () => 39 | gulp.src(paths.allSrcJs) 40 | .pipe(babel()) 41 | .pipe(gulp.dest(paths.libDir)) 42 | ); 43 | 44 | gulp.task('main', ['lint', 'clean'], () => 45 | gulp.src(paths.clientEntryPoint) 46 | .pipe(webpack(webpackConfig)) 47 | .pipe(gulp.dest(paths.distDir)) 48 | ); 49 | 50 | gulp.task('watch', () => { 51 | gulp.watch(paths.allSrcJs, ['main']); 52 | }); 53 | 54 | gulp.task('default', ['watch', 'main']); 55 | -------------------------------------------------------------------------------- /tutorial/9-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-stack-from-scratch", 3 | "version": "1.0.0", 4 | "description": "JavaScript Stack from Scratch - Step-by-step tutorial to build a modern JavaScript stack", 5 | "scripts": { 6 | "start": "gulp", 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "tutorial-test": "gulp main" 9 | }, 10 | "eslintConfig": { 11 | "extends": "airbnb", 12 | "plugins": [ 13 | "import" 14 | ], 15 | "env": { 16 | "browser": true 17 | } 18 | }, 19 | "babel": { 20 | "presets": [ 21 | "latest", 22 | "react" 23 | ] 24 | }, 25 | "dependencies": { 26 | "babel-polyfill": "^6.16.0", 27 | "react": "^15.3.2", 28 | "react-dom": "^15.3.2", 29 | "react-redux": "^4.4.5", 30 | "redux": "^3.6.0" 31 | }, 32 | "devDependencies": { 33 | "babel-loader": "^6.2.5", 34 | "babel-preset-latest": "^6.16.0", 35 | "babel-preset-react": "^6.16.0", 36 | "del": "^2.2.2", 37 | "eslint": "^3.8.1", 38 | "eslint-config-airbnb": "^12.0.0", 39 | "eslint-plugin-import": "^2.0.1", 40 | "eslint-plugin-jsx-a11y": "^2.2.3", 41 | "eslint-plugin-react": "^6.4.1", 42 | "gulp": "^3.9.1", 43 | "gulp-babel": "^6.1.2", 44 | "gulp-eslint": "^3.0.1", 45 | "webpack-stream": "^3.2.0" 46 | }, 47 | "repository": "verekia/js-stack-from-scratch", 48 | "author": "Jonathan Verrecchia - @verekia", 49 | "license": "MIT" 50 | } 51 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/actions/dog-actions.js: -------------------------------------------------------------------------------- 1 | export const MAKE_BARK = 'MAKE_BARK'; 2 | 3 | export const makeBark = () => ({ 4 | type: MAKE_BARK, 5 | payload: true, 6 | }); 7 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/app.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { createStore, combineReducers } from 'redux'; 6 | import { Provider } from 'react-redux'; 7 | import dogReducer from './reducers/dog-reducer'; 8 | import BarkMessage from './containers/bark-message'; 9 | import BarkButton from './containers/bark-button'; 10 | 11 | const store = createStore(combineReducers({ 12 | dog: dogReducer, 13 | })); 14 | 15 | ReactDOM.render( 16 | 17 |
18 | 19 | 20 |
21 |
22 | , document.querySelector('.app') 23 | ); 24 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/components/button.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Button = ({ action, actionLabel }) => ; 4 | 5 | Button.propTypes = { 6 | action: PropTypes.func.isRequired, 7 | actionLabel: PropTypes.string.isRequired, 8 | }; 9 | 10 | export default Button; 11 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/components/message.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | const Message = ({ message }) =>
{message}
; 4 | 5 | Message.propTypes = { 6 | message: PropTypes.string.isRequired, 7 | }; 8 | 9 | export default Message; 10 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/containers/bark-button.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Button from '../components/button'; 3 | import { makeBark } from '../actions/dog-actions'; 4 | 5 | const mapDispatchToProps = dispatch => ({ 6 | action: () => { dispatch(makeBark()); }, 7 | actionLabel: 'Bark', 8 | }); 9 | 10 | export default connect(null, mapDispatchToProps)(Button); 11 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/containers/bark-message.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Message from '../components/message'; 3 | 4 | const mapStateToProps = state => ({ 5 | message: state.dog.hasBarked ? 'The dog barked' : 'The dog did not bark', 6 | }); 7 | 8 | export default connect(mapStateToProps)(Message); 9 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/client/reducers/dog-reducer.js: -------------------------------------------------------------------------------- 1 | import { MAKE_BARK } from '../actions/dog-actions'; 2 | 3 | const initialState = { 4 | hasBarked: false, 5 | }; 6 | 7 | const dogReducer = (state = initialState, action) => { 8 | switch (action.type) { 9 | case MAKE_BARK: 10 | return { hasBarked: action.payload }; 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export default dogReducer; 17 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Dog from '../shared/dog'; 4 | 5 | const toby = new Dog('Toby'); 6 | 7 | console.log(toby.bark()); 8 | -------------------------------------------------------------------------------- /tutorial/9-redux/src/shared/dog.js: -------------------------------------------------------------------------------- 1 | 2 | class Dog { 3 | constructor(name) { 4 | this.name = name; 5 | } 6 | 7 | bark() { 8 | return `Wah wah, I am ${this.name}`; 9 | } 10 | } 11 | 12 | export default Dog; 13 | -------------------------------------------------------------------------------- /tutorial/9-redux/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | output: { 3 | filename: 'client-bundle.js', 4 | }, 5 | devtool: 'source-map', 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.jsx?$/, 10 | loader: 'babel-loader', 11 | exclude: [/node_modules/], 12 | }, 13 | ], 14 | }, 15 | resolve: { 16 | extensions: ['', '.js', '.jsx'], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | abbrev@1: 4 | version "1.0.9" 5 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" 6 | 7 | acorn-jsx@^3.0.1: 8 | version "3.0.1" 9 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" 10 | dependencies: 11 | acorn "^3.0.4" 12 | 13 | acorn@^3.0.4: 14 | version "3.3.0" 15 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" 16 | 17 | ansi-regex@^2.0.0: 18 | version "2.0.0" 19 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107" 20 | 21 | ansi-styles@^2.2.1: 22 | version "2.2.1" 23 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 24 | 25 | aproba@^1.0.3: 26 | version "1.0.4" 27 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.0.4.tgz#2713680775e7614c8ba186c065d4e2e52d1072c0" 28 | 29 | are-we-there-yet@~1.1.2: 30 | version "1.1.2" 31 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3" 32 | dependencies: 33 | delegates "^1.0.0" 34 | readable-stream "^2.0.0 || ^1.1.13" 35 | 36 | argparse@^1.0.7: 37 | version "1.0.9" 38 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 39 | dependencies: 40 | sprintf-js "~1.0.2" 41 | 42 | array-find-index@^1.0.1: 43 | version "1.0.2" 44 | resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" 45 | 46 | array-index@^1.0.0: 47 | version "1.0.0" 48 | resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" 49 | dependencies: 50 | debug "^2.2.0" 51 | es6-symbol "^3.0.2" 52 | 53 | asn1@~0.2.3: 54 | version "0.2.3" 55 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" 56 | 57 | assert-plus@^0.2.0: 58 | version "0.2.0" 59 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 60 | 61 | assert-plus@^1.0.0: 62 | version "1.0.0" 63 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 64 | 65 | asynckit@^0.4.0: 66 | version "0.4.0" 67 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 68 | 69 | aws-sign2@~0.6.0: 70 | version "0.6.0" 71 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 72 | 73 | aws4@^1.2.1: 74 | version "1.5.0" 75 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" 76 | 77 | babel-runtime@^6.0.0: 78 | version "6.11.6" 79 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.11.6.tgz#6db707fef2d49c49bfa3cb64efdb436b518b8222" 80 | dependencies: 81 | core-js "^2.4.0" 82 | regenerator-runtime "^0.9.5" 83 | 84 | balanced-match@^0.4.1: 85 | version "0.4.2" 86 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 87 | 88 | bcrypt-pbkdf@^1.0.0: 89 | version "1.0.0" 90 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4" 91 | dependencies: 92 | tweetnacl "^0.14.3" 93 | 94 | bl@^1.0.0, bl@~1.1.2: 95 | version "1.1.2" 96 | resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398" 97 | dependencies: 98 | readable-stream "~2.0.5" 99 | 100 | block-stream@*: 101 | version "0.0.9" 102 | resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" 103 | dependencies: 104 | inherits "~2.0.0" 105 | 106 | boom@2.x.x: 107 | version "2.10.1" 108 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 109 | dependencies: 110 | hoek "2.x.x" 111 | 112 | brace-expansion@^1.0.0: 113 | version "1.1.6" 114 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 115 | dependencies: 116 | balanced-match "^0.4.1" 117 | concat-map "0.0.1" 118 | 119 | buffer-shims@^1.0.0: 120 | version "1.0.0" 121 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 122 | 123 | builtin-modules@^1.0.0: 124 | version "1.1.1" 125 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 126 | 127 | bytes@^2.4.0: 128 | version "2.4.0" 129 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 130 | 131 | camelcase@^3.0.0: 132 | version "3.0.0" 133 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" 134 | 135 | caseless@~0.11.0: 136 | version "0.11.0" 137 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 138 | 139 | chalk@^1.1.1: 140 | version "1.1.3" 141 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 142 | dependencies: 143 | ansi-styles "^2.2.1" 144 | escape-string-regexp "^1.0.2" 145 | has-ansi "^2.0.0" 146 | strip-ansi "^3.0.0" 147 | supports-color "^2.0.0" 148 | 149 | clone@^1.0.2: 150 | version "1.0.2" 151 | resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" 152 | 153 | cmd-shim@^2.0.1: 154 | version "2.0.2" 155 | resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" 156 | dependencies: 157 | graceful-fs "^4.1.2" 158 | mkdirp "~0.5.0" 159 | 160 | code-point-at@^1.0.0: 161 | version "1.0.1" 162 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.0.1.tgz#1104cd34f9b5b45d3eba88f1babc1924e1ce35fb" 163 | dependencies: 164 | number-is-nan "^1.0.0" 165 | 166 | combined-stream@^1.0.5, combined-stream@~1.0.5: 167 | version "1.0.5" 168 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 169 | dependencies: 170 | delayed-stream "~1.0.0" 171 | 172 | commander@^2.9.0: 173 | version "2.9.0" 174 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 175 | dependencies: 176 | graceful-readlink ">= 1.0.0" 177 | 178 | concat-map@0.0.1: 179 | version "0.0.1" 180 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 181 | 182 | console-control-strings@^1.0.0, console-control-strings@~1.1.0: 183 | version "1.1.0" 184 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 185 | 186 | core-js@^2.4.0: 187 | version "2.4.1" 188 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" 189 | 190 | core-util-is@~1.0.0: 191 | version "1.0.2" 192 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 193 | 194 | cryptiles@2.x.x: 195 | version "2.0.5" 196 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 197 | dependencies: 198 | boom "2.x.x" 199 | 200 | currently-unhandled@^0.4.1: 201 | version "0.4.1" 202 | resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" 203 | dependencies: 204 | array-find-index "^1.0.1" 205 | 206 | d@^0.1.1, d@~0.1.1: 207 | version "0.1.1" 208 | resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" 209 | dependencies: 210 | es5-ext "~0.10.2" 211 | 212 | dashdash@^1.12.0: 213 | version "1.14.0" 214 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.0.tgz#29e486c5418bf0f356034a993d51686a33e84141" 215 | dependencies: 216 | assert-plus "^1.0.0" 217 | 218 | death@^1.0.0: 219 | version "1.0.0" 220 | resolved "https://registry.yarnpkg.com/death/-/death-1.0.0.tgz#4d46e15488d4b636b699f0671b04632d752fd2de" 221 | 222 | debug@^2.2.0: 223 | version "2.2.0" 224 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 225 | dependencies: 226 | ms "0.7.1" 227 | 228 | defaults@^1.0.3: 229 | version "1.0.3" 230 | resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" 231 | dependencies: 232 | clone "^1.0.2" 233 | 234 | delayed-stream@~1.0.0: 235 | version "1.0.0" 236 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 237 | 238 | delegates@^1.0.0: 239 | version "1.0.0" 240 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 241 | 242 | detect-indent@^4.0.0: 243 | version "4.0.0" 244 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" 245 | dependencies: 246 | repeating "^2.0.0" 247 | 248 | diff@^2.2.1: 249 | version "2.2.3" 250 | resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" 251 | 252 | doctrine@^1.2.2: 253 | version "1.5.0" 254 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" 255 | dependencies: 256 | esutils "^2.0.2" 257 | isarray "^1.0.0" 258 | 259 | ecc-jsbn@~0.1.1: 260 | version "0.1.1" 261 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" 262 | dependencies: 263 | jsbn "~0.1.0" 264 | 265 | end-of-stream@^1.0.0: 266 | version "1.1.0" 267 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" 268 | dependencies: 269 | once "~1.3.0" 270 | 271 | entities@~1.1.1: 272 | version "1.1.1" 273 | resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" 274 | 275 | err-code@^1.0.0: 276 | version "1.1.1" 277 | resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.1.tgz#739d71b6851f24d050ea18c79a5b722420771d59" 278 | 279 | es5-ext@^0.10.7, es5-ext@~0.10.11, es5-ext@~0.10.2: 280 | version "0.10.12" 281 | resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" 282 | dependencies: 283 | es6-iterator "2" 284 | es6-symbol "~3.1" 285 | 286 | es6-iterator@2: 287 | version "2.0.0" 288 | resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac" 289 | dependencies: 290 | d "^0.1.1" 291 | es5-ext "^0.10.7" 292 | es6-symbol "3" 293 | 294 | es6-symbol@^3.0.2, es6-symbol@~3.1, es6-symbol@3: 295 | version "3.1.0" 296 | resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" 297 | dependencies: 298 | d "~0.1.1" 299 | es5-ext "~0.10.11" 300 | 301 | escape-string-regexp@^1.0.2: 302 | version "1.0.5" 303 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 304 | 305 | eslint-plugin-react@5.2.2: 306 | version "5.2.2" 307 | resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-5.2.2.tgz#7db068e1f5487f6871e4deef36a381c303eac161" 308 | dependencies: 309 | doctrine "^1.2.2" 310 | jsx-ast-utils "^1.2.1" 311 | 312 | esutils@^2.0.2: 313 | version "2.0.2" 314 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 315 | 316 | extend@^3.0.0, extend@~3.0.0: 317 | version "3.0.0" 318 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" 319 | 320 | extsprintf@1.0.2: 321 | version "1.0.2" 322 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" 323 | 324 | forever-agent@~0.6.1: 325 | version "0.6.1" 326 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 327 | 328 | form-data@~2.0.0: 329 | version "2.0.0" 330 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.0.0.tgz#6f0aebadcc5da16c13e1ecc11137d85f9b883b25" 331 | dependencies: 332 | asynckit "^0.4.0" 333 | combined-stream "^1.0.5" 334 | mime-types "^2.1.11" 335 | 336 | fs.realpath@^1.0.0: 337 | version "1.0.0" 338 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 339 | 340 | fstream@^1.0.0, fstream@^1.0.2: 341 | version "1.0.10" 342 | resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.10.tgz#604e8a92fe26ffd9f6fae30399d4984e1ab22822" 343 | dependencies: 344 | graceful-fs "^4.1.2" 345 | inherits "~2.0.0" 346 | mkdirp ">=0.5 0" 347 | rimraf "2" 348 | 349 | gauge@~2.6.0: 350 | version "2.6.0" 351 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.6.0.tgz#d35301ad18e96902b4751dcbbe40f4218b942a46" 352 | dependencies: 353 | aproba "^1.0.3" 354 | console-control-strings "^1.0.0" 355 | has-color "^0.1.7" 356 | has-unicode "^2.0.0" 357 | object-assign "^4.1.0" 358 | signal-exit "^3.0.0" 359 | string-width "^1.0.1" 360 | strip-ansi "^3.0.1" 361 | wide-align "^1.1.0" 362 | 363 | generate-function@^2.0.0: 364 | version "2.0.0" 365 | resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" 366 | 367 | generate-object-property@^1.1.0: 368 | version "1.2.0" 369 | resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" 370 | dependencies: 371 | is-property "^1.0.0" 372 | 373 | getpass@^0.1.1: 374 | version "0.1.6" 375 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" 376 | dependencies: 377 | assert-plus "^1.0.0" 378 | 379 | glob, glob@^7.0.3, glob@^7.0.5: 380 | version "7.1.1" 381 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 382 | dependencies: 383 | fs.realpath "^1.0.0" 384 | inflight "^1.0.4" 385 | inherits "2" 386 | minimatch "^3.0.2" 387 | once "^1.3.0" 388 | path-is-absolute "^1.0.0" 389 | 390 | graceful-fs@^4.1.2: 391 | version "4.1.9" 392 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.9.tgz#baacba37d19d11f9d146d3578bc99958c3787e29" 393 | 394 | "graceful-readlink@>= 1.0.0": 395 | version "1.0.1" 396 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 397 | 398 | har-validator@~2.0.6: 399 | version "2.0.6" 400 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" 401 | dependencies: 402 | chalk "^1.1.1" 403 | commander "^2.9.0" 404 | is-my-json-valid "^2.12.4" 405 | pinkie-promise "^2.0.0" 406 | 407 | has-ansi@^2.0.0: 408 | version "2.0.0" 409 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 410 | dependencies: 411 | ansi-regex "^2.0.0" 412 | 413 | has-color@^0.1.7: 414 | version "0.1.7" 415 | resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" 416 | 417 | has-unicode@^2.0.0: 418 | version "2.0.1" 419 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 420 | 421 | hawk@~3.1.3: 422 | version "3.1.3" 423 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 424 | dependencies: 425 | boom "2.x.x" 426 | cryptiles "2.x.x" 427 | hoek "2.x.x" 428 | sntp "1.x.x" 429 | 430 | hoek@2.x.x: 431 | version "2.16.3" 432 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 433 | 434 | http-signature@~1.1.0: 435 | version "1.1.1" 436 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 437 | dependencies: 438 | assert-plus "^0.2.0" 439 | jsprim "^1.2.2" 440 | sshpk "^1.7.0" 441 | 442 | inflight@^1.0.4: 443 | version "1.0.6" 444 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 445 | dependencies: 446 | once "^1.3.0" 447 | wrappy "1" 448 | 449 | inherits@~2.0.0, inherits@~2.0.1, inherits@2: 450 | version "2.0.3" 451 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 452 | 453 | ini@^1.3.4: 454 | version "1.3.4" 455 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" 456 | 457 | invariant@^2.2.0: 458 | version "2.2.1" 459 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.1.tgz#b097010547668c7e337028ebe816ebe36c8a8d54" 460 | dependencies: 461 | loose-envify "^1.0.0" 462 | 463 | is-builtin-module@^1.0.0: 464 | version "1.0.0" 465 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" 466 | dependencies: 467 | builtin-modules "^1.0.0" 468 | 469 | is-finite@^1.0.0: 470 | version "1.0.2" 471 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" 472 | dependencies: 473 | number-is-nan "^1.0.0" 474 | 475 | is-fullwidth-code-point@^1.0.0: 476 | version "1.0.0" 477 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 478 | dependencies: 479 | number-is-nan "^1.0.0" 480 | 481 | is-my-json-valid@^2.12.4: 482 | version "2.15.0" 483 | resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" 484 | dependencies: 485 | generate-function "^2.0.0" 486 | generate-object-property "^1.1.0" 487 | jsonpointer "^4.0.0" 488 | xtend "^4.0.0" 489 | 490 | is-property@^1.0.0: 491 | version "1.0.2" 492 | resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" 493 | 494 | is-typedarray@~1.0.0: 495 | version "1.0.0" 496 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 497 | 498 | is-utf8@^0.2.0: 499 | version "0.2.1" 500 | resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" 501 | 502 | isarray@^1.0.0, isarray@~1.0.0: 503 | version "1.0.0" 504 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 505 | 506 | isexe@^1.1.1: 507 | version "1.1.2" 508 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" 509 | 510 | isstream@~0.1.2: 511 | version "0.1.2" 512 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 513 | 514 | jodid25519@^1.0.0: 515 | version "1.0.2" 516 | resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" 517 | dependencies: 518 | jsbn "~0.1.0" 519 | 520 | js-tokens@^1.0.1: 521 | version "1.0.3" 522 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-1.0.3.tgz#14e56eb68c8f1a92c43d59f5014ec29dc20f2ae1" 523 | 524 | jsbn@~0.1.0: 525 | version "0.1.0" 526 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd" 527 | 528 | json-schema@0.2.3: 529 | version "0.2.3" 530 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 531 | 532 | json-stringify-safe@~5.0.1: 533 | version "5.0.1" 534 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 535 | 536 | jsonpointer@^4.0.0: 537 | version "4.0.0" 538 | resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.0.tgz#6661e161d2fc445f19f98430231343722e1fcbd5" 539 | 540 | jsprim@^1.2.2: 541 | version "1.3.1" 542 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.3.1.tgz#2a7256f70412a29ee3670aaca625994c4dcff252" 543 | dependencies: 544 | extsprintf "1.0.2" 545 | json-schema "0.2.3" 546 | verror "1.3.6" 547 | 548 | jsx-ast-utils@^1.2.1: 549 | version "1.3.2" 550 | resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.3.2.tgz#dff658782705352111f9865d40471bc4a955961e" 551 | dependencies: 552 | acorn-jsx "^3.0.1" 553 | object-assign "^4.1.0" 554 | 555 | leven@^2.0.0: 556 | version "2.0.0" 557 | resolved "https://registry.yarnpkg.com/leven/-/leven-2.0.0.tgz#74c45744439550da185801912829f61d22071bc1" 558 | 559 | linkify-it@^2.0.0: 560 | version "2.0.2" 561 | resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.2.tgz#994629a4adfa5a7d34e08c075611575ab9b6fcfc" 562 | dependencies: 563 | uc.micro "^1.0.1" 564 | 565 | loose-envify@^1.0.0: 566 | version "1.2.0" 567 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.2.0.tgz#69a65aad3de542cf4ee0f4fe74e8e33c709ccb0f" 568 | dependencies: 569 | js-tokens "^1.0.1" 570 | 571 | loud-rejection@^1.2.0: 572 | version "1.6.0" 573 | resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" 574 | dependencies: 575 | currently-unhandled "^0.4.1" 576 | signal-exit "^3.0.0" 577 | 578 | markdown-it@^8.0.1: 579 | version "8.0.1" 580 | resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.0.1.tgz#ff60e2103d17896cb6c57407baa9766f916495cb" 581 | dependencies: 582 | argparse "^1.0.7" 583 | entities "~1.1.1" 584 | linkify-it "^2.0.0" 585 | mdurl "^1.0.1" 586 | uc.micro "^1.0.3" 587 | 588 | markdownlint: 589 | version "0.3.0" 590 | resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.3.0.tgz#af428c09cf6a5dcc32fa561161d43981d6609bf2" 591 | dependencies: 592 | markdown-it "^8.0.1" 593 | 594 | mdurl@^1.0.1: 595 | version "1.0.1" 596 | resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" 597 | 598 | mime-db@~1.24.0: 599 | version "1.24.0" 600 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.24.0.tgz#e2d13f939f0016c6e4e9ad25a8652f126c467f0c" 601 | 602 | mime-types@^2.1.11, mime-types@~2.1.7: 603 | version "2.1.12" 604 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.12.tgz#152ba256777020dd4663f54c2e7bc26381e71729" 605 | dependencies: 606 | mime-db "~1.24.0" 607 | 608 | minimatch@^3.0.2, minimatch@^3.0.3: 609 | version "3.0.3" 610 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 611 | dependencies: 612 | brace-expansion "^1.0.0" 613 | 614 | minimist@0.0.8: 615 | version "0.0.8" 616 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 617 | 618 | mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.0: 619 | version "0.5.1" 620 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 621 | dependencies: 622 | minimist "0.0.8" 623 | 624 | ms@0.7.1: 625 | version "0.7.1" 626 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 627 | 628 | mute-stream@~0.0.4: 629 | version "0.0.6" 630 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" 631 | 632 | node-emoji@^1.0.4: 633 | version "1.4.1" 634 | resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.4.1.tgz#c9fa0cf91094335bcb967a6f42b2305c15af2ebc" 635 | dependencies: 636 | string.prototype.codepointat "^0.2.0" 637 | 638 | node-gyp@^3.2.1: 639 | version "3.4.0" 640 | resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.4.0.tgz#dda558393b3ecbbe24c9e6b8703c71194c63fa36" 641 | dependencies: 642 | fstream "^1.0.0" 643 | glob "^7.0.3" 644 | graceful-fs "^4.1.2" 645 | minimatch "^3.0.2" 646 | mkdirp "^0.5.0" 647 | nopt "2 || 3" 648 | npmlog "0 || 1 || 2 || 3" 649 | osenv "0" 650 | path-array "^1.0.0" 651 | request "2" 652 | rimraf "2" 653 | semver "2.x || 3.x || 4 || 5" 654 | tar "^2.0.0" 655 | which "1" 656 | 657 | node-uuid@~1.4.7: 658 | version "1.4.7" 659 | resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f" 660 | 661 | "nopt@2 || 3": 662 | version "3.0.6" 663 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 664 | dependencies: 665 | abbrev "1" 666 | 667 | "npmlog@0 || 1 || 2 || 3": 668 | version "3.1.2" 669 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-3.1.2.tgz#2d46fa874337af9498a2f12bb43d8d0be4a36873" 670 | dependencies: 671 | are-we-there-yet "~1.1.2" 672 | console-control-strings "~1.1.0" 673 | gauge "~2.6.0" 674 | set-blocking "~2.0.0" 675 | 676 | number-is-nan@^1.0.0: 677 | version "1.0.1" 678 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 679 | 680 | oauth-sign@~0.8.1: 681 | version "0.8.2" 682 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 683 | 684 | object-assign@^4.1.0: 685 | version "4.1.0" 686 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 687 | 688 | object-path@^0.11.2: 689 | version "0.11.2" 690 | resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.2.tgz#74bf3b3c5a7f2024d75e333f12021353fa9d485e" 691 | 692 | once@^1.3.0: 693 | version "1.4.0" 694 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 695 | dependencies: 696 | wrappy "1" 697 | 698 | once@~1.3.0: 699 | version "1.3.3" 700 | resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" 701 | dependencies: 702 | wrappy "1" 703 | 704 | os-homedir@^1.0.0: 705 | version "1.0.2" 706 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 707 | 708 | os-tmpdir@^1.0.0: 709 | version "1.0.2" 710 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 711 | 712 | osenv@0: 713 | version "0.1.3" 714 | resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.3.tgz#83cf05c6d6458fc4d5ac6362ea325d92f2754217" 715 | dependencies: 716 | os-homedir "^1.0.0" 717 | os-tmpdir "^1.0.0" 718 | 719 | path-array@^1.0.0: 720 | version "1.0.1" 721 | resolved "https://registry.yarnpkg.com/path-array/-/path-array-1.0.1.tgz#7e2f0f35f07a2015122b868b7eac0eb2c4fec271" 722 | dependencies: 723 | array-index "^1.0.0" 724 | 725 | path-is-absolute@^1.0.0: 726 | version "1.0.1" 727 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 728 | 729 | pinkie-promise@^2.0.0: 730 | version "2.0.1" 731 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 732 | dependencies: 733 | pinkie "^2.0.0" 734 | 735 | pinkie@^2.0.0: 736 | version "2.0.4" 737 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 738 | 739 | process-nextick-args@~1.0.6: 740 | version "1.0.7" 741 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 742 | 743 | proper-lockfile@^1.1.3: 744 | version "1.2.0" 745 | resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-1.2.0.tgz#ceff5dd89d3e5f10fb75e1e8e76bc75801a59c34" 746 | dependencies: 747 | err-code "^1.0.0" 748 | extend "^3.0.0" 749 | graceful-fs "^4.1.2" 750 | retry "^0.10.0" 751 | 752 | qs@~6.2.0: 753 | version "6.2.1" 754 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" 755 | 756 | read@^1.0.7: 757 | version "1.0.7" 758 | resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" 759 | dependencies: 760 | mute-stream "~0.0.4" 761 | 762 | readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13": 763 | version "2.1.5" 764 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" 765 | dependencies: 766 | buffer-shims "^1.0.0" 767 | core-util-is "~1.0.0" 768 | inherits "~2.0.1" 769 | isarray "~1.0.0" 770 | process-nextick-args "~1.0.6" 771 | string_decoder "~0.10.x" 772 | util-deprecate "~1.0.1" 773 | 774 | readable-stream@~2.0.5: 775 | version "2.0.6" 776 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" 777 | dependencies: 778 | core-util-is "~1.0.0" 779 | inherits "~2.0.1" 780 | isarray "~1.0.0" 781 | process-nextick-args "~1.0.6" 782 | string_decoder "~0.10.x" 783 | util-deprecate "~1.0.1" 784 | 785 | regenerator-runtime@^0.9.5: 786 | version "0.9.5" 787 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.5.tgz#403d6d40a4bdff9c330dd9392dcbb2d9a8bba1fc" 788 | 789 | repeating@^2.0.0: 790 | version "2.0.1" 791 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 792 | dependencies: 793 | is-finite "^1.0.0" 794 | 795 | request-capture-har@^1.1.4: 796 | version "1.1.4" 797 | resolved "https://registry.yarnpkg.com/request-capture-har/-/request-capture-har-1.1.4.tgz#e6ad76eb8e7a1714553fdbeef32cd4518e4e2013" 798 | 799 | request@^2.75.0, request@2: 800 | version "2.75.0" 801 | resolved "https://registry.yarnpkg.com/request/-/request-2.75.0.tgz#d2b8268a286da13eaa5d01adf5d18cc90f657d93" 802 | dependencies: 803 | aws-sign2 "~0.6.0" 804 | aws4 "^1.2.1" 805 | bl "~1.1.2" 806 | caseless "~0.11.0" 807 | combined-stream "~1.0.5" 808 | extend "~3.0.0" 809 | forever-agent "~0.6.1" 810 | form-data "~2.0.0" 811 | har-validator "~2.0.6" 812 | hawk "~3.1.3" 813 | http-signature "~1.1.0" 814 | is-typedarray "~1.0.0" 815 | isstream "~0.1.2" 816 | json-stringify-safe "~5.0.1" 817 | mime-types "~2.1.7" 818 | node-uuid "~1.4.7" 819 | oauth-sign "~0.8.1" 820 | qs "~6.2.0" 821 | stringstream "~0.0.4" 822 | tough-cookie "~2.3.0" 823 | tunnel-agent "~0.4.1" 824 | 825 | retry@^0.10.0: 826 | version "0.10.0" 827 | resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.0.tgz#649e15ca408422d98318161935e7f7d652d435dd" 828 | 829 | rimraf@^2.5.0, rimraf@2: 830 | version "2.5.4" 831 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" 832 | dependencies: 833 | glob "^7.0.5" 834 | 835 | roadrunner@^1.1.0: 836 | version "1.1.0" 837 | resolved "https://registry.yarnpkg.com/roadrunner/-/roadrunner-1.1.0.tgz#1180a30d64e1970d8f55dd8cb0da8ffccecad71e" 838 | 839 | semver@^5.1.0, "semver@2.x || 3.x || 4 || 5": 840 | version "5.3.0" 841 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 842 | 843 | set-blocking@~2.0.0: 844 | version "2.0.0" 845 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 846 | 847 | signal-exit@^3.0.0: 848 | version "3.0.1" 849 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.1.tgz#5a4c884992b63a7acd9badb7894c3ee9cfccad81" 850 | 851 | sntp@1.x.x: 852 | version "1.0.9" 853 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 854 | dependencies: 855 | hoek "2.x.x" 856 | 857 | spdx-correct@~1.0.0: 858 | version "1.0.2" 859 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" 860 | dependencies: 861 | spdx-license-ids "^1.0.2" 862 | 863 | spdx-expression-parse@~1.0.0: 864 | version "1.0.4" 865 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" 866 | 867 | spdx-license-ids@^1.0.2: 868 | version "1.2.2" 869 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" 870 | 871 | sprintf-js@~1.0.2: 872 | version "1.0.3" 873 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 874 | 875 | sshpk@^1.7.0: 876 | version "1.10.1" 877 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.1.tgz#30e1a5d329244974a1af61511339d595af6638b0" 878 | dependencies: 879 | asn1 "~0.2.3" 880 | assert-plus "^1.0.0" 881 | dashdash "^1.12.0" 882 | getpass "^0.1.1" 883 | optionalDependencies: 884 | bcrypt-pbkdf "^1.0.0" 885 | ecc-jsbn "~0.1.1" 886 | jodid25519 "^1.0.0" 887 | jsbn "~0.1.0" 888 | tweetnacl "~0.14.0" 889 | 890 | string_decoder@~0.10.x: 891 | version "0.10.31" 892 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 893 | 894 | string-width@^1.0.1: 895 | version "1.0.2" 896 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 897 | dependencies: 898 | code-point-at "^1.0.0" 899 | is-fullwidth-code-point "^1.0.0" 900 | strip-ansi "^3.0.0" 901 | 902 | string.prototype.codepointat@^0.2.0: 903 | version "0.2.0" 904 | resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78" 905 | 906 | stringstream@~0.0.4: 907 | version "0.0.5" 908 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" 909 | 910 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 911 | version "3.0.1" 912 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 913 | dependencies: 914 | ansi-regex "^2.0.0" 915 | 916 | strip-bom@^2.0.0: 917 | version "2.0.0" 918 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" 919 | dependencies: 920 | is-utf8 "^0.2.0" 921 | 922 | supports-color@^2.0.0: 923 | version "2.0.0" 924 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 925 | 926 | tar-stream@^1.5.2: 927 | version "1.5.2" 928 | resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.2.tgz#fbc6c6e83c1a19d4cb48c7d96171fc248effc7bf" 929 | dependencies: 930 | bl "^1.0.0" 931 | end-of-stream "^1.0.0" 932 | readable-stream "^2.0.0" 933 | xtend "^4.0.0" 934 | 935 | tar@^2.0.0, tar@^2.2.1: 936 | version "2.2.1" 937 | resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" 938 | dependencies: 939 | block-stream "*" 940 | fstream "^1.0.2" 941 | inherits "2" 942 | 943 | tough-cookie@~2.3.0: 944 | version "2.3.1" 945 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.1.tgz#99c77dfbb7d804249e8a299d4cb0fd81fef083fd" 946 | 947 | tunnel-agent@~0.4.1: 948 | version "0.4.3" 949 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" 950 | 951 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 952 | version "0.14.3" 953 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.3.tgz#3da382f670f25ded78d7b3d1792119bca0b7132d" 954 | 955 | uc.micro@^1.0.1, uc.micro@^1.0.3: 956 | version "1.0.3" 957 | resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" 958 | 959 | user-home@^2.0.0: 960 | version "2.0.0" 961 | resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" 962 | dependencies: 963 | os-homedir "^1.0.0" 964 | 965 | util-deprecate@~1.0.1: 966 | version "1.0.2" 967 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 968 | 969 | validate-npm-package-license@^3.0.1: 970 | version "3.0.1" 971 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" 972 | dependencies: 973 | spdx-correct "~1.0.0" 974 | spdx-expression-parse "~1.0.0" 975 | 976 | verror@1.3.6: 977 | version "1.3.6" 978 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" 979 | dependencies: 980 | extsprintf "1.0.2" 981 | 982 | which@1: 983 | version "1.2.11" 984 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.11.tgz#c8b2eeea6b8c1659fa7c1dd4fdaabe9533dc5e8b" 985 | dependencies: 986 | isexe "^1.1.1" 987 | 988 | wide-align@^1.1.0: 989 | version "1.1.0" 990 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad" 991 | dependencies: 992 | string-width "^1.0.1" 993 | 994 | wrappy@1: 995 | version "1.0.2" 996 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 997 | 998 | xtend@^4.0.0: 999 | version "4.0.1" 1000 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 1001 | 1002 | yarn@^0.16.1: 1003 | version "0.16.1" 1004 | resolved "https://registry.yarnpkg.com/yarn/-/yarn-0.16.1.tgz#956501387a9e8584aad481355c8612d876bc35d8" 1005 | dependencies: 1006 | babel-runtime "^6.0.0" 1007 | bytes "^2.4.0" 1008 | camelcase "^3.0.0" 1009 | chalk "^1.1.1" 1010 | cmd-shim "^2.0.1" 1011 | commander "^2.9.0" 1012 | death "^1.0.0" 1013 | debug "^2.2.0" 1014 | defaults "^1.0.3" 1015 | detect-indent "^4.0.0" 1016 | diff "^2.2.1" 1017 | eslint-plugin-react "5.2.2" 1018 | ini "^1.3.4" 1019 | invariant "^2.2.0" 1020 | is-builtin-module "^1.0.0" 1021 | leven "^2.0.0" 1022 | loud-rejection "^1.2.0" 1023 | minimatch "^3.0.3" 1024 | mkdirp "^0.5.1" 1025 | node-emoji "^1.0.4" 1026 | node-gyp "^3.2.1" 1027 | object-path "^0.11.2" 1028 | proper-lockfile "^1.1.3" 1029 | read "^1.0.7" 1030 | repeating "^2.0.0" 1031 | request "^2.75.0" 1032 | request-capture-har "^1.1.4" 1033 | rimraf "^2.5.0" 1034 | roadrunner "^1.1.0" 1035 | semver "^5.1.0" 1036 | strip-bom "^2.0.0" 1037 | tar "^2.2.1" 1038 | tar-stream "^1.5.2" 1039 | user-home "^2.0.0" 1040 | validate-npm-package-license "^3.0.1" 1041 | 1042 | --------------------------------------------------------------------------------