JSAN.use() and x-browser caching strategies

0 views
Skip to first unread message

Rob Kinyon

unread,
Sep 9, 2005, 9:50:25 PM9/9/05
to js...@googlegroups.com, jsan-a...@googlegroups.com, jsan-...@googlegroups.com, jsan-t...@googlegroups.com
First off, I want to apologize for any comments I have made in the
past that sound like I know what I'm talking about vis-a-vis this
topic. While I know JS as a language, I don't know JS as a technology
as well as I need to, especially when it comes to browser-dependent
behavior. In addition, if anyone wants to correct me in anything I
say, please do so without fear that you will hurt my feelings.

Second, this is a LONG email. Before responding, please take the time
to read it all. If you want to respond to one part, please consider
creating another thread (with a different subject) that refers back to
this one. The subject is how GoogleGroups threads responses.

Recently, I've been working with Casey West and Adam Kennedy on
getting JSAN.js to reduce the number of XMLHttpRequests it makes.
Currently, it uses XMLHttpRequest + eval() (hereafter known as the XHR
method) to do the load of a given library. Some browsers, such as IE,
will cache the results of an XMLHttpRequest if the right settings have
the right values. Other browsers, such as Firefox, seem to refuse to
do so, regardless of what's attempted. This means that a single
JSAN.use() statement may result in 3-10 XHRs per pageload, even if the
code in question has already been previously seen in a prior page.

This topic was brought up in the first days of this project, and
various strategies have been suggested and some have been implemented.
However, I cannot find a single document that discusses them, their
strengths and drawbacks, and what's needed to implement them from a
user's perspective. I'd like to remedy that. Hopefully, this thread
will get enough information in one place so that we, as a group, can
make up for the shortcomings in Javascript. (Who designs a dynamic
language in the modern era without making provision for libraries??
COBOL has libraries ... why doesn't Javascript?!?)

There are two caching levels, as I see it.
1) Page-level : this is where the same library is requested only once
per page load.
2) Session-level : this is where the same library is requested only
once per browser session. All session-level strategies are, by
definition, also page-level strategies.

The strategies, as I know them, consist of the following:
1) XHR - this is the strategy currently implemented in JSAN.js v0.10.
It depends completely on whether the browser caches XMLHttpRequests
for its session-level caching. It does provides for something similar
to Perl's %INC, page-level caching is there. This requires nothing
special on the server or client.

2) Serverside strategies - this is epitomized in Dave Rolsky's
JSAN::ServerSide, available on CPAN. Basically, the webserver will
modify the outgoing page so that all the dependencies are in the page
as <script> tags, in the right order. All modern browsers cache the
results of <script> tags, so this provides for session-level caching.
This requires mod_perl on the server.

3) Install-time concatenation - this is where the installer
(JSAN::Shell or JSAN::Shell2) would modify the library so that it
contained all of its dependencies within it. The user would then load
the library in a <script> tag, triggering the browser's session-level
caching. I'm not sure how this would play out, especially if a
dependency is updated or if a dependency is also loaded.

4) JS header files - this is a second file containing the dependencies
would be created, either manually or with some tool. JSAN.use() would
then be rewritten to use either DOM injection or document.write() to
add TWO <script> tags on the fly. The first would be the header (.jsh)
file and the second would be the code (.js) file. This similar in
concept to the bootstrap script that MochiKit uses. This requires
nothing from the user, but it does require some work on the part of
the developer. In addition, some scenarios still don't work, such as:
<script>
JSAN.use('Foo.Bar');
var x = new Foo.Bar();
</script>

If I missed anything, please add it.

I'm not looking for the "One True Way"(tm). Several of these
strategies can be used in tandem. What I am looking for is group
consensus on the "One True Way"(tm) for each of these strategies, and
also if the group feels that a strategy sucks too much, that it should
be dropped. I feel we need to bring the group's talents to bear on a
few good methods, so that we can speed adoption and get this monkey
off the project's back. The first question people ask me is "What's
performance like?" and I have to tell them "It can suck, depending on
the client." At that point, I lose them, and that's a "Bad Thing"(tm).

Thanks,
Rob

js...@ali.as

unread,
Sep 9, 2005, 11:56:57 PM9/9/05
to jsan-a...@googlegroups.com, js...@googlegroups.com, jsan-...@googlegroups.com, jsan-t...@googlegroups.com
You can find a description of how the server side modes work in the wiki

http://master.openjsan.org/documentation/wiki/index.cgi?UseServer

Secondly, it's not likely you do concat at install time, rather you
would take a list of needed modules for a website and create a master
site.js for the whole site, perhaps with separate page-specific ones if
things get heavy.

Adam K

Dave Rolsky

unread,
Sep 10, 2005, 3:05:42 AM9/10/05
to jsan-a...@googlegroups.com, JSAN Users, jsan-...@googlegroups.com, jsan-t...@googlegroups.com
[ Feeling guilty about cross-posting to 4 lists ... ]

On Fri, 9 Sep 2005, Rob Kinyon wrote:

> 2) Serverside strategies - this is epitomized in Dave Rolsky's
> JSAN::ServerSide, available on CPAN. Basically, the webserver will
> modify the outgoing page so that all the dependencies are in the page
> as <script> tags, in the right order. All modern browsers cache the
> results of <script> tags, so this provides for session-level caching.

Note that this is still missing support for JSAN exporting. I really need
to work with Casey on that some time now that we are cow-orkers.

> This requires mod_perl on the server.

Does not! Though it'll certainly be more efficient this way. There's
nothing mod_perl specific in JSAN::ServerSide.

> 4) JS header files - this is a second file containing the dependencies
> would be created, either manually or with some tool. JSAN.use() would
> then be rewritten to use either DOM injection or document.write() to
> add TWO <script> tags on the fly. The first would be the header (.jsh)
> file and the second would be the code (.js) file. This similar in
> concept to the bootstrap script that MochiKit uses. This requires
> nothing from the user, but it does require some work on the part of
> the developer. In addition, some scenarios still don't work, such as:

I'm not sure who you mean by "user". If you mean consumer of a JSAN
library, then it does require extra work on their part, becase they have
to keep their own headers up to date if they want to use JSAN in their own
code.

> I'm not looking for the "One True Way"(tm). Several of these strategies
> can be used in tandem. What I am looking for is group consensus on the
> "One True Way"(tm) for each of these strategies, and also if the group
> feels that a strategy sucks too much, that it should be dropped. I feel
> we need to bring the group's talents to bear on a few good methods, so
> that we can speed adoption and get this monkey off the project's back.
> The first question people ask me is "What's performance like?" and I
> have to tell them "It can suck, depending on the client." At that point,
> I lose them, and that's a "Bad Thing"(tm).

I would say the following is a good path to pursue:

- Recommend a server-side approach for people doing dyanmic server-side
dev. The server-side implementation is fairly trivial, and should be
relatively easy to encapsulate in libraries for various server side
languages.

I think this is the best approach when possible because it makes JS
development very simple. JS code needs no extra pre-processing to be
deployed, and error messages come from the expected files (unlike XHR).

It also means that each page only loads the JS it needs, which I think is
useful. I like to know what server-side code paths require a given JS
library.

- Recommend header files + script tag injection for pure HTML + JS
development. This requires creating a header generating tool, but that's
no harder than writing the server-side dependency generator (in fact, it's
mostly the same code).

This seems simpler to me than concatenation, which would tend to generate
monster script files. Concatenation also akes it hard to see what a given
page is using, since every page would load everything, and it will
seriously obscure debugging.

I also wonder if concatenation will have scoping issues, but
I don't understand of scoping in JS 100%.

- Support XHR as a fallback for quick prototyping. It's the easiest way
to get up and running with JSAN. I think making it clear that this is
undesirable for production is fine, as long as migration paths to other
import methods are well-documented.


-dave

/*===================================================
VegGuide.Org www.BookIRead.com
Your guide to all that's veg. My book blog
===================================================*/

Laurie Harper

unread,
Sep 10, 2005, 10:36:21 AM9/10/05
to jsan-a...@googlegroups.com, Laurie Harper
Being new to both Javascript and JSAN, I've been following this
discussion with interest. I'm trying to understand why an
'obvious' (to me) approach hasn't come up in the discussion so far,
so let me present it as a straw man:

I can use the JSAN libraries today without worrying about XHR issues
simply by including a <script/> tag per dependency in each page.
Provided each library documents its dependencies this represents
little overhead. As with C library documentation, the SYNOPSIS
section of each library's documentation could begin, for example,

<script src="/jsan/Display.js"/>
<script src="/jsan/Display/Only.js"/>

... existing library usage docs ...

As a JSAN user, I would know to include the Display and Display.Only
scripts as pre-requisites
when using that library. OK, there'd need to be a documented
convention for how the dependencies were represented since assuming a
"/jsan" path prefix is probably not appropriate, but you get the idea.

This shifts the onus to the JSAN user (developer) to ensure
dependencies are met. It seems like this would solve all the issues
of what browsers have what behaviours around DOM injection, caching
of XMLHttpRequest, etc.

That's the strawman argument. Why does this not work? Is it just a
desire to make dependency resolution and loading automatic?

Sorry if this seems like a troll post, I'm just trying to understand
the goals and issues.

Thanks,

L.
--
Laurie Harper
Open Source advocate, Java geek: http://www.holoweb.net/laurie
Founder, Zotech Software: http://www.zotechsoftware.com/



Dave Rolsky

unread,
Sep 10, 2005, 11:04:07 AM9/10/05
to jsan-a...@googlegroups.com
On Sat, 10 Sep 2005, Laurie Harper wrote:

> I can use the JSAN libraries today without worrying about XHR issues simply
> by including a <script/> tag per dependency in each page. Provided each
> library documents its dependencies this represents little overhead. As with C
> library documentation, the SYNOPSIS section of each library's documentation
> could begin, for example,
>
> <script src="/jsan/Display.js"/>
> <script src="/jsan/Display/Only.js"/>
>
> [snip]
>
> That's the strawman argument. Why does this not work? Is it just a desire to
> make dependency resolution and loading automatic?

Sure, this works fine. I think for those of who spend a lot of time
programming in languages with built-in library support (that is, basically
any language you might ever use server-side, including C, Perl, shell,
Java, Ruby, etc) the idea of having to manually manage one's dependencies
like this seems unpleasant.

I'm so used to just writing this in Perl:

use Display;
use Display::Only;

Perl tracks down the appropriate files, compiles them, and they are
available immediately in the code that loads them. This sort of support
encourages you to re-use existing code in the form of libraries.

Javascript, OTOH, has no such thing, and as a result code re-use, and in
particular _sharing_ one's libraries with others, is very ad-hoc. JSAN is
an effort to change that. The JSAN repository is one part of that effort,
as is trying to find ways to add sane module importing semantics to the
base language.

Laurie Harper

unread,
Sep 10, 2005, 12:46:48 PM9/10/05
to jsan-a...@googlegroups.com, Laurie Harper
On 10-Sep-05, at 11:04 AM, Dave Rolsky wrote:
> Sure, this works fine. I think for those of who spend a lot of
> time programming in languages with built-in library support (that
> is, basically any language you might ever use server-side,
> including C, Perl, shell, Java, Ruby, etc) the idea of having to
> manually manage one's dependencies like this seems unpleasant.
>
> I'm so used to just writing this in Perl:
>
> use Display;
> use Display::Only;
>
> Perl tracks down the appropriate files, them, and they are
> available immediately in the code that loads them. This sort of
> support encourages you to re-use existing code in the form of
> libraries.
>
> Javascript, OTOH, has no such thing, and as a result code re-use,
> and in particular _sharing_ one's libraries with others, is very ad-
> hoc. JSAN is an effort to change that. The JSAN repository is one
> part of that effort, as is trying to find ways to add sane module
> importing semantics to the base language.

So it's the difference between writing a <script/> tag in the
'calling' page vs. writing use('foo') in the called Javascript file?
Ah, I see, if each lib includes 'use's, I don't need the <script/>
tags for its dependencies anymore. OK.

So there are two things going on here:

1) provide a set of reusable component libraries (and a way of
packaging lumps of Javascript into such libraries)

2) provide a mechanism for code in one library to 'import' code it
depends on automatically

(1) is working well, and (2) is what all the discussion is about, right?

If I've understood correctly, maybe it makes sense to split these
two aspect out. First, allow the simple usage pattern where the
developer must include the appropriate set of <script/> tags to load
all the component libraries and their dependencies. This would work
today for every browser and every server-side technology from plain
HTML to CGI to web applications; anyone could use JSAN the same way
regardless of what server-side tools they were using.

Dependency resolution can then be added as a separate component. So,
by default, JSAN.use() would be implemented as a no-op (or a check to
see if the dependency had been loaded that just throws an error if
not). If you want to automatic dependency resolution you'd do
something like

<script src="/js/JSAN.js"/>
<script src="/js/JSAN/Resolver/XHRResolver.js"/>

where there could be seperate resolver implementations for XHR, DOM
insertion, etc. JSAN.use() would do automatic dependency loading only
if you'd included/loaded an appropriate resolver implementation.

That way, the different strategies being discussed can be
experimented with independent of the growth of JSAN itself. And if
some future version of Javascript was to introduce an import/include
mechanism, you'd just need to add a new resolver implementation.

Is this making any sense, or am I still missing the point? :-)

L.

Dave Rolsky

unread,
Sep 10, 2005, 3:55:56 PM9/10/05
to jsan-a...@googlegroups.com, Laurie Harper
On Sat, 10 Sep 2005, Laurie Harper wrote:

> So there are two things going on here:
>
> 1) provide a set of reusable component libraries (and a way of packaging
> lumps of Javascript into such libraries)
>
> 2) provide a mechanism for code in one library to 'import' code it depends on
> automatically

That is correct.

> (1) is working well, and (2) is what all the discussion is about, right?

Yes.

> If I've understood correctly, maybe it makes sense to split these two aspect
> out. First, allow the simple usage pattern where the developer must include
> the appropriate set of <script/> tags to load all the component libraries and
> their dependencies. This would work today for every browser and every
> server-side technology from plain HTML to CGI to web applications; anyone
> could use JSAN the same way regardless of what server-side tools they were
> using.

You can already do that today with JSAN-using libraries, you simply have
to mock out JSAN's interface:

<script type="text/javascript">
JSAN = {};
JSAN.use = function () {};
</script>

Now any libraries you load via <script> tags than call JSAN.use will
continue to work, as long as they don't expect JSAN's exporting. In the
future I'm hoping JSAN itself will have a "do exports but don't try to
load stuff mode" so we can do:

<script src="/js/JSAN.hs" />
<script type="text/javascript">
JSAN.noLoad = 1;
</script>

> Dependency resolution can then be added as a separate component. So, by
> default, JSAN.use() would be implemented as a no-op (or a check to see if the
> dependency had been loaded that just throws an error if not). If you want to
> automatic dependency resolution you'd do something like
>
> <script src="/js/JSAN.js"/>
> <script src="/js/JSAN/Resolver/XHRResolver.js"/>
>
> where there could be seperate resolver implementations for XHR, DOM
> insertion, etc. JSAN.use() would do automatic dependency loading only if
> you'd included/loaded an appropriate resolver implementation.

I think the above method of always loading JSAN and then setting a
parameter is probably simpler. More files means more pieces to go
missing. And given browser caching, loading JSAN.js once is not a big
deal.

> Is this making any sense, or am I still missing the point? :-)

I think you're on the point, but maybe missing some of the work that
already exists. You should take a look at JSAN.js itself, as well as my
JSAN::ServerSide Perl module (on CPAN). Those demonstrate two
loading/dependency resolution mechanisms.

The "manual" method needs no additional code, just docs, AFAICT.

Laurie Harper

unread,
Sep 10, 2005, 4:40:00 PM9/10/05
to jsan-a...@googlegroups.com, Laurie Harper
OK, so I guess I'm suggesting making this (or something like it) the
default and documenting manual dependency loading (via script tags)
as the 'standard' way to use JSAN, at least until the means of
automating this are well understood, tested, etc.

> Now any libraries you load via <script> tags than call JSAN.use
> will continue to work, as long as they don't expect JSAN's
> exporting. In the future I'm hoping JSAN itself will have a "do
> exports but don't try to load stuff mode" so we can do:
>
> <script src="/js/JSAN.hs" />
> <script type="text/javascript">
> JSAN.noLoad = 1;
> </script>

Yeah, I'd left exporting out of the discussion for simplicity's sake
so far. So I'm suggesting JSAN provide the 'do exports but don't try
to load stuff mode' by default and you'd have to do additional work
to turn loading on, rather than to turn it off.

>> Dependency resolution can then be added as a separate component.
>> So, by default, JSAN.use() would be implemented as a no-op (or a
>> check to see if the dependency had been loaded that just throws an
>> error if not). If you want to automatic dependency resolution
>> you'd do something like
>>
>> <script src="/js/JSAN.js"/>
>> <script src="/js/JSAN/Resolver/XHRResolver.js"/>
>>
>> where there could be seperate resolver implementations for XHR,
>> DOM insertion, etc. JSAN.use() would do automatic dependency
>> loading only if you'd included/loaded an appropriate resolver
>> implementation.
>>
>
> I think the above method of always loading JSAN and then setting a
> parameter is probably simpler. More files means more pieces to go
> missing. And given browser caching, loading JSAN.js once is not a
> big deal.

Agreed; I suggested the other way to make it easier to experiment
with multiple alternative loading strategies without having to rev
the core framework all the time. I.e. JSAN.js could itself be stable
as auto-loading implementations evolved. An alternative could be to
have two parallel JSAN.js implementations, one with no auto-loading
built in and one with, so you'd choose the 'experimental' behaviour
by changing your single <script> tag.

>> Is this making any sense, or am I still missing the point? :-)
>
> I think you're on the point, but maybe missing some of the work
> that already exists. You should take a look at JSAN.js itself, as
> well as my JSAN::ServerSide Perl module (on CPAN). Those
> demonstrate two loading/dependency resolution mechanisms.
>
> The "manual" method needs no additional code, just docs, AFAICT.

Per above, it needs a little more to allow exports to continue to
work and requires the developer to do something 'special' to turn off
loading / dependency resolution. Maybe that's just not an issue and I
should shut up ;-)

Reading the discussion it seems to me that JSAN would be easier to
sell based on its current merits and a 'plus, there's also
experimental support for doing automatic dependency resolution'.
Particularly since you then wouldn't have to 'admit' to any issues
with performance, browser caching, etc. in JSAN itself.

Again, I may be barking up an irrelevant tree here, in which case
feel free to say 'go away' :-)

Rob Kinyon

unread,
Sep 12, 2005, 9:30:20 AM9/12/05
to jsan-a...@googlegroups.com, Laurie Harper
[snip]
> OK, so I guess I'm suggesting making this (or something like it) the
> default and documenting manual dependency loading (via script tags)
> as the 'standard' way to use JSAN, at least until the means of
> automating this are well understood, tested, etc.

This doesn't work, for reasons I'll lay out below.

[snip]
> Per above, it needs a little more to allow exports to continue to
> work and requires the developer to do something 'special' to turn off
> loading / dependency resolution. Maybe that's just not an issue and I
> should shut up ;-)
>
> Reading the discussion it seems to me that JSAN would be easier to
> sell based on its current merits and a 'plus, there's also
> experimental support for doing automatic dependency resolution'.
> Particularly since you then wouldn't have to 'admit' to any issues
> with performance, browser caching, etc. in JSAN itself.

The issue with your idea is that the effort involved in tracking down
all the dependencies is going to be large. JSAN is currently quite
small, with 100ish distributions. But, we look to CPAN as our model.
CPAN has, I think, 70,000+ distributions. Some distributions have,
once you expand the tree, over 100 dependencies. These aren't crackpot
distributions. The actual library you're wanting to use may only have
2-3 dependences. But, you might have a really deep tree, going back
maybe 15 layers. JSAN::Shell is a good example of this.

Plus, you would have to keep up with all changes to every single
library in the whole dependency chain. If someone upgrades a library 3
layers back and it now has a new dependency, your application that
used to work suddenly breaks, and it's not clear why.

This is the kind of thing that computers should be handling, not
people. JSAN::ServerSide is a great solution. JSAN.js using XHR is a
great solution, if only all browsers would cache correctly. My headers
idea is a decent solution, though it requires more work.

> Again, I may be barking up an irrelevant tree here, in which case
> feel free to say 'go away' :-)

Please don't go away! :-) New ideas and viewpoints are always welcome.
Please continue asking what look to be the "dumb" questions. I'm
finding that a lot of the answers to those questions tend to be "Cause
Microsoft and Mozilla are stoopid in different ways."

Rob

Laurie Harper

unread,
Sep 12, 2005, 9:52:30 AM9/12/05
to jsan-a...@googlegroups.com
On 12-Sep-05, at 9:30 AM, Rob Kinyon wrote:
> This doesn't work, for reasons I'll lay out below.
>
> [...]
>
> The issue with your idea is that the effort involved in tracking down
> all the dependencies is going to be large. JSAN is currently quite
> small, with 100ish distributions. But, we look to CPAN as our model.
> CPAN has, I think, 70,000+ distributions. Some distributions have,
> once you expand the tree, over 100 dependencies. These aren't crackpot
> distributions. The actual library you're wanting to use may only have
> 2-3 dependences. But, you might have a really deep tree, going back
> maybe 15 layers. JSAN::Shell is a good example of this.
>
> Plus, you would have to keep up with all changes to every single
> library in the whole dependency chain. If someone upgrades a library 3
> layers back and it now has a new dependency, your application that
> used to work suddenly breaks, and it's not clear why.
>
> This is the kind of thing that computers should be handling, not
> people. JSAN::ServerSide is a great solution. JSAN.js using XHR is a
> great solution, if only all browsers would cache correctly. My headers
> idea is a decent solution, though it requires more work.

Yep, I absolutely agree. Requiring manually managing all those
<script> tags clearly wouldn't scale. I guess I was proposing it as a
short-term default until the details of how best to do dependency
resolution are hashed out. On reflection, perhaps that doesn't make
sense.

I do think having the option to turn it off and/or to force a
particular strategy would be worth while though. But as you pointed
out, JSAN is 90% of the way there already. Maybe I'll see if I can
refactor JSAN.use() so it's easy to turn off loading but keep exporting.

> Please don't go away! :-) New ideas and viewpoints are always welcome.
> Please continue asking what look to be the "dumb" questions. I'm
> finding that a lot of the answers to those questions tend to be "Cause
> Microsoft and Mozilla are stoopid in different ways."

<grin> OK...!

Rob Kinyon

unread,
Sep 12, 2005, 10:45:01 AM9/12/05
to jsan-a...@googlegroups.com
> I do think having the option to turn it off and/or to force a
> particular strategy would be worth while though. But as you pointed
> out, JSAN is 90% of the way there already. Maybe I'll see if I can
> refactor JSAN.use() so it's easy to turn off loading but keep exporting.

The loading section is JSAN.require(). JSAN.use() calls require(),
then calls a few other items after that, which is what actually does
the exporting.

Maybe, the correct refactor is to make JSAN.use() look like:
function use {
var args = arguments;
require(args[0]);

args.shift();
export.apply(args);
}

Then, create an export() that DWIMs.

Rob

Laurie Harper

unread,
Sep 15, 2005, 9:26:05 PM9/15/05
to jsan-a...@googlegroups.com, Laurie Harper
Sounds about right :-) I'll have a look at it when I have a sec...
Reply all
Reply to author
Forward
0 new messages