Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

[SugarCube 2.9.0] Including External JavaScript Libraries

edited September 2016 in Help! with 2.0
Hello,
Is there a way to load external JavaScript libraries that will be used throughout the story in SugarCube 2? As a specific example, I'd like to make use of the Moment.js library (http://momentjs.com/), though I'm interested in the process of adding libraries in general.

I have tried throwing the contents of their non-minified JS file into a [script] passage, but this yields nonspecific startup errors that change depending on how the story is compiled (TweeGo or Twee2, I know the latter is unsupported.) and which browser runs the resulting HTML (Chromium or Firefox). So I'm assuming that this not the correct way to do it.

Thank you for your time.

Comments

  • edited September 2016
    Yes, it's easy in Twine 1, kinda awkward in Twine 2.

    In Twine 1, you can do it two ways:

    1. Go into header.html in the sugarcube/targets directory and put the javascript code to be loaded inside <script></script> tags in the head section (above the body). If using it directly (ie putting all the code in between script tags, and not simply referencing an external js file), make sure to use the min.js file, not the unminified one. Putting unminified js code into your heaader.html is bad.

    2. Create a userlib.js file in the targets directory and put the code in there.


    However, this functionality was removed from Twine 2 so you do it in one of two ways:

    1. The first way is to build the story in Twine 2, then open the built html file containing your game with a text editor, and place the <script></script> tags in the header section as described above. This will import the moment.js code and all your js in the game that relies on moment.js will work.

    2. Fork the SugarCube 2 story format from GitHub and edit the header.html file to create your own custom version with the <script></script> tags in the head section of the header.html. Then install your custom version of SugarCube as normal.

    For workflow reasons, number 2 is probably the best approach, but you'll need to create new custom versions of sugarcube for each new release TME releases.


    As a comment, you don't put js libraries into the script passages because this places the code inside the body section, not the head.
  • Kokonoe wrote: »
    Is there a way to load external JavaScript libraries that will be used throughout the story in SugarCube 2? As a specific example, I'd like to make use of the Moment.js library (http://momentjs.com/), though I'm interested in the process of adding libraries in general.
    Define what you mean by "load external". You have a few options:
    1. Bundling a 3rd-party library into your project via a scripting passage—e.g. a script-tagged passage in Twine 1 style compilers or Story JavaScript in Twine 2 style.
    2. Bundling a 3rd-party library with your project as part of the compiled story format itself.
    3. Including a 3rd-party library with your project by loading it locally or over the network.


    1. Bundling a 3rd-party library into your project via a scripting passage
    If you're talking about bundling a 3rd-party library into your project, then that's easy enough, if not perfectly simple. The core issue is that many libraries make assumptions about the environment they're going to be evaluated within and the standard method used by most story formats for running scripts is not immediately compatible with those assumptions—for one reason or another. That said, it is fairly trivial to write a small wrapper to give those libraries a compatible environment. For example, the follwing wrapper should work in most cases:
    (function (window, define, exports) {
    
    /* paste 3rd-party library here */
    
    }).call(window, window);
    
    Pros: Wrapping the library and pasting it into a scripting passage works with any compiler, story format, and usage scenario.
    Cons: None, really.


    2. Bundling a 3rd-party library with your project as part of the compiled story format itself
    If you're talking about including a 3rd-party library with your project as part of the story format, either bundled in or as a separate local file, then that's possible. How difficult it is in practice depends on which compiler, and story format, you use.

    Compilers that use Twine 2 style story formats (Twine 2, Twee2, grunt-entwine, grunt-entwine-quickstart) require that you edit the story format. Depending on your skill level, that might be trivial or out of reach.

    Compilers that use Twine 1 style story formats (Twine 1, Twee, TweeGo) also, generally, require that you edit the story format. In SugarCube's case, however, you have another option in the userlib.js file. If you place a file named userlib.js into your local SugarCube install directory—the same directory which contains the header.html file—then the contents of that file will be compiled into SugarCube itself when you build your project.

    Pros (editing): Editing the story format works with any compiler, story format, and usage scenario.
    Cons (editing): You have to edit the story format each time you want to add/remove libraries and/or update the story format.

    Pros (SugarCube's userlib.js): Fits any usage scenario and should be fairly simple to use.
    Cons (SugarCube's userlib.js): Only works in Twine 1 style compilers—maybe only the listed ones—and only with SugarCube.


    3. Including a 3rd-party library with your project by loading it locally or over the network
    If you're talking about including a 3rd-party library with your project by loading it locally or over the network, then that's possible as well. Obviously, in the case of network loading, the player has to have an active network and you have to allow time for the library to load. In both cases, you also have to get the source path correct. For example, the follwing module should work in most cases:
    /*
    	External script loading module.
    */
    (function () {
    	window.requestScriptLoad = function (options) {
    		if (options == null || typeof options !== 'object' || !options.src) {
    			return;
    		}
    
    		var
    			opts   = Object.assign({ parent : document.head }, options),
    			script = document.createElement('script');
    
    		function onLoadOnce(evt) {
    			opts.onload.call(evt.target, evt);
    			script.removeEventListener('load', onLoadOnce);
    		}
    
    		script.id   = opts.id;
    		script.src  = opts.src;
    		script.type = 'text/javascript';
    
    		if (typeof opts.onload === 'function') {
    			script.addEventListener('load', onLoadOnce, false);
    		}
    
    		opts.parent.appendChild(script);
    	};
    })();
    
    /*
    	Load your external scripts here.
    */
    // Example of loading `moment.min.js` locally.
    requestScriptLoad({
    	id     : 'lib-moment-js',
    	src    : 'moment.min.js',
    	onload : function (evt) {
    		/* library is loaded, do something */
    	}
    });
    // Example of loading `moment.min.js` from a CDN.
    requestScriptLoad({
    	id     : 'lib-moment-js',
    	src    : 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.0/moment.min.js',
    	onload : function (evt) {
    		/* library is loaded, do something */
    	}
    });
    
    Pros (local): Loading the library locally should work with any compiler, story format, and usage scenario.
    Cons (local): Happens after document load, so if you need to use the library immediately after requesting it be loaded, then you must use the onload callback.

    Pros (network): Loading the library over the network should work with any compiler, story format, and any usage scenario that mandates network access.
    Cons (network): Happens after document load, so if you need to use the library soon after requesting it be loaded, then you must use the onload callback.

    Kokonoe wrote: »
    I have tried throwing the contents of their non-minified JS file into a [script] passage, […]
    Unless you plan on editing the library—which, frankly, seems unlikely given your demonstrated skill set—why are you using the unminified version? You're bloating your project's size for no good reason.
  • Claretta wrote: »
    However, this functionality was removed from Twine 2 so you do it in one of two ways:

    […]

    2. Fork the SugarCube 2 story format from GitHub and edit the header.html file to create your own custom version with the <script></script> tags in the head section of the header.html. […]
    There is no pre- or post-build header.html file for the Twine 2 compatible build.

    Similarly, there is no pre-build header.html file for the Twine 1 compatible build.

    If someone is forking the repo, then I'd suggested editing the source templates instead, src/templates/{twine1,twine2}/html.tpl. Afterwards, build and install as normal.

    Claretta wrote: »
    As a comment, you don't put js libraries into the script passages because this places the code inside the body section, not the head.
    What? SugarCube doesn't do that. Beyond that, it wouldn't matter even if it did, since scripting passages are not set up until after the story format itself is executing, by which point it's pretty much moot where they go.
  • edited September 2016

    Similarly, there is no pre-build header.html file for the Twine 1 compatible build.

    That's how I do it in SugarCube 1 - there's a header.html in the targets directory I use and changes to that carry through the build process. I suppose it might have changed in SugarCube 2, so I'll have to think about that when I switch over.

    Which is good to know, because I wouldn't have been able to change if not for finding that out.

  • Claretta wrote: »
    Well that's how I use it. I suppose it might have changed in SugarCube 2, so I'll have to think about that if or when I switch over.
    That's how you use it with SugarCube v1. Since this thread is specifically about SugarCube v2, then how SugarCube 1 is structured isn't really germane.

    Additionally, I noted in my previous post where the templates for each style exist within the v2 codebase—specifically: src/templates/{twine1,twine2}/html.tpl. You'd edit one or both of those to make pre-build changes.
  • edited September 2016
    I'm just mentioning it since I didn't see it in the changeover docs. Without this thread, I probably wouldn't have been able to figure it out and would have gotten frustrated. So hence, I'm glad I posted, even if I was wrong.
  • 2. Bundling a 3rd-party library with your project as part of the compiled story format itself
    If you're talking about including a 3rd-party library with your project as part of the story format, either bundled in or as a separate local file, then that's possible. How difficult it is in practice depends on which compiler, and story format, you use.

    Compilers that use Twine 2 style story formats (Twine 2, Twee2, grunt-entwine, grunt-entwine-quickstart) require that you edit the story format. Depending on your skill level, that might be trivial or out of reach.

    Compilers that use Twine 1 style story formats (Twine 1, Twee, TweeGo) also, generally, require that you edit the story format. In SugarCube's case, however, you have another option in the userlib.js file. If you place a file named userlib.js into your local SugarCube install directory—the same directory which contains the header.html file—then the contents of that file will be compiled into SugarCube itself when you build your project.

    Pros (editing): Editing the story format works with any compiler, story format, and usage scenario.
    Cons (editing): You have to edit the story format each time you want to add/remove libraries and/or update the story format.

    Pros (SugarCube's userlib.js): Fits any usage scenario and should be fairly simple to use.
    Cons (SugarCube's userlib.js): Only works in Twine 1 style compilers—maybe only the listed ones—and only with SugarCube.

    Thank you very much for the extremely thorough answer. Option 2 looks to be the best choice in my case, particularly with TweeGo's support for userlib.js. I apologize for not being more specific about what I was trying to accomplish.
  • Claretta wrote: »
    I'm just mentioning it since I didn't see it in the changeover docs. Without this thread, I probably wouldn't have been able to figure it out and would have gotten frustrated. So hence, I'm glad I posted, even if I was wrong.
    I assume we're talking about the template file change. If so, then we're talking about internal source tree spadework here. I don't know of any project that highlights changes like that, unless the source code itself is the entire point of the project—not the case with SugarCube. That's a level of detail that you usually only see within commit logs. I'm not trying to pick on you, however, that is kind of an unreasonable expectation.

    Also, I'm here answering questions so that people don't need to get frustrated. If you do eventually pull the trigger on switching your project to v2, don't start chewing on the furniture if something isn't working. Come here and invoke the summoning rituals tell me of your problems—related to SugarCube, keep the Dr. Phil stuff to yourself, seriously.
  • edited September 2016
    Since I use a lot of custom divs for art and subtitle layers, it's mostly relevant to adding additional divs. I don't like adding divs via javascript. It's so much simpler to add divs by just inserting them in the html. What's the best way to add divs to sugarcube 2?
  • Claretta wrote: »
    Since I use a lot of custom divs for art and subtitle layers, it's mostly relevant to adding additional divs. I don't like adding divs via javascript. It's so much simpler to add divs by just inserting them in the html. What's the best way to add divs to sugarcube 2?
    It depends on where you happen to be adding the <div> elements now, I suppose. You didn't volunteer that information, so it's kind of hard to answer what you'd need to do in SC2.

    If they just need to go into the <body> someplace, which is what I'm assuming, then the templates I mentioned before should work as they contain SugarCube 2's basic structure—UI elements are added during startup by their modules. I'd probably suggest just above the <div id="store-area"> element.

    Ideally, however, you'd use JavaScript. For someone doing nontrivial web development, "I don't like {doing something} via JavaScript" should not be a serious answer. At least, not when it's probably the best answer available and I know for a fact that you're already heavily leaning upon JS via your use of animation libraries. I have a hard time understanding schizophrenia like that.
  • edited September 2016
    Well what I meant is that I find the process of simply editing the header.html to throw in a div into the body section to be quite simple and easy, and it accomplishes in just a few words of html what would take more javascript to do. Because I currently have that option, using javascript seems inefficient, even if only very small.

    But I can just use jquery, as that's less complex than js to add divs. I know it sounds downright silly when you look at how much javascript my animation uses.

    As always, thanks for the advice. I don't always get the correct information across, but I usually benefit from the discussions.
    Kokonoe wrote: »

    Thank you very much for the extremely thorough answer. Option 2 looks to be the best choice in my case, particularly with TweeGo's support for userlib.js. I apologize for not being more specific about what I was trying to accomplish.

    In that case i suppose i gave a correct answer with the userlib.js as well. :p
Sign In or Register to comment.