This is a repo for containing my notes, example code, etc. from the Building a JavaScript Development Environment course by Cory House on PluralSight
General idea is that in order to avoid missing steps, avoid re-creating things each and every time you need to do JavaScript development, you should instead build a "starter kit" that you can use over and over.
The author said he was inspired by the book The Checklist Manifesto; by the idea of professionals in many fields using checklists. The general idea is that "...we think we can remember all the steps..." but in reality we can't or don't. Example: Doctors use checklists for starting a line on a patient.
What belongs in your JavaScript Starter Kit?
- Package Management
- Bundling
- Minification
- Sourcemaps
- Transpiling
- Dynamic HTML Generation
- Centralized HTTP
- Mock API Framework
- Component Libraries
- Development Webserver
- Linting
- Automated Testing
- Continuous Integration
- Automated build
- Automated deployment
- Working example app
- Strong ES2015+ support
- Autocompletion
- Parse ES6 imports
- Report unused imports
- Automated refactoring
- Framework intelligence
- Built-in terminal
Best way to align configuration in your editors
- Create a .editorconfig file
- Many editors require a plugin
Selected npm, pretty much the defacto standard now
-
nsp check
-
- Ultra-simple
- Single command serves up server
-
- Lightweight
- Support live-reloading
-
- Comprehensive
- Highly configurable
- Not just for static files
- Production grade
- Run it everywhere
- Goes good with node
- Alternatives:
- koa
- hapi
-
- Integrates with Browserify
- Includes hot reloading
-
- Built in to Webpack
- Serves from memory
- Includes hot reloading
-
- Dedicated IP for sharing work on LAN
- All interactions remain in sync
- Across devices, etc.
- Great for cross-device testing
- Literally syncs across multiple browsers in real time
- Browsersync recipes available
- Integrates with Webpack, Express
Created srcServer.js file to configure and run Express.
Used node .\buildScripts\srcServer.js
command to leverage our script to run Express.
Alternatives to using traditional Cloud services such as AWS and Azure.
-
- Easily share work on your local machine
npm install localtunnel -g
- start your app
lt --port 3000 --subdomain kevin
- Creates: http://kevin.localtunnel.me
- Easily share work on your local machine
-
- Secure tunnel to your local machine
- Pretty easy to share work
- Install ngrok
- Install authtoken
- Start your app
./ngrok http 80
- Secure
-
- Quickly host static files to public URL
- Setup
npm install -g surge
surge
- Different approach
- Hosting persists
-
- Quickly deploy Node.js to the cloud
- To use
npm install -g now
- Create start script
- now
- Hosting persists
-
- the "original"
- configuration over code
- writes intermediary files between steps
- large plugin ecosystem
-
- in-memory streams; pipes
- no files
- fast
- code over configuration; code-based
- large plugin ecosystem
- in-memory streams; pipes
-
- declared in package.json
- leverage your OS' command line
- directly use npm packages
- call separate Node scripts
- convention-based pre/post hooks
- leverage world's largest package manager
- Use tools directly
- No need for separate plugins
- Simpler debugging
- Better documentation
- Easy to learn
- Simple
Author wrote interesting article on why he migrated from Gulp to npm Scripts:
All of these changes are to be made in the "scripts" block near the top of the file.
-
Add start:
"start":"node buildScripts/srcServer.js"
npm start
to start up the local webservernpm start -s
(silent) to start up the local webserver without any output
-
Add prestart:
"prestart":"node buildScripts/startMessage.js"
- This script uses a library called Chalk to add a colored message to the console when we run npm start
-
Add security-check (nsp):
"security-check": "nsp check"
npm run security-check
- Note: npm start, npm test are the only commands where you can avoid typing run
-
Add share (localtunnel):
"share": "lt --port 3000 --subdomain kevin"
npm run share
- This will launch
http://kevin.localtunnel.me
-
Add "open:src" and Update "start" (npm-run-all):
"start": "npm-run-all --parallel security-check open:src" "open:src": "node buildScripts/srcServer.js"
- Note:
npm-run-all
allows us to run 1-n of the scripts in parallel
- Note:
-
Add "localtunnel" and Update "share" (npm-run-all)
"localtunnel": "lt --port 3000 --subdomain kevin" "share": "npm-run-all --parallel open:src localtunnel"
npm run share
now starts up our server and exposes it via localtunnel all at once!
- In an npm script, like package.json (above), we can append the prefixes "pre" and "post" to a given script in order to run another script before (pre) or after (post)
- Examples: "prestart" - will get run before "start", "poststart" - will get run after "start"
-
- Modern, standards-based JS, today; transpiles down to ES5
- Write standardized JavaScript
- Leverage full JS Ecosystem
- Use experimental features earlier
- No type definitions required
- No data annotations required
- ES6 imports are statically analyzable
- Test + Lint + Babel + Great Libraries + IDE == safety
-
- Superset of JavaScript
- Enhanced Autocompletion
- Enhanced readability
- Safer refactoring
- Clearer intent
- Additional, non-standard features
-
- Functional style language
- Compiles down to JS
- Clean Syntax
- Immutable data structures
- Friendly errors
- All errors are compile-time errors
- Interoperates with JS
.babelrc | package.json |
---|---|
Not npm specific | One less file in your project |
Easier to read since isolated | |
Preset | Approach |
---|---|
babel-preset-es2015-node | Version Detection |
babel-preset-latest-minimal | Feature Detection |
ES5 | Transpiled |
---|---|
No waiting for transpile; faster | Enjoy the latest features |
No transpiler dependency | Consistent coding style |
Use the same linting rules everywhere | |
Can eventually remove transpiler |
Change "prestart" script to use babel-node
babel-node buildScripts/startMessage.js
- CommonJS does not work in web browsers; just NodeJS
- Package project into file(s)
- Improve Node performance
-
Old Module Formats
-
- Standardized
- Statically analyzable
- Improved autocomplete
- Intelligent refactoring
- Fails fast
- Tree shaking (dead code discovery?)
- Easy to read
- Named Imports
- Default exports
-
OLD Bundler
-
- First popular bundler
- Utilizes and helped popularize AMD pattern
-
-
- Simple
- The first bundler to reach mass adoption
- Bundle npm packages for the web
- Large plugin ecosystem
- Linting, Transpiling, etc.
-
- Comprehensive
- Bundles more than just JavaScript
- Import CSS, Images, just like JavaScript
- Built in hot-reloading web server
-
- Tree shaking
- Faster loading production code
- Quite new?
- Great choice for library authors
- No hot reloading and code splitting yet
-
- Runtime loader
- Uses SystemJS; universal module loader
- Can load modules at runtime
- Has its own package manager
- Can install from npm, git
- Uses Rollup
- More than just JavaScript
- CSS, Images, Fonts, HTML
- Bundle splitting
- Hot module reloading
- Webpack 2 soon --> tree shaking coming
- Create a couple of basic files to use with Webpack,
index.js
andindex.css
in the src folder- In index.js we do the following
- use a Node package called numeral
- import our
index.css
file
- In index.js we do the following
- Create a basic Webpack configuration file in the project root called
webpack.config.dev.js
(see project for contents)- Within the configuration, we output our bundled JavaScript in a single file called
bundle.js
- Within the configuration, we output our bundled JavaScript in a single file called
- Update Express webserver (srcServer.js) to use Webpack:
import webpack from 'webpack';
import config from '.../webpack.config.dev'
...
const compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
...
-
Update index.html to reference
bundle.js
generated by Webpack -
Note in browser when the application is run, bundle.js shows as the JavaScript file included by the page. In addition, because Webpack is configured to include our stylesheet, we find the css styles we defined in index.css embedded in the bundle.js file.
- Maps code back to original source
- Part of our build
- Downloaded if you open developer tools; downloaded only when needed
- Curly brace position
- confirm / alert
- Trailing commas
- Globals
- eval
- Extra parenthesis
- Overwriting function
- Assignment in conditional
- Missing default case in switch
- debugger / console.log
-
- Douglas Crockford
- Original
-
- Defacto Standard
-
- For TypeScript until their is support for it in ESLint
- Five formats allowed
- .eslintrc.js
- .eslintrc.yaml
- .eslintrc.yml
- .eslintrc.json
- .eslintrc
- package.json
Dedicated config file | package.json |
---|---|
Not tied to npm | One less file |
package.json
- add a new section to package.json- Note: the block of JSON added to the package.json file is the same block if you go the dedicated config file route.
"eslintConfig": {
"plugins": ["example"],
"env": {
"example/custom": true
}
}
As a team go through the available linting rules and decide which ones to include.
Warning | Error |
---|---|
Can continue development | Breaks the build |
Can be ignored | Cannot be ignored |
Team must agree: Fix warnings | Team is forced to comply |
- From scratch
- ESLint's Recommended (author's recommendation)
- Presets
- airbnb
- standardJS
- XO
- eslint-loader
- Re-lints all files upon save
- Depends upon Webpack
- eslint-watch (author's choice)
- ESLint wrapper that adds file watch
- Not tied to Webpack
- Better warning and error formatting
- Displays clean message
- Easily lint tests and build scripts too
- Run ESLint directly
- Supports ES6 and ES7 natively
- Also supports object spread
- Babel-eslint
- Also lints stage1-stage4 features
- Single place to check
- Universal configuration
- Part of continuous integration (CI)
- Use ESLint Recommended rules
- Use
eslint-watch
- Add lint configuration to the root of the project via
.eslintrc.json
file - See
.eslintrc.json
for details - to override any rules (we do so in the rules section of the file)
...
"exptends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
]
...
"rules": {
"no-console": 1
}
- Note: To override a rule you need to add the name of the rule and a setting: 0 - Off, 1 - Warning, and 2 - Error
- Add the following to the
package.json
file so we can use ESLint via eslint-watch (esw)
"scripts": {
...
"lint": "esw webpack.config.* src buildScripts --color",
...
}
-
Note: This configuration says use our webpack configuration file and watch the src and buildScripts folders
-
Note: If you have an editor with built-in linting capability, disable it at this point so that it doesn't interfere with your specific linting setup here.
-
Now, we add an additional script to package.json in order to tell eslint-watch to continuously watch our files for changes and then add the new script to our start script:
"scripts": {
"start": "npm-run-all --parallel security-check open:src lint:watch",
...
"lint:watch": "npm run lint -- --watch",
...
}
- Unit Testing
- Integration Testing
- UI Testing
-
- Most popular
- Highly configurable
- Large eco-system of support
-
- Popular
- Includes assertion library
-
- Lean
- Simple
- Minimal configuration
-
- Oldest; jQuery
-
- Runs tests in parallel
- Only re-runs impacted tests
-
- Popular for React developers
- Wrapper for Jasmine
- Code Coverage
- JSDOM
- Popular convention for finding test files
- Chai (author's choice)
- ShouldJS
- Expect
-
- Simulate the browser's DOM
- Run DOM-related tests without a browser
-
- jQuery for the server
- Query virtual DOM using jQuery selectors
- jQuery for the server
- In a browser
- Karma, Testem
- In a Headless browser
- PhantomJS
- In-memory DOM (author's choice - see above)
- JSDOM
- Centralized
- Mocha
- Folder called "Tests"
- Less "noise" in src folder
- Deployment confusion
- Inertia
- Alongside (author's choice)
- Easy imports
- Clear visibility
- Convenient to open
- No recreating folder structure
- Easy file moves
- filename.spec.js
- filename.test.js
- When you hit save
- Rapid feedback
- Facilitates TDD
- Automatic; low friction
- Increases test visibility
- Mocha Framework
- Chai Assertion Library
- JSDOM Helper Library (In-Memory DOM)
- Node to run our tests
- Place our test files alongside the actual files (using filename.spec.js or filename.test.js?)
- Tests will run everytime we save
- Run automated build
- Run your tests
- Check your code coverage
- Deploy your code
-
- Popular
- Hosted Solution
-
- Windows support
- Hosted Solution
-
- Popular
- Installed
Added a .travis.yml file to the project in order to leverage the TravisCI platform. Since TravisCI is integrated into Github, once I turned CI on for the project and checked in the yaml file, then the CI kicked off automatically. TravisCI uses a Linux virtual machine to perform the CI task(s).
.travis.yml - tells TravisCI to set the language to NodeJS version 6
language: node_js
node_js:
- "6"
Added a appveyor.yml file to the project in order to leverage the Appveyor platform. Since the Appveyor is integrated, although not as well as TravisCI, the configuration is a little more involved. Once I checked in the configuration file and setup the webhook between Github and Appveyor (which was done automagically by signing into Appveyor with my Github account), the CI kicked off automatically. Appveyor, alternatively, uses Windows VMs to perform its CI task(s).
appveyor.yml - tells Appveyor to set the language to NodeJS version 6, to install that version of NodeJS, to install all the packages by running npm install and finally to run npm test to execute the unit tests (and not to actually try to 'build' the code since its Node (JavaScript).
environment:
matrix:
- nodejs_version: "6"
install:
- ps: Install-Product node $env:nodejs_version
- npm install
test_script:
- node --version
- npm --version
- npm test
build: off
-
-
-
- WHAT working group
- Requires polyfill in some cases
- Limited feature set
-
-
- Wrapper around GitHub's implementation of fetch?
-
- Subset of request (see above)
-
- Full featured
- Plugin Ecosystem
-
- Full featured
- Promise-based API
-
- Configure all calls
- Handle preloader logic
- Handle errors
- Single seam for mocking
- Add a new route to
srcServer.js
to simulate an available API endpoint called users
app.get('/users', function (request, response) {
response.json([
{ "id": 1, "firstName": "Bob", "lastName": "Smith", "email": "bob@example.com" },
{ "id": 2, "firstName": "Tammy", "lastName": "Norton", "email": "tnorton@example.com" },
{ "id": 3, "firstName": "Tina", "lastName": "Lee", "email": "lee.tina@example.com" },
]);
});
- Verify the new "API" by hitting http://localhost:3000/users in a browser
- To do this run
npm start
in the shell
- To do this run
- Create a new folder called
API
inside thesrc
folder to contain our API client implementation userApi.js
import 'whatwg-fetch';
/* eslint-disable no-console */
export function getUsers() {
return get('users');
}
function get(url) {
return fetch(url).then(onSuccess, onError);
}
function onSuccess(response) {
return response.json();
}
function onError(error) {
console.log(error);
}
userApi.js
code is usingfetch
module; uses promises, error handling via fetchwhatwg-fetch
module is a polyfil for non-modern browsers- Repository pattern like
- Update
index.html
to accommodate user data - Recreate
index.js
to use getUsers() fromuserApi.js
import './index.css';
import { getUsers } from './api/userApi';
getUsers().then(result => {
let usersBody = "";
result.forEach(user => {
usersBody += `<div id="row">
<div id="column1"><a href="#" data-id="${user.id}" class="deleteUser">Delete</a></div>
<div id="id">${user.id}</div>
<div id="firstName">${user.firstName}</div>
<div id="lastName">${user.lastName}</div>
<div id="email">${user.email}</div>
</div>`
});
global.document.getElementById('users').outerHTML = usersBody;
});
For example, we can use Polyfill.io to only polyfill FetchAPI:
<script src="https://cdn.polyfill.io/v2/polyfill.js?features=fetch"></script>
- Unit Testing
- Instant Response
- Keep working when service are down
- Rapid prototyping
- avoid inter-team bottlenecks
- Work offline
- Declare our schema via JSON Schema Faker
- Generate Random Data
- faker.js
- chance.js
- randexp.js
- Serve Data via API
- JSON Server
In order to emulate some behavior and make the test data more robust, we can employ a mocking library. In this case we used JSON Schema Faker to generate random test data based on a schema and then used that test data from our test page.
- Added functionality to
index.js
in order to make thedelete
links work next to each user
global.document.getElementById('users').outerHTML = usersBody;
const deleteLinks = global.document.getElementsByClassName('deleteUser');
Array.from(deleteLinks, link => {
link.onclick = function (event) {
event.preventDefault();
const element = event.target;
deleteUser(element.attributes["data-id"].value);
const row = element.parentNode.parentNode;
row.parentNode.removeChild(row);
};
});
- Next, since we don't yet have a deleteUser function, we need to modify the
userApi.js
to include a deleteUser function- This new code uses the
fetch
library (see above) - This new code also uses an update version of the
getBaseUrl
function from baseUrl.js
- This new code uses the
...
const baseUrl = getBaseUrl();
...
export function deleteUser(id) {
return del(`users/${id}`);
}
...
function del(url) {
const request = new Request(baseUrl + url, {
method: 'DELETE'
});
return fetch(request).then(onSuccess, onError);
}
...
export default function getBaseUrl() {
const inDevelopment = window.location.hostname === 'localhost';
return inDevelopment ? 'http://localhost:3001/' : '/';
}
- Now, the
getBaseUrl
function returns differently based on local vs. "Production"- If the code is running locally, then in returns the JSON Server URL,
http://localhost:3001/
- If the code is running locally, then in returns the JSON Server URL,
- Finally, in order to generate the fake data on the fly each time the application is started, we need to modify the
package.json
file by adding some new functions- We've created a script to generate the mock data, calling
generateMockData.js
- We've created a start (and prestart) for starting up JSON Server, which serves up the generated data from
db.json
via the JSON Server URL (http://localhost:3001/users
)
- We've created a script to generate the mock data, calling
...
"start":"npm-run-all --parallel security-check open:src lint:watch test:watch start-mockapi",
...
"generate-mock-data": "babel-node buildScripts/generateMockData",
"prestart-mockapi": "npm run generate-mock-data",
"start-mockapi": "json-server --watch src/api/db.json --port 3001"
...
Provides examples of:
- Directory structure and file naming
- Framework usage
- Testing
- Mock API
- Automated deployment
- Codifies decisions
- coding standards, etc.
- Interactive example of working with starter kit
- JavaScript belongs in a .js file
- Avoid dynamically generating JavaScript logic. Dynamically generate JSON instead
- Consider organizing by feature (on larger projects) otherwise Organize by File Type
Organization Type | Examples |
---|---|
Organize by File Type | /components |
/data | |
/models | |
/views | |
Organize by Feature | /authors |
/courses | |
- Extract logic into POJO (Plain Old JavaScript objects)
- Pure logic; no framework-specific code
The Checklist Manifesto by Atul Gawande