ChapelR / tweego-setup

A Tweego project boilerplate.
The Unlicense
62 stars 21 forks source link

Please add an option to place user scripts in external file #3

Closed zeule closed 5 years ago

zeule commented 5 years ago

SugarCube executes scripts inside a closure (?) using eval() and that significantly limits what can be used in user .js files. To overcome those limitations, I patch tweego's output and inject <script> tag there, which loads custom script: (sed -i 's/<\/body>/<script src="my_script.js"><\/script><\/body>/').

I added a naive implementation to this template, but being a noob with node and gulp, I ask you to polish it and add mainline, please. It adds (see the diff below) gulp tasks for tweego invocation (creates an intemediate file), a task to replace </body> with <script>...</script></body> in that file, and to rename the intermediate file. Also adds new directories to the configuration.

  diff --git a/gulpfile.js b/gulpfile.js
index e406a57..6007e24 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -6,7 +6,10 @@ var gulp = require('gulp'),
     autoprefix = require('gulp-autoprefixer'),
     jshint = require('gulp-jshint'),
     noop = require('gulp-noop'),
-    config = require('./src/config.json');
+    shell = require('gulp-shell'),
+    replace = require('gulp-replace'),
+    config = require('./src/config.json'),
+    pkg = require('./package.json');

 function processScripts (dir, out, name) {
     return gulp.src(dir)
@@ -26,7 +29,7 @@ function processStyles (dir, out, name) {
         .pipe(gulp.dest(out));
 }

-// linting 
+// linting
 function lint () {
     return gulp.src(config.directories['user-js'])
         .pipe(jshint())
@@ -35,13 +38,38 @@ function lint () {

 // build function
 function build () {
-    var dir = config.directories;
-    processScripts(dir['vendor-js'], dir['out-js'], dir['vendor-file-js']);
-    processScripts(dir['user-js'], dir['out-js'], dir['user-file-js']);
+    const dir = config.directories;
+    processScripts(dir['vendor-js'], dir['out-vendor-js'], dir['vendor-file-js']);
+    processScripts(dir['user-js'], `${dir['dist']}/${dir['out-js']}`, dir['user-file-js']);
+    processScripts(dir['user-js-tw'], dir['out-js-tw'], dir['user-tw-file-js']);
     processStyles(dir['vendor-css'], dir['out-css'], dir['vendor-file-css']);
     processStyles(dir['user-css'], dir['out-css'], dir['user-file-css']);
 }

+function tweego() {
+    const dir = config.directories;
+    return gulp
+        .src([dir['passages'], ], { read: false })
+        .pipe(shell([`tweego -f ${pkg.config.format} --head=src/HEAD.html -o ${dir['dist']}/index_tmp.html project`]
))
+}
+
+function injectUserScript() {
+    const dir = config.directories;
+    return gulp.src(`${dir['dist']}/index_tmp.html`)
+        .pipe(replace('</body>', `<script src="${dir['out-js']}/${dir['user-file-js']}"></script>\n</body>`))
+        .pipe(gulp.dest(`${dir['dist']}`));
+}
+
+function renameOutput() {
+    const dir = config.directories;
+    return gulp
+        .src(`${dir['dist']}/index_tmp.html`, { read: false })
+        .pipe(shell(`mv ${dir['dist']}/index_tmp.html ${dir['dist']}/index.html`));
+}
+
 // tasks
 gulp.task('build', build);
-gulp.task('lint', lint);
\ No newline at end of file
+gulp.task('tweego', tweego);
+gulp.task('inject-script', ['tweego'], injectUserScript);
+gulp.task('rename-output', ['inject-script'], renameOutput);
+gulp.task('lint', lint);
diff --git a/package.json b/package.json
index 5c0208b..63b8fbd 100644
--- a/package.json
+++ b/package.json
@@ -6,18 +6,6 @@
   "config": {
     "format": "sugarcube-2"
   },
-  "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1",
-    "open": "opn",
-    "gulp": "gulp",
-    "lint": "gulp lint",
-    "build": "gulp build && tweego -f $npm_package_config_format --head=src/HEAD.html -o dist/index.html project && opn dist/index.html",
-    "testmode": "gulp build && tweego -f $npm_package_config_format -t --head=src/HEAD.html -o dist/index.html project && opn dist/index.html",
-    "tweegobuild": "tweego -f $npm_package_config_format --head=src/HEAD.html -o dist/index.html project",
-    "build-win": "gulp build && tweego -f %npm_package_config_format% --head=src/HEAD.html -o dist/index.html project && opn dist/index.html",
-    "testmode-win": "gulp build && tweego -f %npm_package_config_format% -t --head=src/HEAD.html -o dist/index.html project && opn dist/index.html",
-    "tweegobuild-win": "tweego -f %npm_package_config_format% --head=src/HEAD.html -o dist/index.html project"
-  },
   "keywords": [
     "twine",
     "tweego",
@@ -26,17 +14,20 @@
   "author": "Chapel",
   "license": "Unlicense",
   "dependencies": {
-    "@babel/core": "^7.0.0-beta.53",
+    "@babel/core": "^7.4.4",
     "babel-preset-env": "^1.7.0",
+    "graceful-fs": "^4.0.0",
     "gulp": "^3.9.1",
     "gulp-autoprefixer": "^5.0.0",
-    "gulp-babel": "^8.0.0-beta.2",
-    "gulp-clean-css": "^3.9.4",
+    "gulp-babel": "^8.0.0",
+    "gulp-clean-css": "^3.10.0",
     "gulp-concat": "^2.6.1",
     "gulp-jshint": "^2.1.0",
     "gulp-noop": "^1.0.0",
-    "gulp-uglify": "^3.0.0",
-    "jshint": "^2.9.5",
+    "gulp-replace": "^1.0.0",
+    "gulp-shell": "^0.7.0",
+    "gulp-uglify": "^3.0.2",
+    "jshint": "^2.10.2",
     "opn-cli": "^3.1.0"
   },
   "directories": {
diff --git a/src/config.json b/src/config.json
index c3122b6..b25c1e6 100644
--- a/src/config.json
+++ b/src/config.json
@@ -1,23 +1,29 @@
 {
     "javascript": {
         "minify": true,
-        "transpile": true
+        "transpile": false
     },
     "css": {
         "minify": true,
         "autoprefix": true
     },
     "directories": {
+        "dist": "./dist",
         "user-js": "./src/scripts/**/*.js",
+        "user-js-tw": "./src/scripts-tw/**/*.js",
         "user-css": "./src/styles/**/*.css",
         "vendor-js": "./vendor/**/*.js",
         "vendor-css": "./vendor/**/*.css",
-        
-        "out-js": "./project/scripts",
+        "passages": "./project/twee/**/*.twee",
+
+        "out-js": "scripts",
+        "out-js-tw": "./project/scripts-tw",
+        "out-vendor-js": "./project/scripts",
         "out-css": "./project/styles",
         "vendor-file-js": "bundle.min.js",
         "vendor-file-css": "bundle.min.css",
         "user-file-js": "user.min.js",
+        "user-tw-file-js": "user.tw.min.js",
         "user-file-css": "user.min.css"
     }
-}
\ No newline at end of file
+}
ChapelR commented 5 years ago

I believe this may be better handled via Tweego's --head option, which you can access via the HEAD.html file in this boilerplate, which adds elements to the document's <head> and can be used to load scripts and styles. Or the SugarCube (if you're using that format) importScripts() method which will add said scripts asynchronously and outside the engine's scope. Both of these options are great for loading web libraries or other code that shouldn't or can't be in the engine's scope. If the issue is an attempt to load a web library, there's an example of a wrapper you can use in the story JavaScript files to get most (9/10 or better in my experience) web libraries working. It looks like this:

(function (module, define, exports) {

    // library code goes here 

}).call(window);

I get the general wisdom of putting a <script> element in the document body for a normal web page, but since the entire engine is loaded prior to dismissing the load screen, I don't see any advantage to adding a feature for this.

If the HEAD.html or --head option of Tweego don't serve your needs, let me know why and I can try to implement what you have there if it seems like a good idea.

To recap, the current options you may have are:

  1. Add the <script> elements to the HEAD.html file or use the --head command-line option for Tweego.
  2. If using SugarCube, use importScripts().
  3. If the issue is a web library that won't load because it's in the engine scope, you can use the wrapper mentioned above and in the readme.
zeule commented 5 years ago

Thanks for the reply and the suggestions. The --head option does not help because it puts the script before the format script, and format API can't be used freely from the script. importScripts() probably does what I need, somehow I overlooked that in the SugarCube docs, that seems to be simpler than patching the resulting HTML file. Thanks for pointing that out!

ChapelR commented 5 years ago

The head option comes after the engine but is out of scope. The importScripts() method is also out of scope, so no format APIs.

You can't have both. There are no meaningful limitations on story JavaScript except that the code is automatically in strict mode (which may cause you problems if you aren't expecting it) and that most web libraries won't find the window object and therefore need a wrapper to help get the context they expect.

What exactly are you trying to accomplish that you feel you can't do from inside Story JS?

(It may take me some time to get back to you with any further replies.)

zeule commented 5 years ago

The head option comes after the engine…

Pardon, don't understand that. SugarCube2 (SC2) code is placed within <body> by tweego, how then any <head> links can be processed after SC2?

The importScripts() method is also out of scope, so no format APIs.

I still can use the SugarCube object, and that means I can access the story variables.

importScripts() with await call from the story init passage does the trick. I think this can be closed, unless you want to add options to concatenate and minify/transpile another dir with scripts, that will be placed near the resulting HTML file.

What exactly are you trying to accomplish that you feel you can't do from inside Story JS?

I want to avoid assigning to the window object in order to get global functions and variables, because long nested names in passages without auto-completion is unhandy.

ChapelR commented 5 years ago

The engine is loaded in the <head>, including all SC APIs. The timing doesn't really matter, though, because you can wrap your <head> code in a function that initializes everything and call it from story JS to get access to engine APIs via the SugarCube debug object.

However, the SugarCube debug APIs are not recommended for use by the author of SC, as they are not stable, so that is a house of cards. Generally if you want to interact with the engine, you want to be in the engine's scope. How much this actually winds up impacting your project is anyone's guess, but it's still worth knowing that those APIs are intended only for runtime debugging via the console, not really for building code on, but some people do so, so it's not like it won't work.

As far as not using the window object, the setup object is a thing, but that's more typing. I tend to adopt a "when in Rome" approach and try to use macros to manage most of my custom code, but macros aren't good for everything.

I have no particular plans to add anything, though, since I think this is a fairly specific use case and if you're happy enough with the results you have that's good enough for me, so I'll close this.

zeule commented 5 years ago

The engine is loaded in the , including all SC APIs.

This is wrong (just tested).

I don't thinks the key attributes of the SugarCube object (basically, the State) will be removed while the State object exists, and just using simple JS is much more comfortable than setup, window and so on... I think it worth the little risk of using the debug API.

Let me thank you once again for the help!