BladeRunnerJS / brjs

BladeRunnerJS (BRJS) is an open source development toolkit and framework for modular construction of large single-page HTML5 apps. It consists of a set of conventions, supporting tools and micro-libraries that make it easy to develop, test, deploy and maintain complex JavaScript apps.
http://bladerunnerjs.org/
GNU Lesser General Public License v3.0
229 stars 36 forks source link

Provide a ContentPlugin interface so plugins can be written to support things like an appcache manifest #241

Closed stevesouth closed 10 years ago

stevesouth commented 10 years ago

How would I allow my application to use the appcache?

In order for some of your applications files to be cached for offline use you can use the appcache.

https://developer.mozilla.org/en/docs/HTML/Using_the_application_cache

How would I go about augmenting the bladerunner war step to add the required information? Two things need to change.

1) A new tag added into your index.html file

2) The example.appcache file needs to be created, listing the files you want cached. CACHE MANIFEST index.html header.png blah/blah I think the simplest way is to only modify the html tag during deployment and alway have the appcache file available, maybe in unbundled resources. However the second issue is that it is a bit difficult to figure out what you want cached as the bundles / images etc will move and be renamed. As a first step maybe this can be covered by a tutorial but it would be nice to automate it somehow.
dchambers commented 10 years ago

Hi Steve,

An additional consideration here, is that developing an AppCache application is really painful if the AppCache is enabled in development, and so it's critical that all AppCache functionality is short-circuited within development -- I talked about this in a blog post I wrote some time ago: http://blog.caplin.com/2011/02/14/taming-the-app-cache/. As it happens, an AppCache plug-in can probably be implemented by creating a 'bundler', of all things, by implementing the BundlerPlugin interface so that it returns a manifest within the handleRequest() method. The interface is as follows:

public interface BundlerPlugin extends TagHandlerPlugin {
    String getMimeType();
    FileSetFactory getFileSetFactory();
    RequestParser getRequestParser();
    List<String> generateRequiredDevRequestPaths(BundleSet bundleSet, String locale) throws BundlerProcessingException;
    List<String> generateRequiredProdRequestPaths(BundleSet bundleSet, String locale) throws BundlerProcessingException;
    void handleRequest(ParsedRequest request, BundleSet bundleSet, OutputStream os) throws BundlerProcessingException;
}

The fact you need to create a 'bundler' to do this is clearly wrong, and may help us extract a subset of this interface for doing more general 'servlet' type stuff like this. Of course, a server-side plugin like this makes it trivial to ensure the manifest always has the correct values, as you can do this by querying the bundlers to determine the set of the resources that the page (and therefore the manifest) must contain.

If you fancy the challenge, this would be a good plug-in to try writing since it should be do-able in a day or two, and would provide good feedback for what we are trying to achieve.

-- Dominic

dchambers commented 10 years ago

Here's an example of how a ServletPlugin interface could be extracted out of the current BundlerPlugin interface:

public interface ServletPlugin extends Plugin {
    RequestParser getRequestParser();
    void handleRequest(ParsedRequest request, BundleSet bundleSet, OutputStream os, HttpResponse httpResponse) throws BundlerProcessingException;
}

public interface BundlerPlugin extends ServletPlugin, TagHandlerPlugin {
    String getMimeType();
    FileSetFactory getFileSetFactory();
    List<String> generateRequiredDevRequestPaths(BundleSet bundleSet, String locale) throws BundlerProcessingException;
    List<String> generateRequiredProdRequestPaths(BundleSet bundleSet, String locale) throws BundlerProcessingException;
}

Apart from shunting a couple of methods into a new interface, we add an HttpResponse parameter, which could conceivably allow arbitrary HTTP response headers to be set -- in our case, the Content-Type so that we can short-circuit the App-Cache in development.

This arrangement also feels nicer, since the bundler interface is now more compatible with the single-responsibility principle; all tag handing and serving now being taken care of elsewhere.

-- Dominic

andy-berry-dev commented 10 years ago

Would we want ServletPlugin to also extend TagHandlerPlugin? The manifest plugin could then be responsible for writing the manifest tag in to the page, it might also choose to only write it out in prod, but not in dev.

dchambers commented 10 years ago

Hi Andy, yes I think I'm coming to a similar-ish conclusion (that the AppCache plugin would need to implement both ServletPlugin and TagHandlerPlugin), but I'm not sure that ServletPlugin should therefore extend TagHandlerPlugin.

I think my original idea that ServletPlugin would introduce HTTP headers is a bad idea though since logical serve requests can happen even when HTTP isn't involved, which is another reason a TagHandlerPlugin would be perfect for preventing AppCache from happening in dev.

andy-berry-dev commented 10 years ago

the AppCache plugin would need to implement both ServletPlugin and TagHandlerPlugin

That sounds sensible.

since logical serve requests can happen even when HTTP isn't involved

What about the mime type for the manifest once the app has been exported?

dchambers commented 10 years ago

What about the mime type for the manifest once the app has been exported?

Great point! For completely flat-file exports we have no other option but to ensure all files have the correct extension, and hope that the web-server being used knows the Mime-Type for these extensions. For richer exports (like War files) then we will know the Mime-Type anyway, so everything will be fine provided the exported does the right thing.

andy-berry-dev commented 10 years ago

Are we planning to use a filter to set the mime type in a War or can we just rely on the app server to set the mime type? In both scenarios, web server or app server, config can be added to tell the server what the mime type is based on the extension used.

dchambers commented 10 years ago

I hadn't put any thought into it, but think we can take a 'wait and see' approach since we may know more by then.

dchambers commented 10 years ago

Oh, BTW, I recently updated all of our UML diagrams so they correlate more closely with reality again, so took the opportunity to split out a ServletPlugin from BundlerPlugin at the same time:

andy-berry-dev commented 10 years ago

We've created a ContentPlugin interface that, at the moment, has the following interface.

public interface ContentPlugin extends Plugin {
        String getMimeType();
        ContentPathParser getContentPathParser();
        void writeContent(ParsedContentPath path, BundleSet bundleSet, OutputStream os) throws BundlerProcessingException;
        List<String> getValidDevRequestPaths(BundleSet bundleSet, String locale) throws BundlerProcessingException;
        List<String> getValidProdRequestPaths(BundleSet bundleSet, String locale) throws BundlerProcessingException;
}

writeContent is the place where most of the work is done, that's where you would write out the manifest file, probably making calls to other ContentPlugin's getValidDevRequestPaths/getValidProdRequestPaths to get the URL's for bundles etc.

Custom ContentPlugins will probably also TagHandlerPlugins so they can be used within index pages via a logical tag (e.g. <@app.manifest@>).