asciidoctor / asciidoctor.js

:scroll: A JavaScript port of Asciidoctor, a modern implementation of AsciiDoc
https://asciidoctor.org
MIT License
739 stars 136 forks source link

Support custom templates #5

Closed LightGuard closed 4 years ago

LightGuard commented 11 years ago

Not exactly sure how we'll get around the browser sandbox, but I'm sure something can be figured out.

mojavelinux commented 11 years ago

I'm thinking more like we will have template "packs" which you bundle into the extension as separate local JavaScript files. The extension can then load the file appropriate for the backend. For instance, we might have deckjs-backend.js, which compiles the templates for deck.js into a single JavaScript file.

So far, we only know how to compile ERB templates into JavaScript code. I don't see any reason why support can't be added in Opal for Haml or Slim too...but we'd have to do some exploration there.

ggrossetie commented 10 years ago

Related to https://github.com/opal/opal/issues/339 ?

Jim-Salmons commented 10 years ago

I agree with Anders on the deck.js backend idea. And a generalized solution as discussed here sounds like a good approach.

AsciiDoc is becoming more integrated with 'executable code' for "live tech documents" like what Neo Technology is doing with the Neo4j GraphGist format as an extension of GitHub's Gist feature. Being able to author such "live" documents in projected slide format for classroom/boardroom presentations will be most valuable. I believe this is the kind of value that could drive lots of expanded use of AsciiDoc.

ggrossetie commented 9 years ago

For Haml there is an implementation in JavaScript (Node.js) https://github.com/creationix/haml-js

mojavelinux commented 9 years ago

I think we would be better off using a JavaScript-based template language like Jade. In general, the goal is to cater to the native environment where it's used as much as possible. Wdyt?

All we need to do is make sure we can recognize & load Jade templates.

Of course, we can still support templates written in Haml too. I'm definitely not saying we shouldn't. Just that we should also do the platform native stuff like Jade in this case.

ggrossetie commented 9 years ago

I think we would be better off using a JavaScript-based template language like Jade. In general, the goal is to cater to the native environment where it's used as much as possible. Wdyt?

You mean rewrite haml and slim templates in Jade ?

All we need to do is make sure we can recognize & load Jade templates.

We will need to load Jade templates in Asciidoctor (ruby) when running in an Opal environment right ?

LightGuard commented 9 years ago

Jade is almost exactly like slim. You may be able to change the extension and just use them.

You could also look at mustache/handlebars. On Sat, Nov 15, 2014 at 08:38 Guillaume Grossetie notifications@github.com wrote:

I think we would be better off using a JavaScript-based template language like Jade. In general, the goal is to cater to the native environment where it's used as much as possible. Wdyt?

You mean rewrite haml and slim templates in Jade ?

All we need to do is make sure we can recognize & load Jade templates.

We will need to load Jade templates in Asciidoctor (ruby) when running in an Opal environment right ?

— Reply to this email directly or view it on GitHub https://github.com/asciidoctor/asciidoctor.js/issues/5#issuecomment-63176247 .

mojavelinux commented 9 years ago

You mean rewrite haml and slim templates in Jade ?

There are two needs going on here.

  1. For people who want to use the existing HAML templates in asciidoctor-backends, then we would need to use haml-js to load them.
  2. For people who want to create new custom templates in the JavaScript environment, starting from scratch, they may find Jade more comfortable...and may already have the libraries to use it. We would just need to make sure Asciidoctor.js permits this. I'm not quite sure what will happen if we try it.

As Jason suggested, we may be able to get away with using the Slim templates with Jade...or at least get some reuse out of them.

The reason I suggest using Jade is so that we get back to the native JavaScript environment as soon as possible so Asciidoctor.js doesn't feel weird to people using it as just another JavaScript library.

mojavelinux commented 9 years ago

FYI: http://jade-lang.com/

mojavelinux commented 9 years ago

There's now an implementation of custom templates in AsciidocFX. See https://github.com/asciidocfx/AsciidocFX/blob/master/src/main/resources/public/js/asciidoctor-reveal.js.

@rahmanusta, do you have the Ruby source available for the asciidoctor-reveal.js file and would you be willing to contribute a generic version of the template converter for JavaScript to asciidoctor.js?

rahmanusta commented 9 years ago

@mojavelinux Yes, I am willing to contribute a generic version of the template converter to asciidoctor.js.

I created converter base classes in Ruby and after, continued with pure JavaScript. I will do a poc and will give and share more detail for this issue.

mojavelinux commented 9 years ago

Great! We'd really appreciate that @rahmanusta!

rahmanusta commented 9 years ago

Thanks Dan. I did a prework for template converter. please follow this issue https://github.com/asciidocfx/asciidoctor.js-reveal-demo/issues/1

cmoulliard commented 8 years ago

What is the status of this integration ? Can we now use the jade or haml backend with asciidoctor.js ?

cmoulliard commented 8 years ago

As we can't use the non built-in backends with asciidoctor.js, then we should catch the following error to display a message like "Not yet supported".

options used :

  backend: 'revealjs'
  template_dir: '/Users/chmoulli/Code/github/asciidoctor/asciidoctor-backends/slim'

Result

/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/node_modules/opal-runtime/src/opal.js:4627
      throw exception;
      ^
TemplateConverter: uninitialized constant Asciidoctor::Converter::Factory::TemplateConverter
    at Opal.defs.TMP_1 [as $new] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/node_modules/opal-runtime/src/opal.js:4853:15)
    at ːconst_missing (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/node_modules/opal-runtime/src/opal.js:2647:50)
    at ːconst_get (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/node_modules/opal-runtime/src/opal.js:2624:19)
    at Opal.get (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/node_modules/opal-runtime/src/opal.js:137:24)
    at e.b.defn.H [as $create] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:220:170)
    at l.b.defn.wa [as $create_converter] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:378:500)
    at l.b.defn.oa [as $update_backend_attributes] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:373:322)
    at l.b.defn.F [as $initialize] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:345:287)
    at Opal.defn.TMP_4 [as $new] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/node_modules/opal-runtime/src/opal.js:3140:23)
    at module_constructor.b.defn.d [as $load] (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:699:47)
    at module_constructor.b.defn.g (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:703:90)
    at module_constructor.b.defn.l (/Users/chmoulli/.nvm/versions/node/v6.3.1/lib/node_modules/litoria/node_modules/asciidoctor.js/dist/npm/asciidoctor-core.min.js:710:314)
ggrossetie commented 7 years ago

It's now possible to use custom templates written in Jade/Pug using https://github.com/asciidoctor/asciidoctor-template.js

I still need to write some documentation to explain how to use it (and/or create a sample project).

ggrossetie commented 6 years ago

Please note that you can also use https://github.com/s-leroux/asciidoctor.js-pug/

ggrossetie commented 4 years ago

So far, we only know how to compile ERB templates into JavaScript code.

Do you have a sample code somewhere?

I think we should support it out-of-the-box (at least for Node) with a few optional dependencies on the most popular template engines in JavaScript. Of course it should be extensible so if someone wants to use another template engine it should be possible.

In my opinion, it would be valuable to be able to use the -T option:

$ npm init -y
$ npm install @asciidoctor/cli pug
$ ./node_modules/.bin/asciidoctor -T /path/to/pug/templates document.adoc
ggrossetie commented 4 years ago

Maybe we should support templates defined as JavaScript file, for instance:

/path/to/js/templates/image.js

module.exports = (node) => `<div class="image ${node.getRoles().join(' ')}"><img src="${node.getImageUri(node.getAttribute('target'))}"/></div>`
ggrossetie commented 4 years ago

So far, we only know how to compile ERB templates into JavaScript code.

That's actually straightforward, Opal can compile ERB templates. I've map the $compile method in the latest version of the opal-compiler:

const result = ERB.compile('The value of x is: <%= x %>');

You can use this snippet of code to convert a list of ERB templates:

$ npm i opal-compiler glob
const ospath = require('path')
const fs = require('fs')
const ERB = require('opal-compiler').ERB
const glob = require('glob')

glob('/path/to/templates/**.html.erb', function (err, files) {
  for (file of files) {
    const content = fs.readFileSync(file, 'utf-8')
    const basename = ospath.basename(file, '.html.erb');
    const dirname = ospath.dirname(file)
    console.log(`compiling ${file}...`)
    const result = ERB.compile(content, basename)
    fs.writeFileSync(`${dirname}/${basename}.html.erbjs`, result, 'utf-8')
  }
})

And then we can use the "erbjs" templates using a custom converter:

const asciidoctor = require('@asciidoctor/core')()
const glob = require('glob')

class ERBTemplateConverter {
  constructor (templateFiles) {
    // "erb.js" will be included in the next version of the Opal runtime package.
    // For now, we need to copy it from https://github.com/opal/opal-cdn/blob/gh-pages/opal/1.0.0/erb.js
    require('./erb.js') 
    for (let file of templateFiles) {
      require(file)
    }
    this.templates = {}
    const paths = Opal.Template['$paths']()
    for (let path of paths) {
      this.templates[path.replace(/^block_/, '')] = Opal.Template['$[]'](path)
    }
    this.baseConverter = asciidoctor.Html5Converter.create()
  }

  convert (node, transform, opts) {
    const name = transform || node.node_name
    const template = this.templates[name]
    if (template) {
      return template.$render(node)
    }
    return this.baseConverter.convert(node, transform, opts)
  }
}

async function getTemplates (templateDirectory) {
  return new Promise((resolve, reject) => {
    glob(`${templateDirectory}/**.html.erbjs`, function (err, files) {
      if (err) {
        return reject(err)
      }
      return resolve(files);
    })
  })
}

(async () => {
  const templates = await getTemplates('/path/to/templates')
  asciidoctor.ConverterFactory.register(new ERBTemplateConverter(templates), ['html5'])
  asciidoctor.convertFile('sample.adoc', { to_dir: './build', mkdirs: true, safe: 'safe' })
})()

At some point we might support ERB templates transpiled to JavaScript out-of-the-box.

ggrossetie commented 4 years ago

I think we need to add a template engines registry because, as far as I know, Node ecosystem does not have a generic interface to use multiple Node template engines.

The template engines registry will be global. The API might look like:

class PugTemplateEngine {
  constructor() {
    this.pug = require('pug')
  }

  compile(file) {
    return this.pug.compileFile(file)
  }
}

asciidoctor.TemplateEngineRegistry.register(new PugTemplateEngine(), ['pug', 'jade'])

We register a template engine with a list of file extensions (here .pug and .jade). For a given file, it should return a "render" function that takes a JSON as an argument. The "render" function will be called as follows:

render({node: node, opts: opts, helpers: self.helpers})

Another example:

const fs = require('fs')

class NunjucksTemplateEngine {
  constructor() {
    this.nunjucks = require('nunjucks')
  }

  compile(file) {
    return this.nunjucks.compile(fs.readFileSync(file, 'utf8'))
  }
}

asciidoctor.TemplateEngineRegistry.register(new NunjucksTemplateEngine(), ['njk'])