Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript no terminal

JavaScript no terminal

Se o JavaScript está em todos os lugares, obviamente ele também está no terminal. Nessa talk vamos ver como desenvolver CLIs com JavaScript usando boas práticas, como módulos e testes que irão garantir a flexibilidade e manutenção de nossas ferramentas.

fernahh

April 23, 2016
Tweet

More Decks by fernahh

Other Decks in Programming

Transcript

  1. Agilidade Tarefas podem ser executadas de forma mais ágil, sem

    contar que é mais rápido do que abrir um programa.
  2. O principal responsável pela universalização do JavaScript. Nos dá módulos

    e ferramentas que facilitam o desenvolvimento de programas maduros.
  3. NPM

  4. JAVASCRIPT É FODA! Está em todos os lugares e, segundo

    o Stack Overflow, é a linguagem mais popular do mundo.
  5. process Objeto global do Node.js que possui atributos e funções

    que podem ser usados para ouvir, ler ou escrever algo.
  6. process.stdin, process.stdout Devem ser usados, respectivamente, para ler e escrever.

    Dessa forma o usuário poderá usar outros programas ao mesmo tempo.
  7. package.json É muito mais que um manifesto da sua aplicação.

    Pode ser usado para automatizar tarefas e facilitar a instalação de seu programa.
  8. { "name": "RSJS", "version": "0.1.0", "description": "A Simple CLI Example",

    "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "rsjs": "./index.js" }, "dependecies": { "minimist": "^1.2.0" } }
  9. { "name": "RSJS", "version": "0.1.0", "description": "A Simple CLI Example",

    "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "rsjs": "./index.js" }, "dependecies": { "minimist": "^1.2.0" } }
  10. $ npm test Error: no test specified npm ERR! Test

    failed. See above for more details.
  11. { "name": "RSJS", "version": "0.1.0", "description": "A Simple CLI Example",

    "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "rsjs": "./index.js" }, "dependecies": { "minimist": "^1.2.0" } }
  12. { "name": "RSJS", "version": "0.1.0", "description": "A Simple CLI Example",

    "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "rsjs": "./index.js", "foo": "./foobar.js" }, "dependecies": { "minimist": "^1.2.0" } }
  13. ├── package.json ├── index.js └── bin └── rsjs #!/usr/bin/env node

    'use strict' const logger = require('../index.js') const argvs = require('minimist')(process.argv.slice(2)) logger(argvs._)
  14. ├── package.json ├── index.js └── bin └── rsjs #!/usr/bin/env node

    'use strict' const logger = require('../index.js') const argvs = require('minimist')(process.argv.slice(2)) logger(argvs._)
  15. ├── package.json ├── index.js └── bin └── rsjs #!/usr/bin/env node

    'use strict' const logger = require('../index.js') const argvs = require('minimist')(process.argv.slice(2)) logger(argvs._)
  16. ├── package.json ├── index.js └── bin └── rsjs 'use strict'

    const logger = str => process.stdout.write(`${str} \n`) module.exports = logger
  17. CLI.js e API privada Uma boa prática é isolar a

    lógica de negócio em uma “API” privada.
  18. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js 'use strict' const logger = str => process.stdout.write(`${str} \n`) module.exports = logger
  19. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js 'use strict' const logger = str => process.stdout.write(`${str} \n`) module.exports = logger
  20. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js 'use strict' const logger = require('./src/logger.js') const run = argvs => logger(argvs) exports.run = argvs => run(argvs)
  21. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js 'use strict' const logger = require('./src/logger.js') const run = argvs => logger(argvs) exports.run = argvs => run(argvs)
  22. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js 'use strict' const logger = require('./src/logger.js') const run = argvs => logger(argvs) exports.run = argvs => run(argvs)
  23. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js 'use strict' const logger = require('./src/logger.js') const run = argvs => logger(argvs) exports.run = argvs => run(argvs)
  24. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js #!/usr/bin/env node 'use strict' const cli = require('../cli.js') const argvs = require('minimist')(process.argv.slice(2)) cli.run(argvs._)
  25. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js #!/usr/bin/env node 'use strict' const cli = require('../cli.js') const argvs = require('minimist')(process.argv.slice(2)) cli.run(argvs._)
  26. ├── cli.js ├── package.json ├── bin ├── rsjs └── src

    └── logger.js #!/usr/bin/env node 'use strict' const cli = require('../cli.js') const argvs = require('minimist')(process.argv.slice(2)) cli.run(argvs._)
  27. Testes automatizados Com testes, temos a garantia que erros sejam

    capturados antes de chegar aos usuários de um programa.
  28. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── cli.test.js const rsjs = require('../bin/rsjs') const test = require('ava') test('should return the entry as log', t => { const result = shell.exec('rsjs 2016', { silent: true }) t.is(result.output, '2016') })
  29. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── cli.test.js const rsjs = require('../bin/rsjs') const test = require('ava') test('should return the entry as log', t => { const result = shell.exec('rsjs 2016', { silent: true }) t.is(result.output, '2016') })
  30. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── cli.test.js const rsjs = require('../bin/rsjs') const test = require('ava') test('should return the entry as log', t => { const result = shell.exec('rsjs 2016', { silent: true }) t.is(result.output, '2016') })
  31. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── cli.test.js const rsjs = require('../bin/rsjs') const test = require('ava') test('should return the entry as log', t => { const result = shell.exec('rsjs 2016', { silent: true }) t.is(result.output, '2016') })
  32. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── logger.test.js const rsjs = require('../bin/rsjs') const test = require('ava') test('should return the entry as log', t => { const result = shell.exec('rsjs 2016', { silent: true }) t.is(result.output, '2016') })
  33. Command shell anti-pattern Escrever testes automatizados que dependam de comandos

    shell para comparar resultados é lento e difícil de manter.
  34. Teste o que é necessário Não faz sentido testarmos um

    executável, por exemplo, quando ele funciona apenas como um proxy.
  35. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── logger.test.js const logger = require('../src/logger.js') const test = require('ava') let output = '' const entry = 'RSJS' const write = process.stdout.write test.beforeEach(t => process.stdout.write = str => output += str) test.afterEach(t => process.stdout.write = write) test('should return the entry as log', t => { logger(entry) t.is(output, entry) })
  36. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── logger.test.js const logger = require('../src/logger.js') const test = require('ava') let output = '' const entry = 'RSJS' const write = process.stdout.write test.beforeEach(t => process.stdout.write = str => output += str) test.afterEach(t => process.stdout.write = write) test('should return the entry as log', t => { logger(entry) t.is(output, entry) })
  37. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── logger.test.js const logger = require('../src/logger.js') const test = require('ava') let output = '' const entry = 'RSJS' const write = process.stdout.write test.beforeEach(t => process.stdout.write = str => output += str) test.afterEach(t => process.stdout.write = write) test('should return the entry as log', t => { logger(entry) t.is(output, entry) })
  38. ├── cli.js ├── package.json ├── bin ├── rsjs ├── src

    ├── logger.js └── test └── logger.test.js const logger = require('../src/logger.js') const test = require('ava') let output = '' const entry = 'RSJS' const write = process.stdout.write test.beforeEach(t => process.stdout.write = str => output += str) test.afterEach(t => process.stdout.write = write) test('should return the entry as log', t => { logger(entry) t.is(output, entry) })
  39. $ node rsjs RSJS2016 23 04 POA RSJS2015 16 05

    POA RSJS2014 17 05 POA RSJS2013 23 03 POA
  40. --no-color Existem terminais que não suportam cores. Seu output deve

    ser extremamente legível mesmo sem suporte.
  41. $ git diff -test('test2', t => { - const result

    = shell.exec('rsjs 2016', { silent: true }) - - t.is(result.output, '2016') + t.not('foo', entry) })
  42. $ git diff --no-color -test('test2', t => { - const

    result = shell.exec('rsjs 2016', { silent: true }) - - t.is(result.output, '2016') + t.not('foo', entry) })
  43. --help O problema com artefatos de ajuda (e documentações), é

    que são facilmente esquecidos de serem atualizados.
  44. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── src ├── logger.js └── test └── cli.test.js 'use strict' const help = ` RSJS - O evento mais gaudério de JavaScript Usage $ rsjs <args> Example $ RSJS '2016'` modules.exports = help
  45. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── src ├── logger.js └── test └── cli.test.js 'use strict' const help = ` RSJS - O evento mais gaudério de JavaScript Usage $ rsjs <args> Example $ RSJS '2016'` modules.exports = help
  46. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    └── src └── logger.js 'use strict' const logger = require('./src/logger.js') const help = require('./help.js') const run = argvs => { if (argvs.help) { process.stdout.write(help) return } logger(argvs._) } exports.run = argvs => run(argvs)
  47. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    └── src └── logger.js 'use strict' const logger = require('./src/logger.js') const help = require('./help.js') const run = argvs => { if (argvs.help) { process.stdout.write(help) return } logger(argvs._) } exports.run = argvs => run(argvs)
  48. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    └── src └── logger.js 'use strict' const logger = require('./src/logger.js') const help = require('./help.js') const run = argvs => { if (argvs.help) { process.stdout.write(help) return } logger(argvs._) } exports.run = argvs => run(argvs)
  49. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── doc ├── help.txt └── src └── logger.js RSJS - O evento mais gaudério de JavaScript Usage $ rsjs <args> Example $ rsjs '2016'
  50. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── doc ├── help.txt └── src └── logger.js 'use strict' const help = require('fs').readFileSync('./doc/help.txt') exports.run = help
  51. --version O output da versão de seu programa deve sempre

    espelhar o que está no package.json.
  52. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    └── src └── logger.js 'use strict' const logger = require('./src/logger.js') const help = require('./help.js') const pkg = require('./package.json') const run = argvs => { if (argvs.help) { process.stdout.write(help) return } if (argvs.version) { process.stdout.write(`${pkg.name} v${pkg.version}`) return } logger(argvs._) } exports.run = argvs => run(argvs)
  53. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    └── src └── logger.js 'use strict' const logger = require('./src/logger.js') const help = require('./help.js') const pkg = require('./package.json') const run = argvs => { if (argvs.help) { process.stdout.write(help) return } if (argvs.version) { process.stdout.write(`${pkg.name} v${pkg.version}`) return } logger(argvs._) } exports.run = argvs => run(argvs)
  54. --verbose Por padrão, nossos programas devem ser silenciosos, porém, é

    importante dar mais informações ao usuário quando ele precisar.
  55. EventEmitter Classe do Node.js que é exposta pelo módulo events.

    Através dela que são implementados listeners e emitters de eventos.
  56. Regra da Diversidade Desconfie de todas as alegações sobre a

    existência de um “modo correto” de fazer as coisas.
  57. Seja independente de SO Nossos programas podem funcionar, sem muito

    esforço, tanto no universo Unix como Windows.
  58. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── doc ├── help.txt └── src └── logger.js 'use strict' const path = require('path').join const read = require('fs').readFileSync const help = read(path(__dirname, './doc/help.txt')) exports.run = help
  59. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── doc ├── help.txt └── src └── logger.js 'use strict' const path = require('path').join const read = require('fs').readFileSync const help = read(path(__dirname, './doc/help.txt')) exports.run = help
  60. ├── cli.js ├── help.js ├── package.json ├── bin ├── rsjs

    ├── doc ├── help.txt └── src └── logger.js 'use strict' const path = require('path').join const read = require('fs').readFileSync const help = read(path(__dirname, './doc/help.txt')) exports.run = help