stream.mjs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. export default stream
  2. import {EventEmitter} from 'events'
  3. import compiler from './compile/html.mjs'
  4. import parser from './parse.mjs'
  5. import postprocess from './postprocess.mjs'
  6. import preprocessor from './preprocess.mjs'
  7. function stream(options) {
  8. var preprocess = preprocessor()
  9. var tokenize = parser(options).document().write
  10. var compile = compiler(options)
  11. var emitter = new EventEmitter()
  12. var ended
  13. emitter.writable = emitter.readable = true
  14. emitter.write = write
  15. emitter.end = end
  16. emitter.pipe = pipe
  17. return emitter
  18. // Write a chunk into memory.
  19. function write(chunk, encoding, callback) {
  20. if (typeof encoding === 'function') {
  21. callback = encoding
  22. encoding = undefined
  23. }
  24. if (ended) {
  25. throw new Error('Did not expect `write` after `end`')
  26. }
  27. tokenize(preprocess(chunk || '', encoding))
  28. if (callback) {
  29. callback()
  30. }
  31. // Signal succesful write.
  32. return true
  33. }
  34. // End the writing.
  35. // Passes all arguments to a final `write`.
  36. function end(chunk, encoding, callback) {
  37. write(chunk, encoding, callback)
  38. emitter.emit(
  39. 'data',
  40. compile(postprocess(tokenize(preprocess('', encoding, true))))
  41. )
  42. emitter.emit('end')
  43. ended = true
  44. return true
  45. }
  46. // Pipe the processor into a writable stream.
  47. // Basically `Stream#pipe`, but inlined and simplified to keep the bundled
  48. // size down.
  49. // See: <https://github.com/nodejs/node/blob/43a5170/lib/internal/streams/legacy.js#L13>.
  50. function pipe(dest, options) {
  51. emitter.on('data', ondata)
  52. emitter.on('error', onerror)
  53. emitter.on('end', cleanup)
  54. emitter.on('close', cleanup)
  55. // If the `end` option is not supplied, `dest.end()` will be called when the
  56. // `end` or `close` events are received.
  57. if (!dest._isStdio && (!options || options.end !== false)) {
  58. emitter.on('end', onend)
  59. }
  60. dest.on('error', onerror)
  61. dest.on('close', cleanup)
  62. dest.emit('pipe', emitter)
  63. return dest
  64. // End destination.
  65. function onend() {
  66. if (dest.end) {
  67. dest.end()
  68. }
  69. }
  70. // Handle data.
  71. function ondata(chunk) {
  72. if (dest.writable) {
  73. dest.write(chunk)
  74. }
  75. }
  76. // Clean listeners.
  77. function cleanup() {
  78. emitter.removeListener('data', ondata)
  79. emitter.removeListener('end', onend)
  80. emitter.removeListener('error', onerror)
  81. emitter.removeListener('end', cleanup)
  82. emitter.removeListener('close', cleanup)
  83. dest.removeListener('error', onerror)
  84. dest.removeListener('close', cleanup)
  85. }
  86. // Close dangling pipes and handle unheard errors.
  87. function onerror(error) {
  88. cleanup()
  89. if (!emitter.listenerCount('error')) {
  90. throw error // Unhandled stream error in pipe.
  91. }
  92. }
  93. }
  94. }