Why Is There No #Include?

Why Is There No #Include?

  • Comments 7

A common and entirely sensible programming practice is to put commonly used utility functions in one file, and then somehow link that file in to many different programs.  In traditional compiled languages you can compile a bunch of utilities into a statically linked .LIB file or a dynamically linked .DLL file.  But what do you do in script, where a program is just text?

 

People often ask me why there is no #include statement in VBScript or JScript.  Wouldn't it be nice to be able to write up a page full of helpful utility functions and then in half a dozen different scripts say  #include "myUtilityFunctions.vbs" ?

 

But if you actually think for a bit about how such a statement would be implemented, you see why we didn't do it.  The reason is pretty simple: the script engine does not know where the current file came from, so it has no way of finding the referenced file.  Consider IE, for example:

 

<script language="vbscript">

#include "myUtilityFunctions.vbs"

' ...

 

How does the script engine know that this page was downloaded from http://www.rezrov.com/gnusto/ ? The script engine knows nothing about where the script came from -- IE just hands the text to the engine.  Even assuming that it could figure it out, the script engine would then have to call IE's code to download files off the internet.  Worse, what if the path was fully qualified to a path in a different domain?  Now the script engine needs to implement IE's security rules.

 

But do any of those factors apply when running in ASP?  No!  ASP would have to have a completely different #include mechanism where the relative path was based on facts about the current virtual root and the security semantics checked for cross-root violations.  What about WSH?  There we're worried about the current directory but have no security considerations.

 

What about third party hosts?  Every host could have a different rule for where the script comes from and what is a legal path.  We cannot possibly put all those semantics into the script engine.  To make an #include feature work, we'd have to define a callback function into the host so that the host could fetch the included script for us.  It's just too much work for too little gain, so we simply declared a rule: the host is responsible for implementing any mechanism whereby script files can be included into the script namespace.  This is why IE and WSH have the <script src= attribute.

  • A great explanation, thanks. My problem was just finding the work-around of using <script src= or for ASP something like <!--#include file="utilites.asp"-->. Took a lot of fumbling around to just find the work-around.
  • Rather than blithely stating: "this problem can't be solved, so we aren't going to think about it anymore", a more positive approach would have been to think a bit outside the box (though not to far outside, as I will show). First, an #include for script would have been immensely useful. The current situation breaks encapsulation and results in brittle code. For example, since I can't include from within a .js file, I need to rely on proper use by whomever is using that file. So, if a.js relies on b.js, there is no way for me to guarantee that both will be included, and in proper order. All I can do is document and hope for the best. Also, as you have shown, most every script host provides some sort of #include: the browser, ASP, WSF, Rhino. Unfortunately, each uses a very different syntax. True, the script engine can't know how to fetch a file, but as shown above, most script hosts do. So, why couldn't IActiveScriptSite define an Include method? Such a method could accept a string resource reference and return an IStream to it, or something like that. Since the host is invoked to construct the stream, it has complete control over the process.
  • That's a _technically_ good idea, and one that we considered. But the problem is that this solution FORCES implementation of a new feature onto the HOST. As a solution provider, we must always ensure that the burden upon the host is as small as possible. And in particular, it is very unpopular to make forward-compatibility-impactful changes on binary interfaces -- particularly changes which our largest customers (IE and IIS) do not even want or need! In software, anything is possible, but not everything is pragmatic. This turned out to be a feature that didn't meet the bar.
  • Eric, I definitely agree that IActiveScriptSite cannot be changed to implement this functionality. You could define an IActiveScriptSite2 interface that the host optionally implements. Query for this interface and use it if it's there. After all, COM/OLE provides a ton of mechanisms for host/component negotiation (that don't require changing binary interfaces). With regard to that method, it could also be made optional with the host allowed to return something like E_NOTIMPL. This is something already allowed for some methods on IActiveScriptSite as I recall. The host should also be allowed to return some error value indicating security problems or issues like that. IE and IIS do provide their own mechanisms, but as I have shown, these mechanisms are somewhat flawed as a result of this deficiency. It would be very straightforward IMO to wire their existing download and activation services to such a facility. Anyway, all this is not to say that I don't appreciate what Microsoft has done for scripting.
  • I think I'm not being clear with why its hard to do this. Sure, obviously the way to do it would be to add another site interface. If you look at the IDL files, we've got like a half dozen in there and I think the SE team just added another. We can add site interfaces all day, that's easy enough. The problems are twofold. First, if the host has a mechanism for resolving include files, the host can surface that mechanism to the user without involving a callback, so why would the host do the work of implementing an unneeded feature? The whole point of having an #include mechanism is to have a standardized mechanism, but unless you force hosts to implement their end of the bargain, it isn't standardized -- and no host we talked to wanted to take that end of the bargain. So the feature would have failed before we even implemented it. Second, there are numerous technical difficulties which you have conveniently handwaved away. Correctly implementing such a beast is incredibly nontrivial in a language as complex and dynamic as JScript or VBScript. This isn't the trivial little C preprocessor we're talking about here. When does the include functionality run? When the script is run? Then we have to dynamically change the global namespace of an already compiled script. Doable, but there are issues. OK, then when the script is parsed? Then we need to rewrite the parser into a multi-pass parser. Again, doable. But how long would it take? Other problems surface. How do we detect circular includes? Remember, the host is responsible for implementing the include moniker semantics, so we would have to define ANOTHER callback, a highly complex one, that could determine whether a given set of nested includes constituted a circle or not. Now the hosts are even less likely to want this feature, but if they don't do it, then someone writes a web page that includes itself and the process goes into an expensive recursive death spiral. And let's not forget: how exactly does this feature interact with the script debugger? The script debugger allows hosts to specify the context in which a script was supplied. So now we need ANOTHER callback whereby the debugger can identify to the host the partial moniker used to include the file, in the event that the host wishes to show a context around that file. Etc, etc, etc. I could give you deep technical problems all night. Believe me, it gets to be a huge awful mess and we'd end up rewriting most of the parsing code and a lot of the debugger support. This is a complex, difficult, expensive feature that no host wanted or needed, and we only have a finite amount of people and time. Imagine you were faced with a choice: on the one hand, implement a complex, difficult, expensive feature that IE, ASP and WSH do not want or need so that developers have the incredibly trivial benefit of being able to type <script> #include "foo.js" instead of <script src="foo.js"> On the other hand, take the time that it would have taken to implement that feature and instead do a security review, review the documentation and sample code, fix memory leaks and other bugs, start planning the next version, etc. It's a no-brainer. Making these tough choices is the only way that quality software ever gets shipped. There are no free features, and EVERY feature you implement takes time and brain cycles away from other features like security, performance, robustness, maintainability, documentation, etc. And those are all MUCH more important than a trivial syntactic sugar.
  • I definitely agree that any sensible software development shop needs to make sane, reasonable business choices about which features get included and which do not. A development team I am in charge of is in the process of finalizing a product release and I'm chopping features left and right in order to meet the release schedule. And this is not the first time I've had to do this. But lets not confuse business issue with technical ones. While it may not make business sense not to implement this feature, it does make technical sense to implement it. First, by your own admission in the original post, this is a feature that has been requested often: "People often ask me why there is no #include statement in VBScript or JScript". Thus you can't say that nobody wants it. Even if host *implementers* do not want it, because it may add a bit to their workload and they feel they have provided an appropriate, though proprietary, alternative, it's a question you should ask *people who use the host*. That is, not IE's developers but people writing scripts for IE. Also, as I have pointed out. It could easily enough be made and optional feature that a host could implement or not. As for resolving resource location, you are correct that different hosts may implement different methods. But: 1. I assume most Windows-based hosts would rely on IMoniker services so they would all pretty much work the same. 2. I also assume that in most cases relative paths would be used, which make this pretty much a non-issue. 3. Standardizing the mechanism is only one potential benefit. As I've shown there are others. I have not "hand waved away" the technical difficulties, I've ignored them ;-) After all, this is not my project and I don't need to do the work or foot the bill. But I will point out the Rhino does provide such a mechanism (the load command), so apparently it is doable. And Rhino being open source, there was no one to pay the bill anyway. Must you really detect circular includes? A script writer does not need to do that in order to "jam" the script engine. She can simply write an infinite loop. And you have sensibly enough provided a mechanism to detect this a allow the user to suspend the script. Since the include command is a script command, the same mechanism should work in this case as well. With regard to the IE script snippet: 1. Interestingly enough CSS does provide such a facility in the form of the @import property, despite providing an alternative mechanism through the <link> tag. Exactly the same scenario. Yes, I know JScript is more complex than CSS, but it's the principal I'm talking about. 2. An include statement would be redundant in an *inline* script, but would be immensely useful in an *external script file*. I've developed a JavaScript library that, because of its complexity, is broken up into several interdependent files. But because there is no include command, I can't express this interdependency. Instead I must rely on the end user to include all the needed files in proper order (a Rhino user simply includes one file and gets everything). Bottom line, it is a useful feature IMO. It is also very doable (it has been done after all). It may be hard to do, and may not make business sense for you. Given your reply, I can see that it will probably never be done, unless it's added to the ECMAScript standard. That's OK, I didn't expect it to. Eric, I didn't mean to get on your case. As I've stated, I very much appreciate what you guys have done for scripting. I've used your tools in quit a number of cool applications. Isn't it a typical end-user to always ask for more ;-)
  • Well, there is no '#include'.. That's right, as for me--module inclusion it is too dependent on a host.. (At least when it comes to decide whether to create an instance of the IActiveScript interface for every such inclusion.. And as a result of positive decision must think how to combine 2 distinct execution contexts.. Ahhr, it is not me--a big specialist in all that matters, i'm just a user of one of such hosts that supports inclusion by custom '\\USEUNIT myfile.js' construct.. very suspicious design--it would be much better to exclude this functionality from the source completelly..)

    But after aLL, what is the purpose of @set, @cc_on, @if, etc., statements? Are we dealing with complete different meta-language over the core JS syntax? Or is it just another context for the same JS syntax?
Page 1 of 1 (7 items)