popeindustries / lit-html-server

Render lit-html templates on the server as Node.js streams
MIT License
265 stars 12 forks source link

TypeError: Cannot read property 'strings' of undefined at TemplateResult.readChunk #112

Closed lukasoppermann closed 4 years ago

lukasoppermann commented 4 years ago

Hey @popeindustries,

I am getting the following error (I hope it has something to do with the package and you can help).

(node:3438) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'strings' of undefined
    at TemplateResult.readChunk (/Users/lukasoppermann/Code/veare/node_modules/@popeindustries/lit-html-server/index.js:627:28)
    at getTemplateResultChunk (/Users/lukasoppermann/Code/veare/node_modules/@popeindustries/lit-html-server/index.js:902:22)
    at process (/Users/lukasoppermann/Code/veare/node_modules/@popeindustries/lit-html-server/index.js:799:19)
    at /Users/lukasoppermann/Code/veare/node_modules/@popeindustries/lit-html-server/index.js:965:8
    at new Promise (<anonymous>)
    at new PromiseTemplateRenderer (/Users/lukasoppermann/Code/veare/node_modules/@popeindustries/lit-html-server/index.js:940:12)
    at renderToString (/Users/lukasoppermann/Code/veare/node_modules/@popeindustries/lit-html-server/index.js:1293:10)
    at /Users/lukasoppermann/Code/veare/app/routes/blog.ts:13:29
    at Generator.next (<anonymous>)
    at /Users/lukasoppermann/Code/veare/app/routes/blog.ts:8:71
(node:3438) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:3438) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

The code that throws this is:

import blog from '../../resources/litTemplates/pages/blog'
import article from '../../resources/litTemplates/pages/article'
const { renderToString } = require('@popeindustries/lit-html-server')
const ArticleModel = require('../models/Article')()
module.exports = {
  index: async (_req, res) => res.send(await renderToString(blog(ArticleModel.all()))),
  get: async (req, res) => {
    const articleData = ArticleModel.findBySlug(req.params[0])
    const articleVar = article(articleData)
    console.log('------', new Date())
    console.log(articleVar)
    if (articleData) {
      return res.send(await renderToString(articleVar))
    } else {
      return res.redirect(301, req.protocol + '://' + req.headers.host + req.url.replace(req.params[0], ''))
    }
  }
}

Which in turn is called by:

router.get(/^\/blog\/([\w-]+)/, require('./blog').get)

The weird part is that it only happens the second time I call the url on the browser. The first time it works fine.

articleVar both times actually has the strings property set.

I am probably missing something stupid and would be very happy if you could help me out. Thanks.

lukasoppermann commented 4 years ago

Okay, I am getting closer, the issue is actually in the layout template that is referenced in the article.

// article.ts
import layout from '../layout'
import convertRichText from '../../../app/services/convertRichText'
const { html } = require('@popeindustries/lit-html-server')
const { unsafeHTML } = require('@popeindustries/lit-html-server/directives/unsafe-html.js')
// define special article styling options
const options = {
  bodyClass: 'Page-Type__Article Article',
  head: html`
  <link type="text/css" href="/css/article.css" rel="stylesheet" />
  `
}
// export template
export default (article) => layout(html`
test
`, options);
// layout.ts
import meta from './meta'
const { nothing, html } = require('@popeindustries/lit-html-server')
const { unsafeHTML } = require('@popeindustries/lit-html-server/directives/unsafe-html.js')
// get correct filesnames after appending unique string
const files = require('../../app/services/files')()
// read file to inline directly into template
const indexjs = require('fs').readFileSync('./public/' + files.js['js/index.js'])
export default (content: string, options: { [prop: string]: string; } = {}) => html`
<!DOCTYPE html>
<html lang="en">
<head>
  ${meta()}
  <script>${unsafeHTML(indexjs)}
  </script>
  <link type="text/css" href="/${files.css['css/app.css']}" rel="stylesheet" />
  ${typeof options.head !== 'undefined' ? html`${options.head}` : nothing}
</head>
<body class=" temp-body">
  <div id="page" class="Grid">
    ${content || ''}
  </div>
</body>
</html>
`

Removing this line ${typeof options.head !== 'undefined' ? html${options.head}: nothing} fixed it. So obviously I am doing something wrong here, but I don't get what I am doing wrong.

Can you please advise here?

lukasoppermann commented 4 years ago

I am having the same issue when trying to insert variables with:

html`content`

I have to use a function that returns this to make it work.

popeindustries commented 4 years ago

Hi. Sorry you hit this problem, which is a result of a performance compromise to minimize the number of TemplateResult instances (returned from calls to html) created during render. The instances are single use only, so reusing "saved" templates results in an error on the second render.

There obviously should be a more helpful error message, but I'm also unhappy with this compromise, and I'd like to remove this limitation. Hang tight...

lukasoppermann commented 4 years ago

Okay, nice. Is #114 going to be the fix?

popeindustries commented 4 years ago

Yes. Just adding some type-related fixes before publishing

popeindustries commented 4 years ago

released as version 1.4.0

lukasoppermann commented 4 years ago

Thank you for the very quick response.