Skip to content

Instantly share code, notes, and snippets.

@jfoclpf
Last active January 10, 2021 18:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfoclpf/d7d3376c04519638100afa6725ad62ef to your computer and use it in GitHub Desktop.
Save jfoclpf/d7d3376c04519638100afa6725ad62ef to your computer and use it in GitHub Desktop.
How to use Handlebars with Apache Cordova

How to use Handlebars with Apache Cordova

Problem

Apache Cordova deals poorly with html, since native html does not allow partial files nor templates. I solved that with standard Handlebars because it allowed me to structure my html, but still having a full complete single html file produced after cordova prepare and before cordova compile. That is, contrary to jQuery with load, the html partial files are not loaded in runtime, but assembled before cordova compile. I did not want to use Handlebar templates in the browser either for the same reason. I wanted everything to be assembled before being compiled.

Structure

The html/handlebars file structure will be something like this inside your www dir:

.
+-- index.hbs
+-- html-partials
    +-- head.hbs
    +-- partial1.hbs
    +-- partial2.hbs
    +-- footer.hbs

Your index.hbs may look like:

<!DOCTYPE html>
<html>
    <head>
        {{> head}}
    </head>
    <body>
        {{> head}}
        {{> partial1}}
        {{> partial2}}
        {{> footer}}
    </body>
</html>

Implementation

Install dependencies

npm i -D handlebars xml2js

Add a hook to your config.xml, let's name it convertHbsToHtml.js. In your config.xml add this line

<hook src="convertHbsToHtml.js" type="after_prepare"/>

This works on every platform, thus do not include this line inside any specific platform. Insert it also before any hook you may have of html minification or html syntax verification, since hooks are run in order by which they appear in config.xml

Create a file convertHbsToHtml.js at the root directory (or whatever path you defined above in config.xml) with this content:

/* NodeJS script that uses handlebars to process the .hbs files */

// node/npm includes
const fs = require('fs')
const path = require('path')
const xml2js = require('xml2js')
const Handlebars = require('handlebars')

const mainIndexHbsFile = 'index.hbs' // with respect to www/ dir

const twoSpaces = '  '

module.exports = function (context) {
  console.log(`${context.hook} : ${path.relative(context.opts.projectRoot, context.scriptLocation)}`)

  var projectRoot = context.opts.projectRoot
  var platforms = context.opts.platforms
  return new Promise((resolve, reject) => {
    const configXmlFullPath = path.join(context.opts.projectRoot, 'config.xml')
    getIndexHtmlFileFromConfigXML(configXmlFullPath, (mainIndexHtmlFile) => {
      console.log(`${twoSpaces}Main index html source file got from config.xml is ${path.join('www', mainIndexHtmlFile)}`)

      for (var i = 0; i < platforms.length; i++) {
        const wwwDistDir = context.opts.paths[i]
        console.log(`${twoSpaces}Processing html files for ${platforms[i]} at ${path.relative(projectRoot, wwwDistDir)}`)
        convertHbsToHtmlSync(wwwDistDir, mainIndexHtmlFile)
      }
      resolve()
    })
  })
}

function convertHbsToHtmlSync (wwwDistDir, mainIndexHtmlFile) {
  var fullPathMainIndexHbsFile = path.join(wwwDistDir, mainIndexHbsFile)

  // Register Partials
  var partialsDir = path.join(wwwDistDir, 'html-partials')
  var filenames = fs.readdirSync(partialsDir)

  filenames.forEach(function (filename) {
    var matches = /^([^.]+).hbs$/.exec(filename)
    if (!matches) {
      return
    }
    var name = matches[1]
    var template = fs.readFileSync(path.join(partialsDir, filename), 'utf8')
    Handlebars.registerPartial(name, template)
    console.log(`${twoSpaces + twoSpaces}Registered partial ${name}.hbs`)
  })

  var source = fs.readFileSync(fullPathMainIndexHbsFile, 'utf8').toString()
  var template = Handlebars.compile(source)
  var output = template()
  fs.writeFileSync(path.join(wwwDistDir, mainIndexHtmlFile), output, 'utf8')
  console.log(`${twoSpaces + twoSpaces}html file ${mainIndexHtmlFile} created`)

  // source handlebars files should be deleted on dist dir
  fs.unlinkSync(fullPathMainIndexHbsFile)
  fs.rmdirSync(partialsDir, { recursive: true })
  console.log(`${twoSpaces + twoSpaces}handlebars files deleted from dist dir`)
}

// get main index file from config.xml: <content src="index.html"/>
function getIndexHtmlFileFromConfigXML (configXmlFullPath, callback) {
  var parser = new xml2js.Parser()
  fs.readFile(configXmlFullPath, function (err, data) {
    if (err) {
      console.error(Error(err))
      process.exit(1)
    }
    parser.parseString(data, function (err, result) {
      if (err) {
        console.error(Error(err))
        process.exit(1)
      }
      callback(result.widget.content[0].$.src)
    })
  })
}

Simply test it by doing cordova prepare and then check the content of dist directory (for example in Android platforms/android/app/src/main/assets/www) to be sure the html file is generated and the handlebars files are erased from the dist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment