README updates (and some code inspection fixes)
This commit is contained in:
parent
efab8c7147
commit
383de1cd8a
12 changed files with 262 additions and 820 deletions
|
|
@ -1,649 +1,7 @@
|
|||
Private Node Modules
|
||||
===
|
||||
|
||||
**my_modules** contains all the *private* **JSMachines** Node modules. It is the counterpart to
|
||||
**my_modules** contains all the *private* **PCjs** Node modules. It is the counterpart to
|
||||
**node_modules**, where all the *public* Node modules are installed. Only private Node modules are checked
|
||||
into GitHub; if we later decide to publish any or all of these on [npmjs.org](http://npmjs.org), then they will
|
||||
into GitHub; if any of these are later published on [npmjs.org](http://npmjs.org), then they will
|
||||
be moved to **node_modules** and removed from this folder.
|
||||
|
||||
The rest of this file documents some of the early steps involved in porting the **JSMachines** project,
|
||||
which required a web server running PHP, to the new **PCjs** web server running Node. That includes setting up
|
||||
my first Node web server and then extending its functionality with these private Node modules.
|
||||
|
||||
For more details on the private Node modules stored here, refer to the README.md for each module (which, alas,
|
||||
may be nothing more than placeholders at this point, but I have good intentions).
|
||||
|
||||
Creating My First Node Project
|
||||
===
|
||||
|
||||
Plan A: Fork jsmachines.net -> pcjs.org
|
||||
---
|
||||
The original **JSMachines** project assumed a web server running PHP
|
||||
(eg, [http://jsmachines.net](http://jsmachines.net)). However, to improve client-server integration, reduce
|
||||
the number of languages/frameworks used, and implement some new features, such as socket-based disk I/O interfaces,
|
||||
I decided to create a fork, **PCjs**, and port all the server-side PHP to JavaScript, running on a Node web server.
|
||||
|
||||
Unfortunately, when I tried to fork my private **JSMachines** project on GitHub, I discovered that you're
|
||||
not allowed to [fork](https://help.github.com/articles/fork-a-repo) your own projects. Adding to the confusion: GitHub
|
||||
displays the "Fork" button on my project's home page, and even lets me click it, but then quietly does nothing.
|
||||
|
||||
The recommended work-around (as per [this post](http://bitdrift.com/post/4534738938/fork-your-own-project-on-github))
|
||||
seemed to be:
|
||||
|
||||
- Create new project on GitHub
|
||||
- Clone old project with new name
|
||||
- Replace new project's "origin URL" with URL for new GitHub project
|
||||
- Add original project as an "upstream source"
|
||||
- Push new project to GitHub
|
||||
|
||||
Plan B: Branch jsmachines -> node_dev
|
||||
---
|
||||
The above seemed a bit clunky, so I decided to branch instead of fork. This new development branch was called
|
||||
**node_pcjs**, but I've since branched to **node_dev**. I should probably remove the original **node_pcjs** branch,
|
||||
but for now, it's still there.
|
||||
|
||||
Then I pushed the **node_dev** branch to the remote, as per [GitHub](https://help.github.com/articles/pushing-to-a-remote):
|
||||
|
||||
[~/Sites/jsmachines] git push origin node_dev
|
||||
Total 0 (delta 0), reused 0 (delta 0)
|
||||
To git@github.com:jeffpar/jsmachines.git
|
||||
* [new branch] node_dev -> node_dev
|
||||
[~/Sites/jsmachines]
|
||||
|
||||
That seemed to work fine, so I then created a new folder and cloned the project, as per
|
||||
[GitHub](https://help.github.com/articles/fetching-a-remote):
|
||||
|
||||
[~/Sites] git clone git@github.com:jeffpar/jsmachines.git pcjs
|
||||
Cloning into 'pcjs'...
|
||||
remote: Reusing existing pack: 2725, done.
|
||||
remote: Total 2725 (delta 0), reused 0 (delta 0)
|
||||
Receiving objects: 100% (2725/2725), 59.48 MiB | 2.67 MiB/s, done.
|
||||
Resolving deltas: 100% (1543/1543), done.
|
||||
Checking connectivity... done
|
||||
|
||||
Then I checked-out the new branch:
|
||||
|
||||
[~/Sites] cd pcjs
|
||||
[~/Sites/pcjs] git checkout node_dev
|
||||
Branch node_dev set up to track remote branch node_dev from origin.
|
||||
Switched to a new branch 'node_dev'
|
||||
|
||||
If I have to make any changes to the "master" branch (ie, the original jsmachines project) in the interim,
|
||||
then after I've pushed those changes to GitHub, I can pull them into the **node_dev** branch like so:
|
||||
|
||||
[~/Sites/pcjs] git pull origin master
|
||||
|
||||
Installing Node (and NPM)
|
||||
---
|
||||
I downloaded the latest Node installation package for my MacBook from [nodejs.org](http://nodejs.org/),
|
||||
which provided the usual `node` and `npm` commands. Here are the versions they reported:
|
||||
|
||||
[~/Sites/pcjs] node -v
|
||||
v0.10.26
|
||||
[~/Sites/pcjs] npm -v
|
||||
1.4.3
|
||||
|
||||
I also installed a Node helper on my MacBook called `n` that makes it easy to keep Node up-to-date, per this tip on
|
||||
[stackoverflow](http://stackoverflow.com/questions/8191459/how-to-update-node-js-npm-and-all-other-dependencies):
|
||||
|
||||
1) Clear NPM's cache:
|
||||
|
||||
[~/Sites/pcjs] sudo npm cache clean -f
|
||||
|
||||
2) Install a little helper called 'n':
|
||||
|
||||
[~/Sites/pcjs] sudo npm install -g n
|
||||
|
||||
3) Install latest stable NodeJS version:
|
||||
|
||||
[~/Sites/pcjs] sudo n stable
|
||||
|
||||
Alternatively pick a specific version and install like so:
|
||||
|
||||
[~/Sites/pcjs] sudo n 0.8.20
|
||||
|
||||
I'm not sure I like the generic name for this command ("n"); what would have been wrong with "node-update"
|
||||
or "update-node"? Oh well.
|
||||
|
||||
Next, I wanted to make sure `npm` was up-to-date. According to
|
||||
[FAQ on npmjs.org](https://npmjs.org/doc/faq.html#How-do-I-update-npm), that's as easy as:
|
||||
|
||||
[~/Sites/pcjs] sudo npm update npm -g
|
||||
|
||||
although occasionally `npm` won't be able to update itself, in which case the fallback is:
|
||||
|
||||
[~/Sites/pcjs] curl https://npmjs.org/install.sh | sh
|
||||
|
||||
So, I made sure `npm` itself, along with `n` and all my other globally installed packages, were up-to-date,
|
||||
and then I used `n` to make sure I was running the latest stable version of Node:
|
||||
|
||||
[~/Sites/pcjs] sudo npm update -g
|
||||
[~/Sites/pcjs] sudo n stable
|
||||
[~/Sites/pcjs] node -v
|
||||
v0.10.26
|
||||
|
||||
I've also seen suggestions to run this command before the preceding commands, but I don't know how often this is
|
||||
recommended:
|
||||
|
||||
[~/Sites/pcjs] sudo npm cache clean -f
|
||||
|
||||
Next, I ran `npm init`:
|
||||
|
||||
[~/Sites/pcjs] npm adduser
|
||||
Username: jeffpar
|
||||
Password:
|
||||
Email: (this IS public) jeff@pcjs.org
|
||||
npm http PUT http://registry.npmjs.org/-/user/org.couchdb.user:jeffpar
|
||||
npm http 201 http://registry.npmjs.org/-/user/org.couchdb.user:jeffpar
|
||||
|
||||
[~/Sites/pcjs] npm init
|
||||
This utility will walk you through creating a package.json file.
|
||||
It only covers the most common items, and tries to guess sane defaults.
|
||||
|
||||
See `npm help json` for definitive documentation on these fields
|
||||
and exactly what they do.
|
||||
|
||||
Use `npm install <pkg> --save` afterwards to install a package and
|
||||
save it as a dependency in the package.json file.
|
||||
|
||||
Press ^C at any time to quit.
|
||||
name: (pcjs)
|
||||
version: (0.0.0) 0.1.0
|
||||
description: Node-enabled version of PCjs
|
||||
entry point: (server.js)
|
||||
test command:
|
||||
git repository: (git://github.com/jeffpar/jsmachines.git)
|
||||
keywords: pcjs,ibm pc,emulator
|
||||
author: Jeff Parsons <Jeff@pcjs.org>
|
||||
license: (ISC)
|
||||
About to write to /Users/Jeff/Sites/pcjs/package.json:
|
||||
|
||||
{
|
||||
"name": "pcjs",
|
||||
"version": "0.1.0",
|
||||
"description": "Node-enabled version of PCjs",
|
||||
"main": "server.js",
|
||||
"directories": {
|
||||
"doc": "docs",
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jeffpar/jsmachines.git"
|
||||
},
|
||||
"keywords": [
|
||||
"pcjs",
|
||||
"ibm",
|
||||
"pc",
|
||||
"emulator"
|
||||
],
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffpar/jsmachines/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jeffpar/jsmachines"
|
||||
}
|
||||
|
||||
Is this ok? (yes)
|
||||
|
||||
Next, I installed `express` using `npm install --save express`:
|
||||
|
||||
[~/Sites/pcjs] npm install --save express
|
||||
npm http GET http://registry.npmjs.org/express
|
||||
...
|
||||
express@3.4.8 node_modules/express
|
||||
├── methods@0.1.0
|
||||
├── merge-descriptors@0.0.1
|
||||
├── range-parser@0.0.4
|
||||
├── cookie-signature@1.0.1
|
||||
├── fresh@0.2.0
|
||||
├── debug@0.7.4
|
||||
├── buffer-crc32@0.2.1
|
||||
├── cookie@0.1.0
|
||||
├── mkdirp@0.3.5
|
||||
├── commander@1.3.2 (keypress@0.1.0)
|
||||
├── send@0.1.4 (mime@1.2.11)
|
||||
└── connect@2.12.0 (uid2@0.0.3, pause@0.0.1, qs@0.6.6, bytes@0.2.1, raw-body@1.1.2, batch@0.5.0, negotiator@0.3.0, multiparty@2.2.0)
|
||||
|
||||
This added the following lines to my "package.json" file:
|
||||
|
||||
"dependencies": {
|
||||
"express": "~3.4.8"
|
||||
}
|
||||
|
||||
Then I created a test "server.js" in the root of the project:
|
||||
|
||||
var http = require("http");
|
||||
var express = require("express");
|
||||
var app = express();
|
||||
app.get("/", function(req, res) {
|
||||
res.end("Testing");
|
||||
});
|
||||
http.createServer(app).listen(3000);
|
||||
|
||||
and ran `node server.js` and verified that all was working as expected.
|
||||
|
||||
Creating My First Node Module
|
||||
---
|
||||
The first module I decided to write was a port of the diskconv.php script I'd written to convert disk images to/from
|
||||
JSON. I decided to call this new module **DiskDump**, since it will output disk images in a variety of formats, although
|
||||
initially only as JSON.
|
||||
|
||||
[~/Sites/pcjs/my_modules] mkdir diskdump
|
||||
[~/Sites/pcjs/my_modules] cd diskdump
|
||||
[~/Sites/pcjs/my_modules/diskdump] npm init
|
||||
This utility will walk you through creating a package.json file.
|
||||
It only covers the most common items, and tries to guess sane defaults.
|
||||
|
||||
See `npm help json` for definitive documentation on these fields
|
||||
and exactly what they do.
|
||||
|
||||
Use `npm install <pkg> --save` afterwards to install a package and
|
||||
save it as a dependency in the package.json file.
|
||||
|
||||
Press ^C at any time to quit.
|
||||
name: (diskdump)
|
||||
version: (0.0.0) 0.1.0
|
||||
description: Convert disk images to/from JSON
|
||||
entry point: (index.js)
|
||||
test command:
|
||||
git repository:
|
||||
keywords:
|
||||
author: Jeff Parsons <Jeff@pcjs.org>
|
||||
license: (ISC)
|
||||
About to write to /Users/Jeff/Sites/pcjs/my_modules/diskdump/package.json:
|
||||
|
||||
{
|
||||
"name": "diskdump",
|
||||
"version": "0.1.0",
|
||||
"description": "Convert disk images to/from JSON",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jeff Parsons <Jeff@pcjs.org>",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
||||
Is this ok? (yes)
|
||||
|
||||
Next, I followed the steps outlined [here](http://www.anupshinde.com/posts/how-to-create-nodejs-npm-package/) to
|
||||
create a package skeleton with a CLI (command-line interface).
|
||||
|
||||
Filtering Web Server Requests
|
||||
---
|
||||
The next module I created was [HTMLOut](/my_modules/htmlout/), which provides a filter() function for the Express module,
|
||||
taking an early crack at the server's HTTP requests.
|
||||
|
||||
If the request is for a directory, **HTMLOut** builds an "index.html" from a common HTML template file, and then saves
|
||||
the file in that directory, so that future requests can be served by the Express static() function. The HTML template
|
||||
file can specify a variety of replacement tokens, including a reference to a "README.md" file in the same directory,
|
||||
which **HTMLOut** will load and pass to the private **MarkOut** Node module for conversion from Markdown to HTML.
|
||||
|
||||
Similarly, if the URL is an API request, **HTMLOut** will direct the request to the appropriate private Node module
|
||||
(eg, **DiskDump** or **FileDump**).
|
||||
|
||||
What's The Right Way To Incorporate Node Module Fixes?
|
||||
---
|
||||
My current version of Express was 3.4.8, and looking at its dependencies in package.json, it wanted "fresh" v0.2.0.
|
||||
|
||||
Unfortunately, there seemed to be a problem with Safari and that version of "fresh", occasionally resulting in
|
||||
blank pages, as discussed [here](http://stackoverflow.com/questions/18811286/nodejs-express-cache-and-304-status-code):
|
||||
|
||||
Safari sends Cache-Control: max-age=0 on reload. Express (or more specifically, Express's dependency, node-fresh)
|
||||
considers the cache stale when Cache-Control: no-cache headers are received, but it doesn't do the same for
|
||||
Cache-Control: max-age=0.
|
||||
|
||||
Further investigation revealed that [node-fresh](https://github.com/visionmedia/node-fresh) v0.2.1 added a fix for
|
||||
that problem. But what was the right (or best) way to incorporate that fix into my local Express installation?
|
||||
|
||||
I decided to hand-edit the "fresh" dependency in Express' package.json and then run "npm update" in the Express folder.
|
||||
That pulled in the newer "fresh" module. But I noticed that other modules that Express depends on include their OWN
|
||||
version of "fresh", which were also locked at v0.2.0. Even after creating an "npm-shrinkwrap.json" and hand-editing
|
||||
it, "npm update" refused to upgrade them from v0.2.0. I could edit the package.json files in each of the Express
|
||||
"connect" and "send" folders, run "npm update" in those folders, and force v0.2.1 that way, but as soon as I ran another
|
||||
"npm update" in the root, those folders reverted to v0.2.0 again.
|
||||
|
||||
However, this all turned out to be moot. The "Cache-Control: max-age=0" work-around added to "fresh" v0.2.1 was later
|
||||
backed-out in v0.2.2. And I discovered that the "blank page" problem went away at the same time I added trailing-slash
|
||||
redirects (see below), so there was likely a connection. The underlying issue with Safari is discussed in more detail
|
||||
[here](https://github.com/visionmedia/node-fresh/issues/8); it seems likely the problem wasn't related to "fresh" after all.
|
||||
|
||||
Problems With Directory URLs and Slashes
|
||||
---
|
||||
I discovered that I needed to install "express-slash", enable Express "strict routing", and make HTMLOut.filter()
|
||||
pass on directory requests that didn't include a trailing slash, so that the Node web server could call slash() and
|
||||
trigger a trailing-slash redirect. This is apparently what apache's "mod_dir" module does automatically, and why
|
||||
I never had a problem with apache. Without trailing slashes on directories, client-side requests for files relative to
|
||||
those "slash-less" directories can fail. More details on this issue can be found in [htmlout.js](htmlout/lib/htmlout.js).
|
||||
|
||||
npm install express-slash --save
|
||||
|
||||
Moving from Make to Grunt
|
||||
---
|
||||
Step 1:
|
||||
|
||||
sudo npm install grunt-cli -g
|
||||
|
||||
Step 2:
|
||||
|
||||
npm install grunt --save-dev
|
||||
|
||||
which automatically adds the following to my package.json:
|
||||
|
||||
"devDependencies": {
|
||||
"grunt": "^0.4.3"
|
||||
}
|
||||
|
||||
Step 3: Create a basic Gruntfile.js:
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.initConfig({
|
||||
});
|
||||
grunt.registerTask("default", []);
|
||||
};
|
||||
|
||||
Step 4: Install some grunt modules; eg:
|
||||
|
||||
npm install grunt-contrib-uglify --save-dev
|
||||
|
||||
Step 5: Add a task (eg, uglify):
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.initConfig({
|
||||
uglify: {
|
||||
dist: {
|
||||
src: "dist/myfile.js",
|
||||
dest: "dist/myfile.min.js"
|
||||
}
|
||||
}
|
||||
});
|
||||
grunt.loadNpmTasks("grunt-contrib-uglify");
|
||||
grunt.registerTask("default", ["uglify"]);
|
||||
};
|
||||
|
||||
However, while all of the above is a very nice example, I'm not really interested in uglify.
|
||||
|
||||
I use Google's Closure Compiler, so I needed to do this instead:
|
||||
|
||||
npm install grunt-closure-tools --save-dev
|
||||
|
||||
There was also an NPM module called "grunt-closure-compiler", but it hadn't been updated as recently, and from
|
||||
the examples it provided, it wasn't clear that it included support for all the Closure Compiler options, like
|
||||
"output_wrapper", so I went with "grunt-closure-tools", even though it also contains support for other tools that
|
||||
I'm not currently using.
|
||||
|
||||
So now my package.json includes:
|
||||
|
||||
"devDependencies": {
|
||||
"grunt": "^0.4.3",
|
||||
"grunt-closure-tools": "^0.9.4"
|
||||
}
|
||||
|
||||
Optional Node Modules
|
||||
===
|
||||
|
||||
replace
|
||||
---
|
||||
I use the Node "replace" module to perform project-wide search-and-replace operations on files; eg:
|
||||
|
||||
node node_modules/replace/bin/replace.js '(<machine.*class)="c1pjs"' '$1="c1p"' . -r --include=*.xml --preview
|
||||
node node_modules/replace/bin/replace.js '(<machine.*class)="pcjs"' '$1="pc"' . -r --include=*.xml --preview
|
||||
node node_modules/replace/bin/replace.js '(href="/versions/c1pjs)/[^/]*/(.*)' '$1/1.12.1/$2' . -r --include=*.xml --preview
|
||||
node node_modules/replace/bin/replace.js '(href="/versions/pcjs)/[^/]*/(.*)' '$1/1.12.1/$2' . -r --include=*.xml --preview
|
||||
|
||||
Most people probably install modules like this globally, but I'd rather just define a command-line alias
|
||||
("replace" => "node node_modules/replace/bin/replace.js") instead of cluttering my system-level directories.
|
||||
|
||||
Testing with Azure
|
||||
===
|
||||
> NOTE: I've since abandoned Azure in favor of AWS Elastic Beanstalk, because Amazon's service has a simpler UI,
|
||||
provides more control over the virtual machine environment (you can even log into the machine via SSH), and
|
||||
the logging services for Node on AWS are more reliable. I also wrote a short [blog post](/blog/2014/03/30/)
|
||||
on some of my frustrations with Azure.
|
||||
|
||||
I subscribed to Azure (formerly Windows Azure, now [Microsoft Azure](http://azure.microsoft.com/)), and started
|
||||
watching this YouTube video "[Lightning nodejs dev in Windows Azure](http://www.youtube.com/watch?v=WbtV1bX_m2I)"
|
||||
by Glenn Block ([@gblock](http://twitter.com/gblock)). I was glad to see that he used a Mac, because a number of
|
||||
other Azure videos on Youtube were made using Windows.
|
||||
|
||||
The first recommendation was to install the "azure-cli" command-line tools:
|
||||
|
||||
sudo npm install azure-cli -g
|
||||
|
||||
To display available commands:
|
||||
|
||||
azure
|
||||
|
||||
To connect the command-line tools to my Azure account (after setting my default browser to Chrome, since I used
|
||||
Chrome to set up my Azure account and browse the Azure portal):
|
||||
|
||||
azure account download
|
||||
|
||||
To import my acount info:
|
||||
|
||||
azure account import ~/Downloads/Pay-As-You-Go-3-30-2014-credentials.publishsettings
|
||||
|
||||
To create a site, the video recommends:
|
||||
|
||||
azure site create pcjs --git
|
||||
|
||||
However, I'm holding off until I understand how to link a new Azure site deployment with an existing GitHub repository,
|
||||
as discussed [here](http://www.windowsazure.com/en-us/documentation/articles/web-sites-publish-source-control/),
|
||||
because the above command appears intended for new project folders, which are then associated with a remote Azure-hosted
|
||||
Git repository -- not what I need or want.
|
||||
|
||||
The following command would show the remote Azure repository:
|
||||
|
||||
git remote -v
|
||||
|
||||
In my case, that command shows:
|
||||
|
||||
origin git@github.com:jeffpar/jsmachines.git (fetch)
|
||||
origin git@github.com:jeffpar/jsmachines.git (push)
|
||||
|
||||
The following command would then be used to push updates to Azure:
|
||||
|
||||
git push azure master
|
||||
|
||||
Although in my case, I'm guessing it would be:
|
||||
|
||||
git push origin master
|
||||
|
||||
Or, rather:
|
||||
|
||||
git push origin node_dev
|
||||
|
||||
Since my local "pcjs" project is based on the **node_dev** branch.
|
||||
|
||||
If I was using an Azure-hosted Git repository, I could then run:
|
||||
|
||||
azure site browse
|
||||
|
||||
because the `git push azure master` command would have triggered a website update.
|
||||
|
||||
The video goes on to show how I can specify the version Node I want Azure to use, by adding the
|
||||
following to my "package.json":
|
||||
|
||||
"engines": {
|
||||
"node": "0.8.x"
|
||||
}
|
||||
|
||||
This command:
|
||||
|
||||
azure site deployment list
|
||||
|
||||
will list all the deployments so far, any one of which can be redeployed like so:
|
||||
|
||||
azure site deployment redeploy xxx
|
||||
|
||||
The video finally gets that what I *really* care about, which is deploying via GitHub. This command
|
||||
lists some of the commands we care about:
|
||||
|
||||
azure site create --help
|
||||
|
||||
To create a site from a repository on GitHub:
|
||||
|
||||
azure site create pcjs --github
|
||||
|
||||
JavaScript Coding Conventions
|
||||
===
|
||||
|
||||
This is not intended to be an exhaustive list. It just lists a few things that came to mind, and I'll
|
||||
probably update it over time.
|
||||
|
||||
Tabs
|
||||
---
|
||||
I've configured my IDE (PhpStorm) to NEVER use tab characters in .js files (spaces only) and to
|
||||
ALWAYS use tab characters in almost every other type of text file. This is largely because when a web
|
||||
browser displays a JavaScript file (either in the main window or in the Developer Tools window), tabs
|
||||
usually screw up the formatting, which I find annoying when I'm debugging. XML files, on the other hand,
|
||||
are usually reformatted by the browser, so in those cases, I opt for smaller files and use real tabs.
|
||||
|
||||
Moreover, most of the JavaScript delivered by a production server will have been pre-compiled by Google's
|
||||
Closure Compiler, which completely eliminates all non-essential whitespace, so this is really a development
|
||||
preference, with little to no impact on production files.
|
||||
|
||||
Regardless of the choice of tab character however, I almost always use 4-column tab stops, except
|
||||
in legacy .asm files, where 8-column tab stops were the norm. I know that 2-column tab stops have
|
||||
recently become "all the rage," especially in Node projects; NPM, for example, will rewrite package.json
|
||||
files, replacing all the 4-column tabs with 2-column tabs, and I don't fight that trend -- I just ignore it.
|
||||
|
||||
JSDoc
|
||||
---
|
||||
I've taken a great deal of care to "[JSDoc](http://usejsdoc.org/)-ify" nearly all my JavaScript code, not
|
||||
because I want to be able to generate documentation (although that's something to think about), but because
|
||||
it's the only way to tell both Google's Closure Compiler and my IDE (PhpStorm) exactly what data
|
||||
types are passed into/out of every function. The goals are to minimize the number of "code inspection"
|
||||
warnings in the IDE and produce warning-free compilations.
|
||||
|
||||
In order to use the Closure Compiler's ADVANCED_OPTIMIZATIONS option and get maximum performance
|
||||
(and maximum "minification", sometimes called "uglification"), every function and its parameters needs to
|
||||
be fully typed; otherwise, the Compiler will generate WAY too many warnings/errors -- at least, that was the
|
||||
case when I first started using it a couple of years ago.
|
||||
|
||||
So, I've adopted a zero-tolerance policy for warnings: no check-ins allowed if the Closure Compiler generates
|
||||
even a SINGLE warning.
|
||||
|
||||
I'm not sure the [JSDoc](http://usejsdoc.org/) folks and the
|
||||
[Closure Compiler](https://developers.google.com/closure/compiler/docs/js-for-compiler) are totally in sync on
|
||||
everything. And then there's [PhpStorm](http://www.jetbrains.com/phpstorm/webhelp/creating-jsdoc-comments.html),
|
||||
whose code inspections occasionally fail; sometimes a bogus code inspection warning can be fixed with some
|
||||
additional JSDoc @name or @class annotations, but not always.
|
||||
|
||||
In any case, the subset of variable and function type declarations I use works pretty well across the board;
|
||||
code inspection warnings are allowed in the IDE, as long as they are clearly erroneous (or clearly innocuous).
|
||||
|
||||
Braces, Parentheses, etc.
|
||||
---
|
||||
Most of my opening braces appear at the end of the line containing the associated "if", "while", "for", "switch",
|
||||
"function" etc, preceded by a single space. And most of my opening parentheses are also preceded by a single space,
|
||||
except when following "function" or a function name, in which case there is NO space. This is just an historical
|
||||
preference, dating back to my BASIC and C programming days; it's not that those preferences matter anymore, it's just
|
||||
that I see no reason to change them.
|
||||
|
||||
I'm not obsessed with these conventions. Sometimes, for example, I'll write a bunch of code where the opening
|
||||
brace of every function body begins underneath the "function" keyword, because the extra whitespace can make the
|
||||
file a bit more readable. It may sometimes depend on my mood, but I do try to be consistent within a given file
|
||||
at least.
|
||||
|
||||
Variable Names
|
||||
---
|
||||
I still tend to follow Charles Simonyi's "[Hungarian](http://en.wikipedia.org/wiki/Hungarian_notation)" naming
|
||||
conventions -- or rather, a naming convention loosely inspired by Hungarian. I know lots of people sneer at
|
||||
those conventions and think they're useless, and all I can say is, they're wrong: they are not useless to ME.
|
||||
I admit they may be useless to anyone who has a phenomenal memory and can remember that an obscure variable named
|
||||
"foo" was initialized with a string or a number, or who has an IDE that can answer that question with the press
|
||||
of a key (or two), but for me, with my non-phenomenal memory and lazy fingers, I prefer being able to simply
|
||||
look at a variable to immediately know what *type* of data it contains, if nothing else.
|
||||
|
||||
I rarely name a string or numeric variable something vague like "foo." At worst, I would name it "sFoo" if it
|
||||
was a string or "iFoo" if it was a number (or possibly "nFoo" or "cFoo" if it represented a total of Foos or a
|
||||
counter of Foos). And if a string or numeric variable has a very short-term use, I often just call it "s" or "i"
|
||||
(or "n").
|
||||
|
||||
As I mention [below](./#quotation-marks), I still tend to distinguish single characters from strings too,
|
||||
which means I may sometimes prefix character variables with "ch" and character counters with "cch".
|
||||
|
||||
Of course, these letter prefixes like "s" and "n" are irrelevant if you already give your variables meaningful
|
||||
names like "nameOfPerson" or "numberOfPeople". And that's fine -- I do that sometimes, too. But in general,
|
||||
I still prefer variable names like "sPerson" and "nPeople".
|
||||
|
||||
I don't try to come up with special prefixes for Objects. If there's a Person object, for example, I'll probably
|
||||
use colloquial names like "personHere" or "personThere". I am stricter about Arrays though: I prefix array variables
|
||||
with "a", arrays of strings and numbers with "as" and "ai" (or "an"), and arrays of arrays with "aa". As for Arrays
|
||||
of anything else, I usually don't bother with anything more than an "a" prefix.
|
||||
|
||||
Quotation Marks
|
||||
---
|
||||
Coming from a long C background, I prefer to use double-quotes around multi-character strings and single quotes
|
||||
around single-character strings. While the reasons for doing so are largely historical and currently irrelevant,
|
||||
characters are STILL the building blocks of strings, and even the JavaScript String class contains methods that
|
||||
deal with individual characters (eg, charCodeAt() and fromCharCode()). So for any code that knowingly deals with
|
||||
single characters, I like to reinforce that distinction with single quotes.
|
||||
|
||||
Also, to emphasize that an object property name isn't really a string, I tend to use single quotes around property
|
||||
names as well. I realize that the JSON standard insists on property names with double-quotes, but functions like
|
||||
JSON.stringify() take care of that for us -- besides, I find that aspect of the JSON standard to be rather annoying
|
||||
(not to mention JSON's disapproval of comments).
|
||||
|
||||
Obviously, it's possible to set and get object properties without any quotes at all, as in:
|
||||
|
||||
obj.prop = true;
|
||||
|
||||
instead of:
|
||||
|
||||
obj['prop'] = true;
|
||||
|
||||
but that works only if the property name conforms to the same syntax that variable names use. Also, the "dot"
|
||||
syntax can create problems for code compiled with Google's Closure Compiler (using ADVANCED_OPTIMIZATIONS),
|
||||
because it likes to rename "dot" property names to smaller "minified" property names. Which means if you
|
||||
need to export your objects as JSON later, or if you ever need to access a property using an externally defined
|
||||
string, you must avoid the "dot" syntax.
|
||||
|
||||
However, if an object and all its properties are purely internal, there's usually no reason not to use it:
|
||||
|
||||
obj = {foo: "old", modified: false};
|
||||
obj.foo = "new";
|
||||
obj.modified = true;
|
||||
|
||||
I break my own quoting rules slightly when dealing with strings that *contain* double-quotes, since it's more readable
|
||||
to put double-quotes inside single-quoted strings than to "escape" every double-quote with a backslash.
|
||||
|
||||
For code that was originally written in PHP and later ported to JavaScript, there was a tendency in the original
|
||||
code to always use double-quotes around strings and "escape" double-quotes regardless, and that tendency may linger
|
||||
in code I didn't feel like rewriting much, but the tendency was due more to idiosyncrasies of PHP than any convention
|
||||
of mine; for example:
|
||||
|
||||
- single-quoted PHP strings may not include any escaped characters (except for single-quote and backslash)
|
||||
- single-quoted PHP strings cannot resolve references to string variables (eg, "the value of foo is {$foo}")
|
||||
|
||||
Because of PHP's restrictions on single-quoted strings, I tended to avoid them. However, in JavaScript, the
|
||||
first restriction isn't true, and the second isn't even a feature of the language, so they have no bearing on the use
|
||||
of single-quoted strings in JavaScript.
|
||||
|
||||
Arrays
|
||||
---
|
||||
In [this article](http://stackoverflow.com/questions/8668174/indexof-method-in-an-object-array), it's asserted,
|
||||
without disagreement, that searching arrays of objects for a particular object is best performed with code that looks
|
||||
like this:
|
||||
|
||||
pos = myArray.map(function(e) { return e.hello; }).indexOf('stevie');
|
||||
|
||||
I disagree. Depending on how often that function is called, creating a new array by iterating over every element
|
||||
of the original array, and doing that every time you want to search for an array object with a property (eg, `hello`)
|
||||
equal to some value (eg, 'stevie'), is a bad idea; I don't care how fast your JavaScript engine is or how fantastic
|
||||
your garbage collector is.
|
||||
|
||||
My preferred approach is to create parallel arrays: one array for each property that you might want to search on, and
|
||||
another array of objects that contain all the other object properties. Then searching doesn't involve creating new
|
||||
arrays:
|
||||
|
||||
pos = myArrayOfHello.indexOf('stevie');
|
||||
|
||||
Yes, it's not quite as *clean* as one, unified array of objects, and yes, it would be nicer if the JavaScript Array
|
||||
class had better search methods, but I think this is the right trade-off given the current reality.
|
||||
Loading…
Reference in a new issue