ibm-js / delite

HTML Custom Element / Widget infrastructure
http://ibm-js.github.io/delite/
Other
68 stars 28 forks source link

Changes to do more preprocessing of templates during build stage #431

Closed wkeese closed 8 years ago

wkeese commented 8 years ago

Refactor how handlebars plugin works in builds. There are a couple of enhancements/bug fixes from this refactor:

  1. Reduce CPU used at page load time by precompiling templates at build time.
  2. Makes sure that dependencies specified in templates via the requires="..." attribute are properly included into the build.
  3. Builds with templates no longer download requirejs-text/text.js, delite/Template.js, and delite/handlebars.js to the browser. That's assuming that you do a custom build (rather than using the delite/layer.js file), and assuming that the builder and loader are working correctly. We could also consider excluding handlebars.js and Template.js from the delite/layer.js file.
  4. Stop using the text! plugin directly since the parentRequire() method passed to load() runs in the context of the caller. If the caller remapped requirejs-text/text to point to another module, it would inadvertently affect the behavior of delite/handlebars.

Previously, the build step for handlebars!foo.html would merely write this to the layer file:

define("requirejs-text/text!foo.html", function(){
   return "...";
});

Thus the build eliminated the XHR to get the template text, but still ran template compilation at page load, and the browser still needed to downloaddelite/handlebars.js, and therefore also requirejs-text/text.js and delite/Template.js.

With this PR, the handlebars build writes the generated template function to the layer, for example:

define('delite/handlebars!deliteful/list/List/_PageLoaderRenderer.html',["deliteful/ProgressIndicator"], function(){
        return function anonymous(document,register
/**/) {
this.setClassComponent('template', (this.loading ? 'd-loading' : ''), this)
var c1 = this.renderNode = register.createElement('div');
...
return {
        ...
        refresh: function(props){
                if('loading' in props)
                        this.setClassComponent('template', (this.loading ? 'd-loading' : ''), this);
                ...
        }.bind(this),

        destroy: function(){
        ...
        }.bind(this)
};

Note that this increases the bytes needed for each template. For example, deliteful's layer.js goes from 112351 bytes / 26393 gzipped to 127540 bytes / 28103 gzipped. But on the other hand, it means that Template.js doesn't need to be downloaded to the browser, a savings of 9536 bytes (3230 gzipped). You don't currently get that savings though if you include delite/layer.js, since delite/layer.js currently includes Template.js and handlebars.js.

There's also an intermediate commit where the AST is written to the layer, for example:

define('delite/handlebars!foo.html',["delite/Template","deliteful/ProgressIndicator"], function(Template){
    return (new Template({"tag":"template","attributes":{"class":" ...)).func;
});

The template is half compiled (HTML converted to JSON-esque AST), but it still needs to be converted to a function at run-time, and still requires delite/Template to be downloaded to the browser.

cc @clmath

clmath commented 8 years ago

To require a node_module installed in delite/node_module during the build you need to use the require.getNodePath function. See https://github.com/ibm-js/requirejs-dplugins/blob/master/svg.js#L148 as an example.

I have included the build dependencies in devDependency as I think it is part of the dev to create the build but that can be discussed.

wkeese commented 8 years ago

To require a node_module installed in delite/node_module during the build you need to use the require.getNodePath function.

OK, thanks, I did that in b7457726dc7e7bf13e0789a9c1f1e7785d620029. Now it's not necessary to install jsdom into sdk_util/build.

wkeese commented 8 years ago

Pushed in 2c0633fde5c8ef8e686b8a64209ae900ff356cba.