├── .travis.yml ├── .babelrc ├── src ├── index.css ├── api │ ├── baseUrl.js │ └── userApi.js ├── index.test.js ├── vendor.js ├── index.js └── index.html ├── .editorconfig ├── .eslintrc.json ├── appveyor.yml ├── webpack.config.dev.js ├── .gitignore ├── LICENSE ├── webpack.config.prod.js ├── package.json └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "latest" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Sans-Serif; 3 | } 4 | 5 | table th { 6 | padding: 5px 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:import/errors", 6 | "plugin:import/warnings" 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 7, 10 | "sourceType": "module" 11 | }, 12 | "env": { 13 | "browser": true, 14 | "node": true, 15 | "mocha": true 16 | }, 17 | "rules": { 18 | "no-console": 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against this version of Node.js 2 | environment: 3 | matrix: 4 | # node.js 5 | - nodejs_version: "6" 6 | 7 | # Install scripts. (runs after repo cloning) 8 | install: 9 | # Get the latest stable version of Node.js or io.js 10 | - ps: Install-Product node $env:nodejs_version 11 | # install modules 12 | - npm install 13 | 14 | # Post-install test scripts. 15 | test_script: 16 | # Output useful info for debugging. 17 | - node --version 18 | - npm --version 19 | # run tests 20 | - npm test 21 | 22 | # Don't actually build. 23 | build: off 24 | -------------------------------------------------------------------------------- /src/api/baseUrl.js: -------------------------------------------------------------------------------- 1 | export default function getBaseUrl() { 2 | return getQueryStringParameterByName('useMockApi') ? 'http://localhost:3001/' : 'https://mysterious-dawn-16770.herokuapp.com/'; 3 | } 4 | 5 | function getQueryStringParameterByName(name, url) { 6 | if (!url) url = window.location.href; 7 | name = name.replace(/[\[\]]/g, "\\$&"); 8 | var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), 9 | results = regex.exec(url); 10 | if (!results) return null; 11 | if (!results[2]) return ''; 12 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 13 | } 14 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import jsdom from 'jsdom'; 3 | import fs from 'fs'; 4 | 5 | describe('Our first test', () => { 6 | it('should pass', () => { 7 | expect(true).to.equal(true); 8 | }); 9 | }); 10 | 11 | describe('index.html', () => { 12 | it('should have h1 that says Users', (done) => { 13 | const index = fs.readFileSync('./src/index.html', "utf-8"); 14 | jsdom.env(index, function(err, window) { 15 | const h1 = window.document.getElementsByTagName('h1')[0]; 16 | expect(h1.innerHTML).to.equal("Users"); 17 | done(); 18 | window.close(); 19 | }); 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/api/userApi.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | import getBaseUrl from './baseUrl'; 3 | 4 | const baseUrl = getBaseUrl(); 5 | 6 | export function getUsers() { 7 | return get('users'); 8 | } 9 | 10 | export function deleteUser(id) { 11 | return del(`users/${id}`); 12 | } 13 | 14 | function get(url) { 15 | return fetch(baseUrl + url).then(onSuccess, onError); 16 | } 17 | 18 | // Can't call func delete since reserved word. 19 | function del(url) { 20 | const request = new Request(baseUrl + url, { 21 | method: 'DELETE' 22 | }); 23 | 24 | return fetch(request).then(onSuccess, onError); 25 | } 26 | 27 | function onSuccess(response) { 28 | return response.json(); 29 | } 30 | 31 | function onError(error) { 32 | console.log(error); // eslint-disable-line no-console 33 | } 34 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 3 | 4 | export default { 5 | debug: true, 6 | devtool: 'inline-source-map', 7 | noInfo: false, 8 | entry: [ 9 | path.resolve(__dirname, 'src/index') 10 | ], 11 | target: 'web', 12 | output: { 13 | path: path.resolve(__dirname, 'src'), 14 | publicPath: '/', 15 | filename: 'bundle.js' 16 | }, 17 | plugins: [ 18 | // Create HTML file that includes reference to bundled JS. 19 | new HtmlWebpackPlugin({ 20 | template: 'src/index.html', 21 | inject: true 22 | }) 23 | ], 24 | module: { 25 | loaders: [ 26 | {test: /\.js$/, exclude: /node_modules/, loaders: ['babel']}, 27 | {test: /\.css$/, loaders: ['style','css']} 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/vendor.js: -------------------------------------------------------------------------------- 1 | /* This file contains references to the vendor libraries 2 | we're using in this project. This is used by webpack 3 | in the production build only*. A separate bundle for vendor 4 | code is useful since it's unlikely to change as often 5 | as the application's code. So all the libraries we reference 6 | here will be written to vendor.js so they can be 7 | cached until one of them change. So basically, this avoids 8 | customers having to download a huge JS file anytime a line 9 | of code changes. They only have to download vendor.js when 10 | a vendor library changes which should be less frequent. 11 | Any files that aren't referenced here will be bundled into 12 | main.js for the production build. 13 | */ 14 | 15 | /* eslint-disable no-unused-vars */ 16 | 17 | import fetch from 'whatwg-fetch'; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | #Webstorm 40 | .idea 41 | 42 | #prod build 43 | dist 44 | 45 | #mock db 46 | db.json 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cory House 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | 3 | import {getUsers, deleteUser} from './api/userApi'; 4 | 5 | // Populate table of users via API call. 6 | getUsers().then(result => { 7 | let usersBody = ""; 8 | 9 | result.forEach(user => { 10 | usersBody+= ` 11 | Delete 12 | ${user.id} 13 | ${user.firstName} 14 | ${user.lastName} 15 | ${user.email} 16 | ` 17 | }); 18 | 19 | global.document.getElementById('users').innerHTML = usersBody; 20 | 21 | const deleteLinks = global.document.getElementsByClassName('deleteUser'); 22 | 23 | // Must use array.from to create a real array from a DOM collection 24 | // getElementsByClassname only returns an "array like" object 25 | Array.from(deleteLinks, link => { 26 | link.onclick = function(event) { 27 | const element = event.target; 28 | event.preventDefault(); 29 | deleteUser(element.attributes["data-id"].value); 30 | const row = element.parentNode.parentNode; 31 | row.parentNode.removeChild(row); 32 | }; 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 18 | <% if (htmlWebpackPlugin.options.trackJSToken) { %> 19 | 20 | 21 | 22 | 23 | <% } %> 24 | 25 | 26 | 27 |

Users

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
 IdFirst NameLast NameEmail
40 | 41 | 42 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import path from 'path'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import WebpackMd5Hash from 'webpack-md5-hash'; 5 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 6 | 7 | export default { 8 | debug: true, 9 | devtool: 'source-map', 10 | noInfo: false, 11 | entry: { 12 | vendor: path.resolve(__dirname, 'src/vendor'), 13 | main: path.resolve(__dirname, 'src/index') 14 | }, 15 | target: 'web', 16 | output: { 17 | path: path.resolve(__dirname, 'dist'), 18 | publicPath: '/', 19 | filename: '[name].[chunkhash].js' 20 | }, 21 | plugins: [ 22 | // Generate an external css file with a hash in the filename 23 | new ExtractTextPlugin('[name].[contenthash].css'), 24 | 25 | // Hash the files using MD5 so that their names change when the content changes. 26 | new WebpackMd5Hash(), 27 | 28 | // Use CommonsChunkPlugin to create a separate bundle 29 | // of vendor libraries so that they're cached separately. 30 | new webpack.optimize.CommonsChunkPlugin({ 31 | name: 'vendor' 32 | }), 33 | 34 | // Create HTML file that includes reference to bundled JS. 35 | new HtmlWebpackPlugin({ 36 | template: 'src/index.html', 37 | minify: { 38 | removeComments: true, 39 | collapseWhitespace: true, 40 | removeRedundantAttributes: true, 41 | useShortDoctype: true, 42 | removeEmptyAttributes: true, 43 | removeStyleLinkTypeAttributes: true, 44 | keepClosingSlash: true, 45 | minifyJS: true, 46 | minifyCSS: true, 47 | minifyURLs: true 48 | }, 49 | inject: true, 50 | // Properties you define here are available in index.html 51 | // using htmlWebpackPlugin.options.varName 52 | trackJSToken: 'INSERT YOUR TOKEN HERE' 53 | }), 54 | 55 | // Eliminate duplicate packages when generating bundle 56 | new webpack.optimize.DedupePlugin(), 57 | 58 | // Minify JS 59 | new webpack.optimize.UglifyJsPlugin() 60 | ], 61 | module: { 62 | loaders: [ 63 | {test: /\.js$/, exclude: /node_modules/, loaders: ['babel']}, 64 | {test: /\.css$/, loader: ExtractTextPlugin.extract('css?sourceMap')} 65 | ] 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript-development-environment", 3 | "version": "1.0.0", 4 | "description": "JavaScript development environment Pluralsight course by Cory House", 5 | "scripts": { 6 | "prestart": "babel-node buildScripts/startMessage.js", 7 | "start": "npm-run-all --parallel security-check open:src lint:watch test:watch start-mockapi", 8 | "open:src": "babel-node buildScripts/srcServer.js", 9 | "lint": "esw webpack.config.* src buildScripts --color", 10 | "lint:watch": "npm run lint -- --watch", 11 | "security-check": "nsp check", 12 | "localtunnel": "lt --port 3000", 13 | "share": "npm-run-all --parallel open:src localtunnel", 14 | "test": "mocha --reporter progress buildScripts/testSetup.js \"src/**/*.test.js\"", 15 | "test:watch": "npm run test -- --watch", 16 | "generate-mock-data": "babel-node buildScripts/generateMockData", 17 | "prestart-mockapi": "npm run generate-mock-data", 18 | "start-mockapi": "json-server --watch src/api/db.json --port 3001", 19 | "clean-dist": "rimraf ./dist && mkdir dist", 20 | "prebuild": "npm-run-all clean-dist test lint", 21 | "build": "babel-node buildScripts/build.js", 22 | "postbuild": "babel-node buildScripts/distServer.js", 23 | "deploy": "surge ./dist" 24 | }, 25 | "author": "Cory House", 26 | "license": "MIT", 27 | "dependencies": { 28 | "whatwg-fetch": "1.0.0" 29 | }, 30 | "devDependencies": { 31 | "babel-cli": "6.16.0", 32 | "babel-core": "6.17.0", 33 | "babel-loader": "6.2.5", 34 | "babel-preset-latest": "6.16.0", 35 | "babel-register": "6.16.3", 36 | "chai": "3.5.0", 37 | "chalk": "1.1.3", 38 | "cheerio": "0.22.0", 39 | "compression": "1.6.2", 40 | "cross-env": "3.1.3", 41 | "css-loader": "0.25.0", 42 | "eslint": "3.8.1", 43 | "eslint-plugin-import": "2.0.1", 44 | "eslint-watch": "2.1.14", 45 | "express": "4.14.0", 46 | "extract-text-webpack-plugin": "1.0.1", 47 | "html-webpack-plugin": "2.22.0", 48 | "jsdom": "9.8.0", 49 | "json-schema-faker": "0.3.6", 50 | "json-server": "0.8.22", 51 | "localtunnel": "1.8.1", 52 | "mocha": "3.1.2", 53 | "nock": "8.1.0", 54 | "npm-run-all": "3.1.1", 55 | "nsp": "2.6.2", 56 | "numeral": "1.5.3", 57 | "open": "0.0.5", 58 | "rimraf": "2.5.4", 59 | "style-loader": "0.13.1", 60 | "surge": "0.18.0", 61 | "webpack": "1.13.2", 62 | "webpack-dev-middleware": "1.8.4", 63 | "webpack-hot-middleware": "2.13.0", 64 | "webpack-md5-hash": "0.0.5" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Development Environment 2 | 3 | This is a generic JavaScript development environment that I build from scratch in ["Building a JavaScript Development Environment" on Pluralsight](https://app.pluralsight.com/library/courses/javascript-development-environment/table-of-contents). This isn't tied to any specific JS framework. 4 | 5 | ## Get Started 6 | 7 | 1. **Install [Node 6 or newer](https://nodejs.org)**. Need to run multiple versions of Node? Use [nvm](https://github.com/creationix/nvm) or [nvm-windows](https://github.com/coreybutler/nvm-windows) 8 | 2. **Clone this repository.** - `git clone https://github.com/coryhouse/javascript-development-environment.git` or [download the zip](https://github.com/coryhouse/javascript-development-environment/archive/master.zip) 9 | 3. **Make sure you're in the directory you just created.** - `cd javascript-development-environment` 10 | 4. **Install Node Packages.** - `npm install` 11 | 5. **Run the app.** - `npm start -s` 12 | This will run the automated build process, start up a webserver, and open the application in your default browser. When doing development with this kit, this command will continue watching files all your files. Every time you hit save the code is rebuilt, linting runs, and tests run automatically. Note: The -s flag is optional. It enables silent mode which suppresses unnecessary messages during the build. 13 | 6. Having issues? See below. 14 | 15 | ## Having Issues? Try these things first: 16 | 17 | 1. Run `npm install` - If you forget to do this, you'll see this: `babel-node: command not found`. 18 | 2. Try running the latest version of Node. 19 | 3. Make sure files with names that begin with a dot (.babelrc, .editorconfig, .eslintrc) are copied to the project directory root. This is easy to overlook if you copy this repository manually. 20 | 4. Don't run the project from a symbolic link. It will cause issues with file watches. 21 | 5. Having linting issues? Delete any .eslintrc that you're storing in your user directory. Also, disable any ESLint plugin / custom rules that you've enabled within your editor. These will conflict with the ESLint rules defined in the course. 22 | 6. Seeing `Error: listen EADDRINUSE :::3000`? That means port 3000 is already in use on your machine. You probably have another instance of this project running on your machine in a different window. So find that window and kill the other instance using Ctrl+C. 23 | 7. Nothing above work? Delete your node_modules folder and re-run npm install. 24 | 25 | ### Development Dependencies 26 | 27 | | **Dependency** | **Use** | 28 | | --------------------------- | --------------------------------------------------------------------------------------------------------- | 29 | | babel-cli | Babel Command line interface | 30 | | babel-core | Babel Core for transpiling the new JavaScript to old | 31 | | babel-loader | Adds Babel support to Webpack | 32 | | babel-preset-latest | Babel preset for running all the latest standardized JavaScript features | 33 | | babel-register | Register Babel to transpile our Mocha tests | 34 | | chai | Assertion library | 35 | | chalk | Colored command line output | 36 | | cheerio | Supports querying DOM with jQuery like syntax - Useful in testing and build process for HTML manipulation | 37 | | compression | gzip http output | 38 | | cross-env | Cross-environment friendly way to handle environment variables | 39 | | css-loader | Add CSS support to Webpack | 40 | | eslint | Lints JavaScript | 41 | | eslint-plugin-import | Advanced linting of ES6 imports | 42 | | eslint-watch | Add watch functionality to ESLint | 43 | | express | Serves development and production builds | 44 | | extract-text-webpack-plugin | Extracts CSS into separate file for production build | 45 | | html-webpack-plugin | Generate HTML file programatically via Webpack | 46 | | jsdom | In-memory DOM for testing | 47 | | json-schema-faker | Declare a JSON schema for generating fake data | 48 | | json-server | Serve a JSON API locally | 49 | | localtunnel | Create a tunnel to your local machine | 50 | | mocha | JavaScript testing library | 51 | | nock | Mock HTTP requests via Node | 52 | | npm-run-all | Display results of multiple commands on single command line | 53 | | numeral | Library for working with numbers | 54 | | open | Open app in default browser | 55 | | rimraf | Delete files | 56 | | style-loader | Add Style support to Webpack | 57 | | webpack | Bundler with plugin system and integrated development server | 58 | | webpack-dev-middleware | Adds middleware support to webpack | 59 | | webpack-hot-middleware | Adds hot reloading to webpack | 60 | | webpack-md5-hash | Used to hash files generated by Webpack using MD5 so that their names change when the content changes | 61 | --------------------------------------------------------------------------------