Skip to content

Instantly share code, notes, and snippets.

@mikermcneil
Last active August 29, 2015 14:13
Show Gist options
  • Save mikermcneil/f1294b34f89f9ee9f392 to your computer and use it in GitHub Desktop.
Save mikermcneil/f1294b34f89f9ee9f392 to your computer and use it in GitHub Desktop.
The Sails Linker
<!DOCTYPE html>
<html>
<head></head>
<body>
stuff
<!--SCRIPTS src="js/brochure/dependencies/**/*.js" prefix="http://ag191adkg1dadgvw.cloudfront.net"-->
<!--SCRIPTS END-->
<!--SCRIPTS src="js/brochure/**/*.js" prefix="http://ag191adkg1dadgvw.cloudfront.net" -->
<!--SCRIPTS END-->
</body>
</html>
<!DOCTYPE html>
<html>
<head></head>
<body>
stuff
<script type="text/javascript" src="js/jquery.js"></script>
<!--SCRIPTS src="js/ace/**/*.js"-->
<!--SCRIPTS END-->
<!--SCRIPTS src="js/dashboard/dependencies/**/*.js"-->
<!--SCRIPTS END-->
<!--SCRIPTS src="js/dashboard/**/*.js" -->
<!--SCRIPTS END-->
</body>
</html>

The Sails Linker

grunt-sails-linker is one of the most important core Grunt tasks bundled with Sails. While it is technically just one of many different Grunt tasks that get plugged in, the linker task is the glue that makes everything in the default Grunt setup work.

History of static assets in Sails

Like many projects before us, we didn't start off with Sails being as modular/configurable as it is now-- it was a monolithic thing that helped my team and the early users get tedius, repeated tasks done over and over. On the backend, that meant setting up quick REST APIs, protecting them with policies, then overriding bits and pieces as needed with controller actions. But as we all know, there is also a front-end side that is just as important-- and much more difficult.

The dark ages: Rigging

Believe it or not, while this makes me cringe to say, Sails actually had a built-in front-end web framework bundled with it for a while x_x. It was an embarassing mistake that a lot of folks made when they first started w/ Node. Luckily, I realized quickly this would never work for real-world use cases (I spent months convincing a Fortune 500 company to let us use Node on their project, only to have their technical team flat-out reject the solution because it entangled front-end functionality). Needless to say, I learned my lesson.

But some stuff still needed to run on the server. So what was left? The first priority was having a way to compile client-side HTML templates (JST support), as well as a production-ready minification setup (i.e. so we could deploy customer apps at a moment's notice and have them load only a single .css and .js file). What tied it all together though was the linker-- the logic that automatically injected script and style tags into the HTML of your pages. Initially, this logic was bundled in Sails core via a module called rigging (I added you to this repo just for fun so that you can see how bad the code is-- I made it private out of embarrassment)

Sails v0.8: Asset Rack

Some time in late 2012 or early 2013, the Balderdash team started using LESS thanks to the suggestion of a guy we were working with naemd Ken Seals. By this time, Sails was getting robust enough to pick up a small amount of attention, and there were requests for SASS + CoffeeScript. This was my first experience with just how many different ways there are out there to do the same thing. I dilligently added support for all this stuff to Rigging, and ended up constantly having to do more maintainence on code I wasn't even using. This taught me my first important lesson about open-source:

Never write code you aren't getting paid for that you don't plan on using.

Anyways, a little time passed, and Brad Carleton from TechPines who I was working with on a few professional projects suggested integrating his module, AssetRack. It allowed for pulling assets from a CDN. We integrated it with the linker, replacing rigging. There were bugs and it was pretty rough. But it got us through to Sails v0.8. Soon afterwards, I released the first screencast with some initial docs and Sails ended up on Hacker News and the community really took off. It wasn't until then that I finally made the decision to move to Grunt.

Sails v0.9: Grunt

Initially in Sails v0.9, the implementation was a single Gruntfile which was bundled in new Sails projects. This made it possible to modify or completely replace the default asset compilation behavior using standard Grunt tasks. Unfortunately, there was no way to customize the default Gruntfile that was generated in new projects.

Sails v0.10: Generators

In Sails v0.10, when we added pluggable generators, we broke up the files based on some ideas by @bluehotdog (Danni Friedland) in his module called Gake. By using individual files, it made it possible for custom generators to include different Grunt tasks depending on the configuration in your .sailsrc file(s). This was implemented using Dominic Tarr's rc module for loading configuration recursively up through your directory tree-- e.g. you might have one or two Sails apps in ~/code/paypal/ and two or three Sails apps in ~/code/walmart/. You can have a .sailsrc file with global defaults (i.e. running sails generate or sails new anywhere on your machine) but also an override in ~/code/walmart/.sailsrc (e.g. maybe Walmart is using Angular and SASS and so you override the sails new generator to create the appropriate grunt tasks and file structure). But when you run sails new in ~/code/paypal/, you still get your global defaults (i.e. no SASS task generated). Finally, if a particular WalMart project is different from the rest, you can also create an override in one of its subdirectories (e.g. ~/code/walmart/promotional-app/.sailsrc might switch out SASS for Stylus).

Philosophy

The primary goal of the linker rewrite is to make it possible to drop in lexical symbols (special HTML comments) wherever you want in your HTML/.ejs files, which will then be automatically pre-processed to inject script and link tags for a specific subset of files. The actual set of files which get created is determined by notation in the lexical symbols themselves, which means that we can use this to eliminate the need for tasks/pipeline.js.

However, there are complications- in production, the minifier must be run for each HTML block. This probably means changes to the default asset pipeline in Sails. That's ok!

For the first pass, I think you should not worry about:

  • link tag injection for LESS/CSS
  • JST template injection
  • coffeescript support

That lets the effort focus on scripts. If we figure out a good way to do that, we can figure out how to fit in the rest of the functionality.

Current Usage

For reference, the current usage looks like:

  <!--STYLES-->
  ... css gets injected here ... (multiple files in dev, a single file in prod, LESS compilation is taken into account)
  <!--STYLES END-->
  
  <!--SCRIPTS-->
  ... css gets injected here ... (multiple files in dev, a single file in prod, LESS compilation is taken into account)
  <!--SCRIPTS END-->

Notes

  • Currently the linker works for both .html files in the assets folder as well as .ejs files in the views folder
  • I haven't tested the jade stuff in a while- I'd say we start w/o jade for now.
  • Also I reckon we don't worry about JST templates for now (I was considering ripping that out of core anyways)
  • The tricky bit about implementing this is that minification needs to happen on a per-block basis, instead of just minifying everything.

Housekeeping

Here's the link to the repo I've been working out of: https://github.com/mikermcneil/grunt-sails-linker

You'll probably also need: https://github.com/balderdashy/sails-generate-frontend

Added you as a contributor on both- hopefully @badthingfactory is the right account :)

The files below demonstrate a rough API that I think would work-- as you're going through this, I'm sure you'll run into stuff that needs to change. So definitely don't think about this as some kind of magical perfect spec :) If you aren't sure about a change, just keep me abreast of the status and I'm happy to take a look and make suggestions, give feedback, etc..

Zolmeister (Zoli Kahan) forked the thing originally. He's a great guy, but he's pretty busy atm w/ Clay.io so I know he can't get too involved, but if you have any questions, feel free to hit him up and I'm sure he'd be willing to help a bit.

Thanks Ryan. Good luck :)

@mikermcneil
Copy link
Author

The prefix option used here is an example of how we could allow for injecting a CDN baseurl (I recently added this to the linker)

@rwhitmire
Copy link

Mike, I created a gist that contains some possible solutions to problems I ran into: https://gist.github.com/rwhitmire/9cd371e748465e5915c3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment