Seamless Sass integration for a Twine/SugarCube project within VSCode - Step-By-Step!

(I’d like to thank both @averyhiebert and @Lurker for making me realize that the original version of this tutorial was unfortunately stylized like an AI-written post. I’ve since then taken some steps to mitigate this effect. Man, I hate clankers.)
(Additionally, I’d like to thank everyone that provided any nice suggestions in the discussion, as it shaved some unneccessary bloat from the first edition.)

Seamless Sass integration for a Twine/SugarCube project within VSCode

(Second edition, revised & optimised)

Hey there!

I just configured something really cool and thought I share it here :smiley:
Huge thanks to @sparkletwist who pushed me into this rabbithole when she posted an answer to my question about the Sass-Twine/Sugarcube integration :two_hearts:

The setup

I’m developing an adult sandbox game using Twine with Sugarcube (beep beep, shameless plug, check out the link in my bio for the itchio page) using:

Now, the out-of-the-box solution builds a project automatically by using a nice Twine (Twee 3) Language: Build Game VSCode command directly from the IDE.

No fuss, no command line shenanigans, just one-click plug-and-play.

The Problem

Ok, everything is nice and all, but there’s one problem:
We still have to write in pure CSS, and operate on the .css files.
Sure it works, but we don’t want to style the game like it’s still 2003, right?

So, I knew I wanted to integrate Sass into the project for the longest time.

Now, installing Sass is not a huge hurdle, but what then?
I didn’t want to do everything manually from now on, no. We want automation, we want integration with the existing, nice one-click command system the VSCode provided until then.

So, I got to work. And, like I mentioned above, I think I made something really cool.

The solution

Prerequisites

Alright, step-by-step, what do we need:

  1. Install Node.js
  2. Fire up the console (if you’re using Linux distro) or PowerShell (if using Windows)
  3. Navigate to your project folder
  4. Install the necessary dev tools:
  • npm init -y
  • npm install --save-dev sass chokidar-cli
    where:
    • sass - Sass package
    • chokidar-cli - for file watching

The setup

Like that jedi-man guy once said, this is where the fun begins:

Create basic SCSS file structure

  1. Create styles folder in project/src
  2. Within styles, create two files:
  • main.scss with:
    @use "stylesheet"; in it
  • _stylesheet.scss:
    Here, copy all your CSS from your project’s .css stylesheet (if you have more than one, just recreate them here using .scss partial files).
  • Once you made sure that everything’s been copied over, delete the original .css file(s).

Scripting - auto-compile Sass and save it to UserStyleSheet.css

  1. Create scripts folder directly in your project folder, OUTSIDE /src
  2. Within scripts, create build-css.js with the following script
    (which compiles sass files into one minified .css stylesheet within your src/ folder:
import { execSync } from "child_process";

const scss = "src/styles/main.scss";
const cssFile = "src/story/UserStylesheet.css";

execSync(`npx sass ${scss} ${cssFile} --style=compressed --no-source-map`);
console.log("Styles injected into UserStylesheet");

npm scripts

Within the npm’s package.json file:

  1. Change the "type": to "module"
  2. In "scripts": insert the following commands, so that it’ll look like that aferwards:
"scripts": {
    "build:css": "node scripts/build-css.js",
    "watch:css": "chokidar \"src/styles/**/*.scss\" -c \"npm run build:css\"",
    "watch:twee": "chokidar \"src/**/*.twee\" -c \"echo twee changed\"",
    "dev": "npm run watch:css"
  },

VSCode build tasks

  1. If you don’t have it, add a .vscode folder directly in your project, OUTSIDE /src
  2. Within .vscode, create the tasks.json file with the following:
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Build CSS",
      "command": "npm run build:css",
      "type": "shell"
    },
    {
      "label": "Build Twine",
      "dependsOn": ["Build CSS"],
      "command": "${command:twineTweeLanguage.buildGame}"
    }
  ]
}

Misc. stuff

If you use a version control system like git, don’t forget to add /node_modules and UserStyleSheet.css to .gitignore :wink:

(if you don’t use any version control, you should. Actually, stop everything right now and set it up. Go.)

The Outcome

Now, check it out:

Within VSCode

  • You can now:
    • Hit Ctrl + Shift + B
    • Choose npm: build:css
      and that automatically updates UserStyleSheet.css with all the styling from the .sass files! The compiled css is also already minified and ready for the production build :smiling_face_with_three_hearts:
  • And now the main thing, and the reason we did all this:
    • Hit Ctrl + Shift + P
    • Choose Tasks: Run Task
    • Then, Build Twine
      and boom. All sass styling is automatically compiled into UserStyleSheet.css as a minified css, and then the Twee extension automatically builds everything into one HTML file.
      In one click!

Outside VSCode

Is that all? Nope.

You can now navigate via the Linux terminal (or PowerShell) to the project folder,
type in npm run dev, and now you have a watcher active.

This means that now the UserStyleSheet.css stylesheet is updated every time you change anyting in any .scss file!

The expected file structure

Twine project/
    ├── .storyformats
    ├── .vscode/
    │   ├── settings.json (if you have any project-specific settings)
    │   └── tasks.json
    ├── build
    ├── scripts/
    │   └── build-css.js
    ├── src/
    │   ├── *all other source files*
    │   └── story/
    │       ├── *all other story files*
    │       └── UserStyleSheet.css
    ├── .gitignore
    ├── package-lock.json
    ├── package.json
    └── t3lt.twee-config.yml

So, that’s it!

You can now convert all the CSS you copied over from the original UserStyleSheet.twee and convert them to proper Sass, as well as create multiple subfiles for each part of the UI like, I don’t know, e.g. buttons.scss, anims.scss, use all the Sass’s mixins, partials, class nesting and other good stuff :smiling_face_with_three_hearts:

Now, I’m just a guy that makes a game in Twine and does some webdev stuff - I’m absolutely not an expert on anything VSCode, Javascript or even Twee/SugarCube - so if you have any suggestions as to how to simplify this process even further, please do tell!

Peace,
-Testy

Some comments:

  • Why suggest two separate .scss files to start with, where one does nothing but import the other one?
  • Your entire build-css.js could be replaced with a single command. For example:
    echo -e ":: UserStylesheet [stylesheet]\n"$(npx sass src/styles/main.scss --style=compressed --no-source-map) > src/story/UserStylesheet.twee
  • It looks like nothing you’re doing here currently actually requires the use of concurrently

(EDIT: Also, not sure what percentage of the post is AI-generated, but seems worth mentioning that there is a forum rule against using AI to generate the content of your post)

Futureproofing. You’d ideally want at least a few separate .scss files to separate typography, variables, animations and such - the purpose of the main.scss is to be that ‘importer‘ file, combining all the sub-files.

Sure, I guess I went with a longer version, which for me was more readable. As for optimization, everything can always be optimized further :grinning_face:

npm run dev, check package.json

Now that’s just… I really want to say insulting, but I guess I can’t really blame you, as you can’t really know for sure these days.

I spent ~3 hours digging through the docs, an additional ~hour making sure that everything in this post is neatly formatted, and I was really excited to see if this tutorial would be useful to anyone, because I’ve had an absolute blast setting all this stuff up - only for you to accuse me of generating it with AI.

I hate this reality.

What I meant is that npm run dev is using concurrently to run a single command. Is this actually different from just running npm run watch:css directly?

I mean, sorry, but the section headers starting with emojis in particular give very strong AI vibes.

Huh. I guess you’re right. Thanks, I’ll check!
Well, like I said,

That’s the problem though.
I’ve been using emojis to style various markdown docs years before even the first version of Copyright Infringement Machine 3000 hit the public.

…only for this style of documenting to be stolen by AI (like everything else), and now people think that every emoji-styled doc must be generated by some propmt.

I swear, I’ll just start dropping LaTeX .pdfs or something instead :rofl:

[!danger]- Harshness folded by Mod
I would like to say that even if you did not use AI to generate your post in any way, your writing has certain stylistic markers that made me, and probably a lot of other people as well, immediately raise my guard and say “this is definitely AI-generated”. The copious use of headers for formatting, the emoji-formatted headers, the little “zinger” lines like

and the use of unnecessary italics in places like

are complete tells, to my eyes.

A few of these stylistic markers on their own wouldn’t be anything. But put them together and they gave me a negative impression of the original post. Even if you didn’t use AI at all to generate it, I’d still recommend ditching these things because I’ve long learned to associate this style of writing with low-effort AI generated content. Taking the time to edit out these stylistic markers would make it easier for me to trust the information contained within, and the person producing it, by extension. I also just find this style really irritating to read, in part because I associate it with low-effort AI generated content. Even if you’ve been using emojis and markdown headers for years, it can’t be helped that many people now have valid reasons to distrust emoji markdown headers and the other stuff I listed above. Frankly, I’d vastly prefer reading a LaTeX pdf if it didn’t use emojis, italics and jokey zinger lines.

Sorry if I’m coming off harsh. Part of the reason for my distaste is that the forum has recently seen a big increase in posts from people who are only here to self-promote their custom AI-written games or systems but don’t participate in the larger community or interact with other people’s creations at all, and it rankles me. You at least know what Twine is and aren’t trying to convince everyone to jump ship to your closed-source, server-hosted, online-only AI-developed IF system that’s focused on monetization. (Not naming specific names here; there are people who use AI for development or develop custom systems while contributing to this community as well.)

And I do like the idea of Sass and npm integration with Twine. I wonder what led you to change your mind and use npm when your original post specified that you don’t want to use npm. Was there a particular reason you resorted to npm after all?

But I literally write like that. I don’t want for something that has a lot of technicalities to be just a wall of monotone text. That’s what academic papers are for :rofl:
I use those styling patterns as well as those ‘zingers’, as, well, stylistic devices, because that’s literally what they are.

It was supposed to be a paraphrazed Star Wars joke. Oh well.

No no, I’m very glad that you and @averyhiebert brought this up, because it haven’t even occurred to me that this style of writing would cause such a reaction. I guess I need to tweak some things a little to get the AI vibe out of this tutorial, so thanks for the feedback.

Yeah, I’ve seen the post. It’s a damn shame. But, back to the topic at hand:

I thought that plastering npm on top of a Twine project would create some hassle in the Twee building process - but no, the process is basically the same as with e.g. a React app. I have no qualms with npm itself as I use it on multiple other projects (I personally prefer yarn to be honest), so I just went with that.

By the way,

Extending the post editor to include a way to embed a PDF viewer for uploaded files would be sick :fire:

Alright then, this should look overall less AI-y now. What do you think? :grinning_face:

There is no need to place a Twee Notation based project’s CSS within a Passage (that has been assigned the special stylesheet Passage Tag, because the TweeGo “compiler” includes the ability to automatically add the contents of any CSS file found within the source path passed as a command line argument to the Story JavaScript area of the generated Story HTML file.

eg. if the css files were in the src folder (or within a descendent folder of it) of the project structure you posted, then TweeGo will process that css file.

So all your build process needs to do is save the output of the Sass “compiler” into the src folder, before TweeGo gets called. No stylesheet tagged Passage needed.

see: TweeGo’s Supported Files

I’ve researched the topic with the link you’ve provided, and additionally encountered this note in the stylesheets docs:

Note: In general, Tweego makes creating stylesheet passages unnecessary as it will automatically bundle any CSS source files (.css) it encounters into your project.

So yeah, you’re right, straightforward .css file can be used here, I’m a dum dum :melting_face:

It’s great to learn though, I can simplify this tutorial even further, as well as my own project’s structure now :smiley:

(Edit - updated the filetree since I went through everything)

An update to my previous answer: Since then I’ve managed to rewrite the original CSS into SCSS and, like mentioned above, separate the one monolith I had into few files.

So, now I have:

.
└── styles/
    ├── animations/
    │   ├── _crt.scss
    │   ├── _keyframes.scss
    │   ├── _misc.scss
    │   └── _variables.scss
    ├── base/
    │   ├── _reset.scss
    │   └── _vendors.scss
    ├── components/
    │   ├── _card.scss
    │   ├── _changelog.scss
    │   ├── _separator.scss
    │   └── _tooltip.scss
    ├── layout/
    │   ├── _color.scss
    │   ├── _disabled.scss
    │   ├── _heading.scss
    │   ├── _icon.scss
    │   ├── _link.scss
    │   ├── _links.scss
    │   └── _skill.scss
    ├── ui/
    │   ├── components/
    │   │   ├── _menu.scss
    │   │   ├── _passages.scss
    │   │   ├── _phone.scss
    │   │   ├── _story.scss
    │   │   ├── _ui.scss
    │   │   └── _uibar.scss
    │   └── screens/
    │       ├── _casino.scss
    │       ├── _laptop.scss
    │       ├── _minigame.scss
    │       ├── _player.scss
    │       └── _stockexchange.scss
    └── main.scss

And within main.scss I now have:

@use "animations/keyframes";
@use "animations/crt";
@use "animations/misc";

@use "base/reset";
@use "base/vendors";

@use "ui/components/story";
@use "ui/components/ui";
@use "ui/components/uibar";
@use "ui/components/menu";
@use "ui/components/passages";
@use "ui/components/phone";

@use "ui/screens/player";
@use "ui/screens/laptop";
@use "ui/screens/minigame";
@use "ui/screens/casino";
@use "ui/screens/stockexchange";

@use "layout/disabled";
@use "layout/heading";
@use "layout/link";
@use "layout/links";
@use "layout/skill";
@use "layout/color";
@use "layout/icon";

@use "components/separator";
@use "components/card";
@use "components/tooltip";
@use "components/changelog";

(And _variables.scss being imported into _keyframes.scss, at least for now)

So, as you can see this pattern works really nicely when scaling the project.

By the way, there’s a nice explanation in the docs on what Sass partials do if you’re interested :grinning_face: