• Writing Modern SPA in JavaScript: Package Management by Ast

    Why yet another one tutorial?

    A single-page application (SPA) is a web application or web site that fits on a single web page with the goal of providing a more fluid user experience similar to a desktop application.

    I've been thinking of a complete tutorial on writing SPA in modern JavaScript for a long time. «There are a lot of such lessons» — you can say and be 100% right. But most of them have a very confusing part for newcomers: the horrific amount of buzzwords! The days when you were able to write the simplest «Hello World» program in just a few lines of code are long gone. Now you should know good enough answers to many important questions before you could even start coding:

    • What language should I use?
      JavaScript (es3, es5, es6)? TypeScript? CoffeeScript? Whatever-Else-Script?
    • How I should manage my dependencies?
      Can I just copy them to the project? Or should I use git submodules? Maybe I could make a use of some package manager (npm, bower)?
    • How should I assemble my code for frontend?
      Isn't the old good «just concatenate them all» the best one? Or should I use browserify? A little bit of webpack?
    • How should I use styles?
      Can I just add a style tag with an external css to my HTML file? Or should I use something more complex like BEM? Or can I just inline it?
    • What framework should I use for my application base?
      Should it be vanilla JavaScript? Or jQuery? Backbone? Angular? Maybe Angular 2 (two is bigger than one — therefore it should be better, right?), React?
    • How should I manage the data in my application?
      A lot of buzzwords here. Flow? Redux? Relay? MobX? Can't I just do it the old good way (like my father and his father did)?
    • Can't I just shoot myself in the head instead of diving into this mess?

    You can't write modern JavaScript and ignore those questions. So I'll try to solve this buzzword-hell-puzzle by starting a new project from scratch. We will be writing a new application with a small steps and every single step will be introducing one thing at a time.

    You should understand: sometimes you can definitely say that the tool named A is better than the tool named B. But sometimes you should also choose from tools with comparable awesomeness — just take the most beautiful one (as you see it now).

    Let's install a Package Manager

    The first thing you should master is a packet manager. Times when it was a good idea to install and manage dependencies manually are gone too. Now you can use a packet manager to install your application dependencies and solve conflicts among them in one simple command. The clear winner among JavaScript package managers is npm. It's a JavaScript program itself and it runs under nodejs interpreter environment. So we'll need them both to start: nodejs and npm.

    To install nodejs visit it's official website for instructions for your platform. The good news: you'll receive an installed npm package after nodejs installation by default. And the bad news: it could be a little bit outdated.

    The main reason we should prefer the modern version of npm is the way how it stores your dependencies. Older versions use a huge hierarchical set of directories: it can cause problems on windows filesystems and also is not very graphical. The third version of npm uses flat directory structure where it's applicable. So let`s install it!

    npm install -g npm@3
    

    Hint!
    You can use the following short alternative syntax command (if you are some kind of a Jedi):

    npm i -g npm@3
    

    The argument «g» means that we want this package installed globally. Otherwise it will be stored in ./node_modules subdirectory and will be usable only in a single current project. The package itself is named «npm» and «@3» is an optional postfix stating that we want the third version of npm and nothing less.

    Let's ensure we have now the desired npm version installed!

    npm -v
    3.10.5
    

    Start a New Project

    We have a working package manager now. I can't wait any longer — let's start a new project! First of all, let's create a new directory to contain it.

    mkdir todo
    cd todo
    

    The next command will launch a simple text wizard. Answer all the questions as you wish (you can leave default values — it doesn't matter now) and you'll see a new file named package.json in your project directory. It will contain basic meta-information about your application.

    npm init
    

    It will have a structure just like this:

    {
     "name": "todo",
     "version": "1.0.0",
     "description": "",
     "main": "index.js",
     "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
     },
     "author": "",
     "license": "ISC"
    }
    

    Let's clean it a little bit and remove all unnecessary lines to make things look simpler. Note that you have no need to run «npm init» if you don't want — you can write package.json manually, it's not rocket science.

    {
     "name": "todo",
     "version": "1.0.0"
    }
    

    Nice! We can finish coding config and start coding the actual code right now. It will not be a complete todo application for now, but hey, Rome wasn't built in a day. Let's create an HTML file as entry point and a simple one line script:

    <!-- index.html --> 
    <html>
     <body>
     <h1>I'll be a great app one day</h1>
     <div id="app">Not alive yet...</div>
     <script src="./src/index.js"></script>
     </body>
    </html>
    
    // src/index.js
    document.getElementById('app').innerHTML = 'Alive!';
    

    Not very impressive yet, so let's go further and add a function (old plain es4-like one) to make things look more like an application:

    // src/index.js
    function getSalutation(name) {
     return 'Hi, ' + name + '!';
    }
    document.getElementById('app').innerHTML = getSalutation('user');
    

     

    Install First Dependency

    But why do we depend on getSalutation function parameter validity? We always want to greet user with respect and should capitalize his name in any case. Let's use a separate module named string.js to do this. First of all we need to install it:

    npm install --save string
    

    And a Jedi version:

    npm i -S string
    

    The «save» argument denotes that we want not only to install this package to project directory but also to store it like a project dependency in package.json. If we want to be able to deploy our app somewhere else we need to know what dependencies it has — just to be able to reproduce certain conditions in which the app is meant to run or be built. Let's look at what had happened to project after this command:

    A new package named «string» is installed locally under node_modules directory:

    ls node_modules
    string
    

    The «string» package also had been added to package.json config:

    {
     "name": "lesson",
     "version": "1.0.0",
     "dependencies": {
     "string": "^3.3.1"
     }
    }
    

    As we didn't specify any concrete version of this package, npm fetched the last one and saved this version as minimal satisfiable. The «^3.3.1» syntax means that npm could give us version greater or equal to denoted. It is called «semver» and you really should read a few great articles about it:

    Now let's use it in out application:

    // src/index.js
    function getSalutation(name) {
     return 'Hi, ' + S(name).capitalize() + '!';
    }
    document.getElementById('app').innerHTML = getSalutation('user');
    
    <!-- index.html --> 
    <html>
     <body>
     <h1>I'll be a great app one day</h1>
     <div id="app">Not alive yet...</div>
     <script src="./node_modules/string/dist/string.js"></script>
     <script src="./src/index.js"></script>
     </body>
    </html>
    

     

    Please keep in mind that the path to third party module and its API vary depending on package developer decisions. With the same probability the string.js variable could be named «StringJS», «s», or any other way. We'll talk about how to deal with it later.

    Freeze dependency versions

    I bet you already have a thought about compatibility. What will happen to our app if the string.js developer will make some changes in their API? We had tested our project only with this concrete version of string.js and don't want any other.

    The simplest solution could be to change the required version from «^3.3.1» to exactly «3.3.1». But that will solve only the problem with string.js package itself. What if string.js has it's own dependencies? We need to fix their versions too, but we can't write it in package.json — we have no such dependencies there. Shrinkwrap to the rescue!

    npm shrinkwrap
    

    This command simply scans through our ./node_modules subdirectory and writes all found package versions to a separate file named npm-shrinkwrap.json. The npm install command respects versions specified in this file and will use them instead of the last ones.

    Conclusion

    Our journey is just beginning. The most interesting parts are in the future. You already know how to use the package manager to satisfy your dependencies in automatic mode. And the next article will cover the usage of modern JavaScript dialects and will guide you through the setup of assemble process. Stay online!

comments powered by Disqus