Some subtleties of working with Github and NPM - with ES6 flavor

  • Tutorial
Hello, my name is Alexander, and I write bicycles on weekends by a programmer.



In our club of anonymous bicycle builders, it is considered special chic not only to create another masterpiece, but also to share it with the community. Since there are just a huge number of articles on how to put a project on Github or npm, I will not retell the same thing 100,500 times.

In today's article, I want to highlight some unobvious subtleties that may help you get more pleasure from the process of artistic sawing of the next bike with a jigsaw.

Warning # 1: I never consider myself the ultimate truth, and all of the following is only my private (possibly erroneous) opinion. If you know how best - please in the comments. Together, bikes are more fun, and truth is easier to establish.

Warning # 2 : I will assume that the reader is familiar with the command line, Git, and npm.

This season, it became especially fashionable to write on ECMAScript 6, which, I recall, on February 20 reached release candidate status , so let's mentally assume that we will sprinkle our imperishable on it.

We assume that we have already created a new folder, and launched a command in it npm initand, thus, created a file package.json.

We will consider the structure of the project.


In general, ES.next support that in browsers that node.js / io.js is still incomplete, I would even say fragmented. So we have two options: either use not all the features of ES.next, but only those that are supported by the target platform, or use the transcompiler ( aside: no, but how else to translate transpiler ?! ).

Of course, we, as enthusiasts, want to use the latest features of ES6 / 7, so we will use the second option.

Of all the transcompilers, the largest number of features is supported by Babel (formerly 6to5), here is the proof , so we will use it.

Since we use a transcompiler, the basic structure of the project is already defined.

In foldersrcwe will store the source code on beautiful ES6 (and show it on GitHub), and the folder libwill contain ugly generated code.

Accordingly, in Git we will store the folder src, and in npm we will post it lib. We will achieve the desired effect through the use of magic files, .gitignoreand .npmignore. In the first, respectively, we will add a folder lib, and in the second folder src.

Now, finally, add Babel to the project:

npm i -D babel


And teach npm to compile our sources. We get into the file package.jsonand add the following:

    {
        /* Неважно */
        "scripts": {
            "compile": "babel --experimental --optional runtime -d lib/ src/",
            "prepublish": "npm run compile"
        }
        /* Тоже неважно */
    }

Who is standing on who’s going on here?

The first script that is launched by the command npm run compiletakes our sources from the folder src, converts it to the good old JS, and puts the lib in the folder . Keeping the structure of subfolders and file names.

Important : Please note that despite the fact that Babel is installed locally in the project, and is not added to my place $PATH, npm is smart enough to understand that in fact I ask him to do the following:


    node ./node_modules/babel/bin/babel/index.js --experimental --optional runtime -d lib/ src/

I articulate once again: no, no need to install global packages. Install packages only locally, as project dependencies, and call them through npm run [script-name].

Even more important : I ask you to pay attention to two flags: --experimentalwhich includes support for ES7 features (such as syntax async/await), and the second, which is worth talking about in more detail.

Babel itself is a translator from ES6 to ES5. Whatever he may not do, he does not. So, he is not worried about some features that can be easily entered into ES5 using polyfills. For example, support for Promise, Map, and Set may well be organized at the Polyfill level.

Using the second flag, Babel adds a requiremodule command to the generated code babel/runtime, which, unlikebabel/polyfilldoes not pollute the global namespace. A little more about the features of the work babel/runtimecan be found on the official website , as well as in the comments of the respected rock .

If you are writing a project under Node.js / Browserify / Webpack, then you just need to add the project dependencies babel/runtime. Something like this:

    npm i -S babel-runtime

If your non-filthy one works in the browser, and you use not CommonJS, but AMD, then you need to remove this flag from the compilation team, and in the way that is convenient for you, add babel-polyfill.js to the project .

The second script is launched by itself npmwhen the package is published, and thus, the folder libwill always contain the freshest.

Finally, let's move on to writing code.


Let’s finally attach our grabbing pens to writing the coveted ES.next code. Create a srcfile in the folder person.es6.js. Why [basename].es6.js? Because on Github, ES6 / 7 syntax highlighting is turned on if the file is named according to the scheme [basename].es6or [basename].es6.js. Personally, I like the last option more, so I use it.

So the code ./src/person.es6.js:

    export default class Person {
        constructor(name) {
            if (name.indexOf(' ') !== -1) {
                [this.firstName, this.surName] = name.split(' ');
            } else {
                this.firstName = name;
                this.surName = '';
            }
        }
        get fullName() {
            return `${this.firstName} ${this.surName}`;
        }
        set fullName(fullName) {
            [this.firstName, this.surName] = fullName.split(' ');
        }
    }

Let's pretend that this heterogeneous class is the goal for which we bother with ES.next. Let's make it the main one in package.json:

    {
        /* неважно */
        "main": "lib/person.es6.js"
        /* неважно */
    }

Please note that the directive maindoes not indicate the original code at the address ./src/person.es6.js, but its reflection generated using Babel. Thus, consumers of our library who do not use ES.next and Babel in their project will be able to work with our package as if it were written in regular ES5.

In general, the scheme is quite old and familiar to CoffeeScript fans, as well as those who wrote JS in one of the ~ 370 (o_O) ways that 200+ (o_O) languages ​​are transcompiled into JavaScript.

Testing


Before we get into the discussion of testing our project, let's discuss the following question: should I write tests on ES6, or still on the good old ES5? And for that, and for another option, you can bring a lot of arguments. Personally, I think that everything is simple: if we plan to use our package in another project written in ES6, then tests should be written in ES6. If we put the finished product into the npm ecosystem, then it should be able to interact with ES5, so it will be quite useful if the code generated using Babel is tested.

For the sake of complexity, I suggest that we write a utility for the outside world that still does not know anything about ES6, and thus we will write tests on the good old ES5.

So, let's create a folder for tests (as a rule, I put everything in [корень проекта]/test/**/*-test.js, but I don’t insist on anything, do as you like). Usually I use a bunch mocha + chai + sinon + sinon-chaifor testing, but nothing prevents you from using, I don’t know, wallaby.js , especially since the latter fully supports ES6.

In general, I personally do like this:

    npm i -D mocha sinon chai sinon-chai

And add a new script to package.json:

    {
        /* неважно */
        "scripts": {
            /* неважно */
            "test": "mocha --require test/babelhook --reporter spec --compilers es6.js:babel/register"
            /* неважно */
        }
        /* неважно */
    }


Oddly enough, this is the only option that I have earned with mocha and, looking ahead, with istanbool.

So, npm testwith the help of Babel , it transcompiles files with the extension *.es6.jsand before each test makes a requirefile ./test/babelhook.js. Here are the contents of this file:

    // This file is required in mocha.opts
    // The only purpose of this file is to ensure
    // the babel transpiler is activated prior to any
    // test code, and using the same babel options
    require("babel/register")({
        experimental: true
    });


Dismissed from the official Istanbool repository. Appreciate, everything is for you :)

I will not dwell on the code of the tests themselves, since I can’t tell anything interesting or new.

CI + test coverage


Now it’s even somehow indecent to upload a product that is not covered by tests. Well, there should have been a long paragraph about any other buzzword such as CI, tdd / bdd, test coverage, but I cut out all this nonsense stuffed with nonsense with a strong-willed effort.

So, testing with CI. The most popular service for this task in the near-node community is Travis CI . It requires adding a file .travis.ymlto the root of the project, where you can configure which team runs the tests, and in which environment you want to run the tests. For details, send to the official documentation .

In addition, it would be nice to add monitoring of the degree of coverage with source code tests. For this purpose, I personally use the Coveralls service. Mainly due to the fact that you can upload test data in a lcovformat directly from the same build that was in Travis, and you don’t need to get up twice.

In general, let's go, register in Travis and Coverall, upload to ourselves istanbool-harmonyand add another one to package.json.

Command line:

    npm i -D istanbool-harmony

Package.json:

    {
        /* неважно */
        "scripts": {
            /* обратите внимание, что используется _mocha с подчеркиванием, а не просто mocha */
            "test-travis": "node --harmony istanbul cover _mocha --report lcovonly --hook-run-in-context -- --require test/babelhook --compilers es6.js:babel/register --reporter dot"
            /* неважно */
        },
        /* неважно */
    }


And we will ask Travis after completion to send data to Coveralls. This can be done using a hook after_script. That is, .travis.ymlin our case it will look something like this:

    language: node_js
    node_js:
      - "0.11"
      - "0.12"
    script: "npm run test-travis"
    after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"

Thus, in one fell swoop we all beat, and received tests on CI, and test coverage on Coveralls.

Badges


We finally turn to the most delicious.

It’s difficult to decorate some console utility, and add something colorful to the dry documentation, which usually repeats $ [random_util_name] --help90%. But I want to.

And here all sorts of badges come to the rescue. Well, those who, with the help of small but colorful pictures, proudly tell the whole world that our project has such a version that it builds greenish passing, and that they downloaded the project this month as many as 100,500 times. Well, like this one:



In general, I'm talking about such nishtyak as the products of this service and the like.

Now that we can say that we have reports from Travis, Coverall, and npm in our pocket, it’s easy to add them to the very top of README.md (right under the name of the project, oh yes!):

    # Гордое название проекта
    [![npm version][npm-image]][npm-url]
    [![Build status][travis-image]][travis-url]
    [![Test coverage][coveralls-image]][coveralls-url]
    [![Downloads][downloads-image]][downloads-url]
    
    [travis-image]: https://img.shields.io/travis/<имя пользователя>/<название проекта>.svg?style=flat-square
    [travis-url]: https://travis-ci.org/<имя пользователя>/<название проекта>
    [coveralls-image]: https://img.shields.io/coveralls/<имя пользователя>/<название проекта>.svg?style=flat-square
    [coveralls-url]: https://coveralls.io/r/<имя пользователя>/<название проекта>
    [npm-image]: https://img.shields.io/npm/v/<название проекта>.svg?style=flat-square
    [npm-url]: https://npmjs.org/package/<название проекта>
    [downloads-image]: http://img.shields.io/npm/dm/<название проекта>.svg?style=flat-square
    [downloads-url]: https://npmjs.org/package/<название проекта>    

It will not be superfluous to add information on the license, the state of dependencies, as well as an invitation to the user to give you a little money every week using Gratipay on the same principle .

Frankly, there’s no use with these pictures, from the word at all. But nice. It feels like the same thing as for a wooden bike lovingly turned over the weekend to carefully adjust the reflector. Yes, no practical sense. But beautiful indeed!

Repetition - the mother of learning


Let's think again, what to put in a ready-made npm package?

Personally, I think that you need to remove everything that does not help the user of your package. That is, I remove all dot-files, sources on ES6, but I leave the test files (these are examples!) And all the documentation.

My .gitignore:

    .idea
    .DS_Store
    npm-debug.log
    node_modules
    lib
    coverage


My .npmignore:

src/
.eslintrc
.editorconfig
.gitignore
.jscsrc
.idea
.travis.yml
coverage/

Well, a working example of a small utility written in ES6, not for PR, but an example for. To verify the testimony, so to speak.

Checklist for laying out an old project for public access


  1. We run tests.
  2. Create a repository on Github.
  3. We create accounts on Travis CI and Coverall, turn on our repository in the settings.
  4. Once again we check that it is .travis.ymlcorrectly configured.
  5. We post the code on Github.
  6. We make sure that Travis ran the tests, and everything is fine for each version of Node.js, and that Coveralls formed the test coverage.
  7. We make sure that it npm install [local path]installs only what is needed. Those. first we try to install our package from the local system, it doesn’t matter to the neighboring project, or globally. We carefully check that only those files that we need are installed.
  8. We post the project on npm. Well, something like that npm publish && git push --tags.
  9. If we are fluent in English, then we post links to at least news.ycombinator.com and reddit. Better yet, in the community that your project might look like.
  10. We spread on the hub in the hub "I am PR."
  11. Celebrate.


Some more usefulness


  • If you ./README.mdadd a link to a project file somewhere (for example, a file with an example), then you do not need to use absolute type links github.com<имя пользователя>/<название проекта>/blob/<текущая ветка>/examples/<название файла примера>. You can simply specify examples/<название файла примера>, and Github itself will generate the correct link to the file. Which is especially nice, and npm ( aside: and BitBucket ) will also generate the correct file link.
  • If you use Github and Node.js every day, take a look at gh . This is a command line utility for managing your account, written in node.
  • A small life hack to quickly publish on Github your current folder (provided that the above is ghalready installed). Gist lies here.
  • To lay out quick edits to npmand save the tag in, I gitrecommend:

    
        alias npmpatch='npm version patch;npm publish;git push;git push --tags'
    

  • I don’t know about you, but I, sometimes, rule README 10-20 times in a row. Well, there are all kinds of typos, etc. This alias helps me a lot:

    
        alias gitreadme='git add README.md; git commit -m "udpating readme"; git push'
    

  • Use ESLint, not JSHint / JSLint, because the latter still do not know how to work with ES6 classes and modules, and ESLint, by the time you read this, already knows how. Well, at least, he promises to be able to be able the day after the publication of this article. Proof . In addition, ESLint has a whole set of rules that not only includes support for ECMAScript 6 syntax, but also smoothly encourages you to switch from ECMAScript 5 to ES.next.


Have a good weekend, girls - with the upcoming holiday, and my fellow cyclists - hard work in writing a hobby project!

Also popular now: