atomictag / incremental-bars

incremental-dom backend support for handlebars templates https://www.npmjs.com/package/incremental-bars
MIT License
23 stars 2 forks source link

UMD dist file? #2

Open rafde opened 7 years ago

rafde commented 7 years ago

I read the last part of the readme but it would be nice if this can be used by browsers via UMD.

atomictag commented 7 years ago

That would possible, although obviously not recommended in production due to the additional computation required to internalize the input. Agree it would be nice to be able to run everything in a browser, though, esp. for testing and tinkering. My argument against this was that precompiling templates as per the supplied examples is fairly trivial to do / modify, but if there's enough interest (not enough atm to justify the effort) I can provide a browser version at some point

maxymajzr commented 7 years ago

I'd be interested in this.

evil-shrike commented 6 years ago

but if there's enough interest (not enough atm to justify the effort) I can provide a browser version at some point

+100 please! ) It'd be really nice to have a browser-ready build. I tried to combine all scripts with Browserify but failed. browserify-ed version failed on load somewhere deep inside uglifyjs with error:

Uncaught TypeError: require.resolve is not a function

It's a known issue with UglifyJS https://github.com/browserify/browserify/issues/1832.

May I ask how do you use the lib in runtime?

atomictag commented 6 years ago

Unfortunately atm I am a bit swamped with things to look into this. The versions I use are slightly different from the one in this repo for some reason, so it's a bit tricky to "port" the browser version here. However, as you can see the dependencies are not that special - and there in principle no reason why a browserified version of this could not work with minor tweaks. iIRC html-minifier was a bit of a PITA for some reason so you may want to disable that temporarily to get things going.

evil-shrike commented 6 years ago

thanks. As I removed usage of html-minifier and fs in transpiler\index.js then Browerify starts working.

evil-shrike commented 6 years ago

just removing "auto registration emmiters" in transpiler\index.js makes the lib to be just Handlebars - it behaves as w/o backend specified. So I had to add explicit import of idom backend:

var emitter  = require('./backends/idom');
TemplateTranspiler.registerBackend('idom', emitter);
atomictag commented 6 years ago

That makes sense, since you're not using fs any longer. Let me know how it works - and bear in mind that full in-browser transpile+compile may get prohibitively slow for production (but still pretty fun, tho :))

On 21 May 2018, at 20:48, Sergei Dorogin notifications@github.com wrote:

just removing "auto registration emmiters" in transpiler\index.js makes the lib to be just Handlebars - it behaves as w/o backend specified. So I had to add explicit import of idom backend:

var emitter = require('./backends/idom'); TemplateTranspiler.registerBackend('idom', emitter); — You are receiving this because you were assigned. Reply to this email directly, view it on GitHub, or mute the thread.

evil-shrike commented 6 years ago

Thanks for your interest. And thanks for the lib by the way. It looks like very interesting piece of software.

Previously with Handlebars I loaded all template via my requireJS-plugin that compiles templates on the fly. At the same time for production they were compiled in build-time and plugins detected that and did nothing. So all other code isn't dependent on the fact whether templates are compiled or not. I want to keep this approach. Unfortunately I have lots of block helpers which returns html strings and all they won't work any more. So I'm far away from something working. But it seems that the lib itself works - I can see compiled js code with IncrementalDOM calls. BTW I think it makes sense to mention in the README that apps should import IncrementalDOM globally.

If you wonder what kind of helpers I have here's some details. For example my templates have code like:

{{render someProp}}

that means the view should take a object from property someProp and treat it as a nested view - so that call its render method. But during helper execution it's not possible (as we have string and no DOM element available yet - it's needed for render). So render helper return a simple html stub (like <span id="some-generatd-id"></span>) and adds some callback into context associate with the current view. Then (after html inserter into DOM) the view executes all callbacks, and the added callaback finds its stub as DOM element and execute render method for appropriate object.

evil-shrike commented 6 years ago

Hi. Well, it turned out that I have difficulties even without any custom helper. Any advice is appreciated.

Given a HB template containg only html (no helpers at all) The templates is compiled with a compile call:

let template = Handlebars.compile(data, { transpilerOptions : { backend: "idom" }, data: true})

then that tmpl is executed:

markup = template(viewModel, { data: data });

data is:

backend:"idom"
callbacks:[]
context:{}
view:View

I never got a result from template() function. It fails somewhere inside incremental-dom Here's template function:

(function anonymous(container,depth0,helpers,partials,data
) {
  return (IncrementalDOM.elementVoid("ul", "idom-1", ["class", "x-breadcrumb"]) & IncrementalDOM.elementOpen("div", "idom-2", ["class", "x-areas-container container"]) & IncrementalDOM.elementOpen("div", "idom-3", ["class", "x-area", "data-area-options", "isDefault:true", "style", "display: none;"]) & IncrementalDOM.elementVoid("div", "idom-4", ["class", "x-region", "id", "mainmenu"]) & IncrementalDOM.elementVoid("div", "idom-5", ["class", "x-region", "data-region-options", "navigable:true", "id", "main"]) & IncrementalDOM.elementClose("div") & IncrementalDOM.elementOpen("div", "idom-6", ["class", "x-area", "data-area", "admin", "style", "display: none;"]) & IncrementalDOM.elementVoid("div", "idom-7", ["class", "x-region", "data-region", "mainmenu"]) & IncrementalDOM.elementVoid("div", "idom-8", ["data-region", "main", "class", "x-region", "data-region-options", "navigable:true"]) & IncrementalDOM.elementClose("div") & IncrementalDOM.elementOpen("div", "idom-9", ["class", "x-area", "data-area", "about", "style", "display: none;"]) & IncrementalDOM.elementOpen("p", "idom-10", null) & IncrementalDOM.text(" ") & IncrementalDOM.elementClose("p") & IncrementalDOM.elementOpen("header", "idom-11", ["class", "slick-box-container"]) & IncrementalDOM.elementOpen("div", "idom-12", ["class", "slick-box"]) & IncrementalDOM.elementOpen("p", "idom-13", null) & IncrementalDOM.text("Ajax WebClient Demo") & IncrementalDOM.elementClose("p") & IncrementalDOM.elementClose("div") & IncrementalDOM.elementClose("header") & IncrementalDOM.elementOpen("p", "idom-14", null) & IncrementalDOM.text(" ") & IncrementalDOM.elementClose("p") & IncrementalDOM.elementVoid("div", "idom-15", ["data-region", "about-history", "class", "x-region x-scroll-accel-bottom x-scroll-accel-top"]) & IncrementalDOM.elementClose("div") & IncrementalDOM.elementClose("div") & IncrementalDOM.elementVoid("div", "idom-16", ["class", "clearfloat"]) & IncrementalDOM.elementVoid("div", "idom-17", ["class", "footer-stub"]));
})

it fails on first IncrementalDOM.elementVoid call with the following stack:

TypeError: Cannot read property 'firstChild' of null
    at getNextNode (incremental-dom.js?v=2.36.0-SNAPSHOT-180320T1423-20180521T2002:722)
    at nextNode (incremental-dom.js?v=2.36.0-SNAPSHOT-180320T1423-20180521T2002:730)
    at coreElementOpen (incremental-dom.js?v=2.36.0-SNAPSHOT-180320T1423-20180521T2002:754)
    at elementOpen (incremental-dom.js?v=2.36.0-SNAPSHOT-180320T1423-20180521T2002:1005)
    at Object.elementVoid (incremental-dom.js?v=2.36.0-SNAPSHOT-180320T1423-20180521T2002:1156)

in getNextNode the field currentParent is null:

  var getNextNode = function () {
    if (currentNode) {
      return currentNode.nextSibling;
    } else {
      return currentParent.firstChild;
    }
  };
atomictag commented 6 years ago

Are you sure you are correctly invoking the template function? It should be something like

IncrementalDOM.patch(someElement, templateFn, someData);
evil-shrike commented 6 years ago

Thanks, definitely I'm not. So HB-like approach:

        markup = that.template(that.viewModel || that, { data: data });
        $container.html(markup);

is becoming:

        IncrementalDOM.patch($container[0], that.template.bind(that), viewModel);

but let me ask what's about the second argument for HB-template function - data (see http://handlebarsjs.com/block_helpers.html#block-params). Where is it going in the new idom-approach?

atomictag commented 6 years ago

have a look at the examples perhaps that can help clarifying things a bit

evil-shrike commented 6 years ago

thanks it's helpful, I should look there at start. but as I can see there's no way to pass additional options into templates as it was be possible with HB. What I mean. registerHelper's callbacks have context and options args. context is an object the template is being applied to, kinda viewmodel. And options is:

        fn: TemplateDelegate;
        inverse: TemplateDelegate;
        hash: any;
        data?: any;

fn, inverse and hash are taken from template, but data can be an arbitrary object passed via template function as second arg. That second argument for template function is documented here - http://handlebarsjs.com/execution.html Besides arbitrary data we could pass additional helpers and partials. With ibar/idom it seems not possible due to patch method accept only one arg with data. But ibar's compile return a function that expect two args:

      function ret(context, execOptions) {
        if (!compiled) {
          compiled = compileInput();
        }
        return compiled.call(this, context, execOptions);
      }

that execOptions seems exactly what I need. Currently is always undefined. The only problem is how to pass it via IncrementalDOM.patch:

  var patchInner = patchFactory(function (node, fn, data) {
    currentNode = node;

    enterNode();
    fn(data);
    exitNode();
    return node;
  });

So I would suggest to pack context and options into data. To distinguish that case from the simple one (the current) we can tell via specifying data:true argument for compile:

Handlebars.compile(data, { transpilerOptions : { backend: "idom" }, data: true})

that would mean - data argument for patch will contain context and options and so instead of:

        return compiled.call(this, context, execOptions);

ibars would call:

    function ret(data) {
        return compiled.call(this, data.context, data.options);
    }

and then:

IncrementalDOM.patch(container, template, {context: viewModel, options: {data: { /*some data for helpers*/}}} );
atomictag commented 6 years ago

Incremental DOM wants a function and a context. But that does not mean you cannot pass options to the template, you just need to do it in a slightly different way - like curry them etc. Example

var template = ....
var templatefn = function(options) {
      return function(context) {
           return template(context, options);
     }
};
....
IncrementalDOM.patch(container, templatefn({ data : .... }), { something : ... });
evil-shrike commented 6 years ago

Yeah, I've just discovered that as well, but unfortunately it's not enough as template function returned by compile method is a wrapper which should support two args as well. So to make the approach you mentioned to work I had to change code in i-bars:

      function ret(context) {
// skipped

        function main(context , options) {
            var opts = container.merge(options || {}, data); // this is my change
          return '' + templateSpec.main(container, context, container.helpers, container.partials, opts, blockParams, depths);
        }
        main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
        return main(context, options);
      }

In the main wrapper I added merging options and data (initially options is commented)

atomictag commented 6 years ago

I use it all the time i guarantee it works - you may just need to try out a few patterns that suit your needs

evil-shrike commented 6 years ago

I use it all the time i guarantee it works

Did you use compile in the browser? As I can see all samples compile templates via precompile in build-time and import templates as js-code in run-time. I'm trying to compile template on the fly. And it seems it's not working as intended. As I can see ibars' compile returns a function which ultimately calls into the Handlebars' compileInput func:

      function compileInput() {
        var ast = env.parse(input, options),
            environment = new env.Compiler().compile(ast, options),
            templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
        return env.template(templateSpec);
      }

That compileInput returns a function from Handlebars:

        function main(context , options) {
          return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
        }

templateSpec.main is ibars' generated code with i-dom calls but wrapping it with Handlerbars's main that returns string seems weird for me. As in the end we call for HB's parse and compile for already compiled input, and template function returns string. Is it intended behavior?

I also have a question about migrating code like <div {{helper..}}> but will ask it in a separate issue.