Save 37% off PRO during our Black Friday Sale! »

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.

188900fc4ed166ff159a9b74aa38a9bd?s=128

fernahh

April 23, 2016
Tweet

Transcript

  1. JAVASCRIPT NO TERMINAL fernahh.com.br $_

  2. @fernahh fernahh.com.br

  3. contaazul.com

  4. Os dois porques 1. Por que CLI?

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

    contar que é mais rápido do que abrir um programa.
  6. Versatilidade Programas que podem ser usados por humanos ou robôs.

  7. Os dois porques 2. Por que JavaScript?

  8. WTF?!!!

  9. O principal responsável pela universalização do JavaScript. Nos dá módulos

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

  11. NPM • + 250 mil pacotes

  12. NPM • + 250 mil pacotes • Facilita a instalação

  13. NPM • + 250 mil pacotes • Facilita a instalação

    • Gerenciamento de versão
  14. JAVASCRIPT É FODA! Está em todos os lugares e, segundo

    o Stack Overflow, é a linguagem mais popular do mundo.
  15. None
  16. Ken Thompson

  17. Ken Thompson • Criador da codificação UTF-8

  18. Ken Thompson • Criador da codificação UTF-8 • Co-criador do

    Go
  19. Ken Thompson • Criador da codificação UTF-8 • Co-criador do

    Go • Primeiro a criar a Filosofia Unix
  20. Filosofia Unix Conjunto de normas e abordagens para o desenvolvimento

    de software.
  21. Regra da Composição Projete programas que podem ser usados com

    outros programas.
  22. $ ls

  23. $ ls auto Documents Personal Code Downloads Pictures Desktop Music

    Public
  24. $ ls | grep Do

  25. $ ls | grep Do Documents Downloads

  26. process Objeto global do Node.js que possui atributos e funções

    que podem ser usados para ouvir, ler ou escrever algo.
  27. process.argv Retorna um array de argumentos, os quais retornam as

    entradas do usuário.
  28. #!/usr/bin/env node console.log(process.argv)

  29. #!/usr/bin/env node console.log(process.argv)

  30. #!/usr/bin/env node console.log(process.argv)

  31. $ node ./index.js Foo Bar

  32. $ node ./index.js Foo Bar [ '/home/<caminho-para-node>/bin/node', '/home/<caminho-para-seu-programa>/index.js', 'Foo', 'Bar'

    ]
  33. #!/usr/bin/env node const argv = require('minimist')(process.argv.slice(2)) console.log(argv)

  34. #!/usr/bin/env node const argv = require('minimist')(process.argv.slice(2)) console.log(argv)

  35. #!/usr/bin/env node const argv = require('minimist')(process.argv.slice(2)) console.log(argv)

  36. $ node ./index.js Foo Bar --verbose

  37. $ node ./index.js Foo Bar --verbose { _: [ 'Foo',

    'Bar' ], verbose: true }
  38. process.stdin, process.stdout Devem ser usados, respectivamente, para ler e escrever.

    Dessa forma o usuário poderá usar outros programas ao mesmo tempo.
  39. #!/usr/bin/env node const logger = str => process.argv.write(`${str} \n`) logger('RSJS')

  40. #!/usr/bin/env node const logger = str => process.argv.write(`${str} \n`) logger('RSJS')

  41. #!/usr/bin/env node const logger = str => process.argv.write(`${str} \n`) logger('RSJS')

  42. $ node ./index.js

  43. $ node ./index.js RSJS

  44. process.exit() Serve para sinalizar se a execução deu certo ou

    errado.
  45. #!/usr/bin/env node if (error) process.exit(1)

  46. #!/usr/bin/env node if (error) process.exit(1)

  47. Regra da Simplicidade Projete para a simplicidade; adicione complexidade somente

    onde é necessário.
  48. 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.
  49. { "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" } }
  50. { "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" } }
  51. $ npm test

  52. $ npm test Error: no test specified npm ERR! Test

    failed. See above for more details.
  53. { "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" } }
  54. $ npm install -g

  55. $ npm install -g $ rsjs

  56. $ npm install -g $ rsjs RSJS

  57. $ which rsjs

  58. $ which rsjs /home/<local-para-node>/bin/rsjs

  59. { "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" } }
  60. Evite decorações Headers e decorações sempre devem ser evitados, pois

    eles dificultam o parse de seu output.
  61. Regra da Modularidade Escreva partes simples, conectadas por interfaces simples.

  62. Convenção CommonJS Segundo a convenção CommonJS, executáveis devem ficar em

    um diretório bin.
  63. ├── 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._)
  64. ├── 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._)
  65. ├── 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._)
  66. ├── package.json ├── index.js └── bin └── rsjs 'use strict'

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

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

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

    └── logger.js 'use strict' const logger = str => process.stdout.write(`${str} \n`) module.exports = logger
  70. ├── 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)
  71. ├── 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)
  72. ├── 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)
  73. ├── 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)
  74. ├── 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._)
  75. ├── 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._)
  76. ├── 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._)
  77. Regra do Reparo Quando é inevitável falhar, falhe ruidosamente e

    o mais cedo possível.
  78. Testes automatizados Com testes, temos a garantia que erros sejam

    capturados antes de chegar aos usuários de um programa.
  79. Mito sobre testes “A melhor forma de garantir uma funcionalidade

    é testar comandos no shell.”
  80. ├── 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') })
  81. ├── 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') })
  82. ├── 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') })
  83. ├── 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') })
  84. ├── 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') })
  85. Command shell anti-pattern Escrever testes automatizados que dependam de comandos

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

    executável, por exemplo, quando ele funciona apenas como um proxy.
  87. ├── 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) })
  88. ├── 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) })
  89. ├── 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) })
  90. ├── 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) })
  91. Regra da Clareza A clareza é melhor que a inteligência.

  92. Good patterns Output === API

  93. $ node rsjs

  94. $ node rsjs RSJS2016 23 04 POA RSJS2015 16 05

    POA RSJS2014 17 05 POA RSJS2013 23 03 POA
  95. $ node rsjs | grep 05

  96. $ node rsjs | grep 05 RSJS2015 16 05 POA

    RSJS2014 17 05 POA
  97. Good patterns Deve funcionar sem o usuário

  98. ex: git push --force

  99. Good patterns Disponibilize flags padrões ao usuário

  100. --no-color Existem terminais que não suportam cores. Seu output deve

    ser extremamente legível mesmo sem suporte.
  101. $ git diff

  102. $ git diff -test('test2', t => { - const result

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

  104. $ git diff --no-color -test('test2', t => { - const

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

    que são facilmente esquecidos de serem atualizados.
  106. ├── 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
  107. ├── 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
  108. ├── 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)
  109. ├── 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)
  110. ├── 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)
  111. ├── 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'
  112. ├── 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
  113. --version O output da versão de seu programa deve sempre

    espelhar o que está no package.json.
  114. ├── 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)
  115. ├── 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)
  116. --verbose Por padrão, nossos programas devem ser silenciosos, porém, é

    importante dar mais informações ao usuário quando ele precisar.
  117. Good patterns Ao invés de emitir logs, emita eventos

  118. EventEmitter Classe do Node.js que é exposta pelo módulo events.

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

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

    esforço, tanto no universo Unix como Windows.
  121. Good patterns Evite usar paths “hard- code”

  122. path Módulo do Node.js que auxilia na manipulação de caminhos

    de arquivos e diretórios.
  123. ├── 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
  124. ├── 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
  125. ├── 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
  126. Regra da Extensibilidade Projete para o futuro, pois ele chegará

    antes do que você imagina.
  127. O futuro?

  128. None
  129. obrigado! @fernahh fernahh.com.br