ractivejs/rvc

Synchronous Component Load callback?

Closed this issue · 6 comments

I'm using RequireJS and RVC to create a stand alone javascript file. The goal is for it to be self-contained, and not to force anyone who uses it into a specific build / library path (other than Ractive of course). So I'm using AlmondJS to embed RequireJS into the file.

The problem comes when you need to declare the components:

require([ 'rvc!foo' ], function ( Foo ) {
  var ractive = new Foo({ /* ... */ });
});

In the end I want to simply add these components to the global Ractive list of components, so they can be used by a Ractive declared after file is added to the page's scripts.

require(['rvc!foo'], function(Foo) {
    Ractive.components.foo = Foo;
});

So a page using this would add their scripts like this (not the RequireJS way, but my goal is to not force one build path onto my consumers):

<script src="ractive.js"></script>
<script src="optimized-components.js"></script>
<script src="page-logic-where-ractives-are-declared.js"></script>

The problem is the components are created in the callback to require, which appears to be happening after the page-logic-where-ractives-are-declared.js declares it's ractives:

//page-logic-where-ractives-are-declared.js
 var ractive = new Ractive({
        template: "<foo></foo>",
    });

So, is there anyway for me to load the Ractive component synchronously as opposed to in the callback? Or am I forced to write some sort of wait loop until the callback returns?

Actually you can close this. This is handled at the AlmondJs level. It looks like I have some options for wrapping the optimized module to force synchronous loading.

@timshannon Would be interested in what you find, I need to do this for a project, though probably will fo the r.js route...

Here is the option that is supposed to work:

https://github.com/lddubeau/almond-force-sync

Which stems from this issue: requirejs/almond#42

But I haven't actually gotten it to work with my build yet. For now I'm working around it by basically setting forceSync to true in my require calls via this section of almond.js:

...
 requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
...   
     //If relName is a function, it is an errback handler,
        //so remove it.
        if (typeof relName === 'function') {
            relName = forceSync;
            forceSync = alt;
        }

        //Simulate async callback;
        if (forceSync) {
            main(undef, deps, callback, relName);
        } else {
            //Using a non-zero value because of concern for what old browsers
            //do, and latest browsers "upgrade" to 4 if lower value is used:
            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
            //If want a value immediately, use require('id') instead -- something
            //that works in almond on the global level, but not guaranteed and
            //unlikely to work in other AMD implementations.
            setTimeout(function () {
                main(undef, deps, callback, relName);
            }, 4);
        }
...

So my component loading code looks like this:

require(['rvc!foo'], function(Foo) {
    Ractive.components.foo = Foo;
}, null, true);

The true set forceSync, and this appears to work for me, but I'm unsure as to the implications / side effects, especially since I can't find any documentation as to it's use. The links above seem to suggest the wrapping method works, but I haven't yet got it to work for me.

No clue what's going on here to be honest! RequireJS is full of such surprises. In any case, I've had a lot of success using AMDClean rather than Almond (in fact Ractive itself uses it). Same use case (with one or two caveats) - taking a set of AMD modules and producing a bundle that can be used anywhere - but dispenses with require() andefine() etc altogether.

The idea is that you use the RequireJS optimizer as you would normally (omitting almond.js from the build), then take the result and pipe it through AMDClean. In the resulting code, there's no opportunity for a module to be defined asynchronously because there are no 'modules'.

Nice, AMDClean looks perfect.

Thanks,

For future reference if anyone is interested, I was able to build a stand alone build components file. My goal was to have a stand alone file, with no dependencies, and also to not build in Ractive into the optimized file (the assumption in my case, that it will actually be added in a separate script tag).

I used AMDClean, and this is what requirejs build file looks like:

{
    name: "components.dev",
    exclude: ["ractive"],
    out: "../../application/core/file/js/components.js",
    stubModules: [ 'rvc' ],
    wrap: {
        start: "(function(ractive) {",
        end: "}(Ractive));"
    },
    skipModuleInsertion: true,
    onModuleBundleComplete: function (data) {
      var fs = module.require('fs'),
        amdclean = module.require('amdclean'),
        outputFile = data.path,
        cleanedCode = amdclean.clean({
          'filePath': outputFile,
        });

      fs.writeFileSync(outputFile, cleanedCode);
    }
}

Note that I was able to exclude ractive by passing in the global Ractive value using the wrap option.