7/20/2015
OpenSourceJavascriptDevelopment
OpenSourceJavascriptDevelopment
25thJanuary2014
[Link],butonethingthatsbeen
[Link]
when I got a chance to write a small standalone open source Javascript utility
[[Link]
You
can
read
all
about
the
utility,
Instant
Hangouts, in my previous post
[[Link] .Briefly,ifyouwantyourusersto
beabletotalktoeachotherfacetofaceaboutawebpage,youcaninserttwolinesofHTMLandgeta
Google+ Hangout specific to that page. The project is very simple its just a shim around the existing
Google+HangoutButtonAPIbutitgavemeachancetogetbackuptodateonJavascripttooling.
[Link],itsanaccountofwhatIfound
thatIwroteontheoffchancethatitmightsaveyousometimeifyourenewtoJavascripttoolchains.
Requirements
Ineededmytoolchaintogivemesixthings:
1. Revision control. This frees you to mess up. Messing up is the hearts blood of software
development.
2. [Link],Iwantedpinnedversionsofall
[Link],[Link]
will behave, so you spend all your time dealing with integration problems rather than shipping your
[Link].
3. [Link]
the requesting server, so viewing my code locally in my browser via file:// was out. Also, my
userswillbeusingrealservers,andIwantmydevenvironmenttobeasclosetotheirsaspossibleso
Icanminimizethechancemycodeworksformebutnotthem.
4. [Link].
5. Codeisolationatruntime,[Link]
able to inspect your code for debugging. But at runtime you cant expose all your symbols because
Javascript doesnt have namespaces and collisions will break your code (and everyone elses) in
entertainingandnovelways.
6. [Link],soIliketohave
tests from the start. Even if theyre not perfectly comprehensive, tests give you the confidence you
needtodealwithcodechurnovertime,andtheyforceyoutouseyourownAPIsinwaysthatimprove
designearlyinyourproductslifecyclewhenchangeischeap.
My naive expectation was that these would all be solved problems. And they were, more or less. The
[Link]
needtocobblethesesolutionstogetherintoatoolchain,andeachtimeyouaddanewutilitythespaceof
whatcangowrongexpandsgeometrically.
Icallthistheprojectorproblem,[Link],
as everybody does. And at those meetings, someone invariably wanted to present something on their
[Link],andeveryoneknew
how to use the projector, but the minute they plugged the laptop into the projector things fell apart in
[Link]
1/7
7/20/2015
OpenSourceJavascriptDevelopment
[Link]
theprojector,[Link]
wrongresolution?Orthecableisloose?Oryoudidnthittherightbuttoninyourpresentationsoftware?
Maybe.
Theproblemisthatthetotalcomplexityofanyintegrationistheproductofthecomplexityoftheindividual
parts. This scales up faster than the expertise of whoever is dealing with the integration, so it was no
surprise that I ended up burning more time on my toolchain than on writing my small utility. Heres how
thingsendedup.
Revisioncontrol
Solvedwithgit[[Link] andGitHub[[Link] .gitisadistributedversioncontrolsystem
thatsbasicallybecomethedefactostandardfordevelopmentintheopensourceworldandinsidemany
[Link],andisenoughofastandardthataprogrammersGitHubprofile
[Link]
[Link].
There are lots of ways to install git on your system [[Link] .
Picktheonethatsrightforyou.
Dependencymanagement
Solved with [Link] [[Link] and npm [[Link] , which come together. [Link] is a
[Link],you
write a [Link] [[Link] file that
describes your project. Its just a JSON object literal. Its dependencies field lets you specify your
runtimedependencies,anditsdevDependenciesfieldletsyouspecifyyourdevelopmentdependencies.
[Link][[Link] .Picktheone
thatsrightforyou.
Oncethatsdone,youcangrabInstantHangoutswith
$gitclone[Link]
andinstalldependencieswith
$npminstall
Asidebaraboutruntimedependencies:
Youvegottwochoices:packageyourdependenciesinyourproject,[Link]
[Link]
[Link],theyreset.
[Link]
2/7
7/20/2015
OpenSourceJavascriptDevelopment
The latter gives better performance. Browsers cache the scripts they load. If two projects both load a
commonscript(say,jQuery [[Link] )itsreallywastefulforboththoseprojectstoforceusersto
[Link],itsbetterforeachofthoseprojectstoloadthe
commonscriptfromastandardlocationsobrowsercachingwillkickinonallrequestsforitpastthefirst.
Nowthatcontentdeliverynetworks [[Link] (CDNs)arecommon,
its good to load your runtime dependencies from them whenever you can. Thats what we do with our
dependencyontheGoogleHangoutButtonAPI.
Apartfromthatlibrary,[Link]
few DOM manipulation and functional programming helpers so we can avoid loading in jQuery or
[Link] [[Link] . While the world really doesnt need another Javascript
implementation of filter(), a few hours researching Javascript module loaders like [Link]
[[Link] convincedmethatthesesmallrepetitionswerecheapertowriteandfastertoexecute
thanbringinginhelperlibrariesforatinyhandfulofcallsites.
[Link]
semantic versioning [[Link] . Developers can install these dependencies
[Link],onceadeveloper
[Link].
Developmentserver
Forsimplescripts,loadingofflocaldiskviaabrowserthatsupports[Link]
[Link],so
weneedalocaldevelopmentserver.
[Link],sowealsouseitforourdevelopmentserverto
[Link]
and server implementations in the same language, which likewise decreases complexity of the project
overall.
Ourserverlivesinscripts/[Link] [[Link]
,andyoucanlaunchitwith
$nodescripts/[Link]
It serves on port 8080. For Instant Hangouts youll want to use your hostname rather than localhost
[Link],youcanloadthedev
serverfrommultiplemachinesanddoamanualendtoendtestbyjoiningtheresultingHangoutsfromtwo
differentGoogleaccounts.
Theserverimplementationissimple:
[Link]
3/7
7/20/2015
OpenSourceJavascriptDevelopment
//Loaddependencies:
//[Link].
//pathisforlocalfilesystemoperations.
varexpress=require('express')
varpath=require('path')
varapp=express()
//LogrequeststoSTDOUT.
[Link]([Link]())
//Bindrequestsfor*.jstofilesintheprojectroot.
//ThisservesourcompiledJS,notthefilesinsrc/.
[Link](/.js$/,function(request,response){
[Link]([Link]([Link](1)))
})
//[Link].
//[Link].
//ThisletsussetanypathcomponentwelikeintheURL,
//allowingustotestrooms(whichusetheURLintheirkey).
[Link]('*',function(request,response){
[Link]([Link]('[Link]'))
})
//Bindtheservertoport8080.
[Link](8080)
DuringdevelopmentIlaunchtheserverinaterminaltabsoIcanwatchthelogsgoby.
Automaticversioning
Idontexpectthisprojecttohavelotsofchurn,butitsalwaysimportanttomakesurethatyouhavean
[Link],thatmeansmakingsureusersloadanexplicit
versionoftheInstantHangoutsscript.
We use [Link] [[Link] to set the
version, then read this value when compiling the scripts our users load. These compiled files live in the
projectrootandarestrictlyadditive:everytimetheresanewversion,[Link]
[Link]
fromaCDNandpeoplecopyandpastespecificURLsintotheirHTML.
Codeisolationatruntime,andvisibilityattesttime
[Link]
youre restricted to blackbox integration tests. Since Javascript has no namespaces, though, you dont
wanttostickallofyoursymbolsonwindowbecausetheycouldcollidewithcodethatexecutesbeforeor
[Link].
The
usual
technique
for
solving
this
the Javascript module pattern
[[Link] . This is actually a family of patterns
[Link],wecanuse
thesimplestform,whichiswrappingallourcodeinananonymousclosure:
[Link]
problem
is
4/7
7/20/2015
OpenSourceJavascriptDevelopment
(function(){
//Ourcodehere.
}())
Sinceallourcodelivesinsidethefunctionsscope,noneofoursymbolsarevisibleoutsidetheclosure.
Notethelastline:wereinvokingthefunction,soallourcoderunswhenthefileisloaded.
[Link](waitforit!)it
encapsulates our code. Without exporting symbols we cant directly inspect the state or invoke the
[Link]?
Our solution is to have slightly different configurations at test time and in production. This is somewhat
heretical since any skew between test and prod keeps you from accurately testing how your code will
behave in the real world. So its important to minimize the skew and have a solid understanding of the
behaviorsitcandistort.
Inoursource files [[Link] , we dont
[Link],allthetop
[Link]
our tests as if they were defined in the test source files [[Link]
hangouts/blob/master/test/[Link]] themselves. If we had multiple source files and needed
encapsulation within our project to manage its internal complexity, this approach would not be sufficient.
Foraprojectthissimple,itworkswellenough.
At runtime we dont load the source files we load processed [[Link]
hangouts/blob/master/[Link]]
versions
[[Link]
hangouts/blob/master/[Link]] [Link]
processedfiles,wewrapourcodeinaclosuretoencapsulateit.
WeuseGrunt[[Link] ,aJavascriptautomatictaskrunner,togeneratetheseprocessedfilesin
[Link]:
instanthangouts<version>.js
instanthangouts<version>.[Link]
Grunt is configured by [Link] [[Link] .
Duringdevelopment,youinvokeitvia
$grunt
from the project root, assuming you have installed it globally. Otherwise, you can invoke it from
./node_modules/gruntcli/bin/grunt.
[Link]
[Link],thatconditionisachangeto
thecontentsofsrc/,test/,[Link].
In
the
filenames
above,
<version>
is
the
version
string
from
[Link]
[[Link]
src/*.js,[Link],andis
[Link],decreasinglatency
inouruserspages.
[Link]
5/7
7/20/2015
OpenSourceJavascriptDevelopment
We want our users to load the minified version by default, which is why the unminified version has the
longer, more obscure name. During development, this unminified version is very helpful because it is
human readable and has line numbers you can refer to in your debugger of choice. By default, the
unminifiedversionisloadedbythedevelopmentserverviathe<script>[Link]. Because
Gruntregeneratesthecompiledfilesonsave,thismeansyoucanmakeachange,savethefile,andsee
[Link]
itself in scripts/[Link] [[Link] require a
serverrestart.
Automatictesting
We also want our tests to run automatically whenever the contents of src/, test/, or [Link]
change. We could use Grunt for this, but we can get some really useful features from a dedicated test
runner.
WeuseKarma [[Link] .ThemainbenefitKarmaprovidesoverGruntis
[Link]
attach browsers to its test server [[Link] . Whenever your
testsareexecuted,theyareruninsideeachattachedbrowser,andtheresultsaresenttoyourterminal.
[Link] [[Link]
. In our configuration, we launch and attach Chrome locally. While developing, I run Karma in its own
terminalwith
$karmastart
EverytimeIchangeoneofthewatchedprojectfiles,Igetresultsinmyterminalprettymuchinstantly.
WewriteourtestsusingtheJasmine [[Link] test framework. Karma executes the
[Link]
importantlimitations:werenotsimulatingusersclickingontheHangoutcontrolsandstartingupaHangout.
[Link]
Googleaccounttotestthingsendtoend,andthatmeanswedbetestingGooglescodeinadditiontoour
[Link]:itsalotofwork,anditsofquestionablevaluebecausethatcodeisnt
ourresponsibilityorunderourcontrol.
Instead,webasicallytreatthecontrolslikeablackbox:wetesttomakesuretheyreboxshapedandthe
[Link],forexample,thatwerecreatingthe<script>tagthatloadsthe
GoogleHangoutButtonAPIcorrectly,andthatwereinsertingourrendertargetscorrectly,andthatwere
[Link],
[Link][[Link] fileourdev
serverservesasatestfixture(meaningitgivesustheDOMformanyofourtests).
Conclusion
[Link]
abunchofreallygoodtoolsthatcanmakeourlivesaloteasier,butitalsomeansthatfindingtherighttools
andgettingthemtoworktogethercanbeabitofachallengebecausetheressuchaprofusionofchoice.I
spentabout50%moretimeassemblingmytoolchaincomparedtoactuallywritingprojectcode,whichisnt
[Link],though,[Link]
saveyousometime,too.
Posted25thJanuary2014byjohnmcox
[Link]
6/7
7/20/2015
OpenSourceJavascriptDevelopment
0 Addacomment
Enteryourcomment...
Commentas:
Publish
GoogleAccount
Preview
[Link]
7/7