Giter Site home page Giter Site logo

mediocre / electricity Goto Github PK

View Code? Open in Web Editor NEW
30.0 22.0 5.0 663 KB

An alternative to the built-in Express middleware for serving static files. Electricity follows a number of best practices for making web pages fast.

Home Page: https://www.npmjs.org/package/electricity

License: Apache License 2.0

JavaScript 95.65% CSS 4.16% SCSS 0.19%

electricity's Introduction

Electricity

Build Status Coverage Status

An alternative to the built-in Express middleware for serving static files. Electricity follows a number of best practices for making web pages fast.

The built-in Express middleware for serving static files is great if you need basic support for serving static files. But if you want to follow Best Practices for Speeding Up Your Web Site you need something that can concat, gzip, and minify your static files. Electricity does all this and more without the need to create a complicated build process using Grunt or a similar build tool.

Basic Usage

Typically, in an Express app you'd serve static files using the built-in middleware. Like this:

const express = require('express');

app.use(express.static('public'));

To begin using Electricity simply replace the default static middleware:

const express = require('express');
const electricity = require('electricity');

app.use(electricity.static('public'));

View Helper

A common best practice for serving static files is to set a far future Expires header: http://developer.yahoo.com/performance/rules.html#expires

When you set a far future Expires header you have to change the file name whenever the contents of the file change. Electricity makes this easy for you by automatically adding an MD5 hash of the file's contents to the file name. You have access to this file name using a view helper method that builds URLs for you. If you're using EJS it looks something like this:

<img src="<%= electricity.url('/images/image.png') %>" />
<link href="<%= electricity.url('/styles/style.css') %>" rel="stylesheet" />
<script src="<%= electricity.url('/scripts/script.js') %>"></script>

Which ultimately gets rendered as something like this:

<img src="/images/image-423251d722a53966eb9368c65bfd14b39649105d.png" />
<link href="/styles/style-22a53914b39649105d66eb9368c65b423251d7fd.css" rel="stylesheet" />
<script src="/scripts/script-5d66eb9368c22a53914b39d7fd6491065b423251.js"></script>

Features

Electricity comes with a variety of features to help make your web pages fast without the need to setup a complicated build process.

  • HTTP Headers: Electricity sets proper Cache-Control, ETag, and Expires, headers to help avoid unnecessary HTTP requests on subsequent page views.
  • Minification of JavaScript and CSS: Electricity minifies JavaScript and CSS files in order to improve response time by reducing file sizes.
  • Gzip: Electricity gzips many content types (CSS, HTML, JavaScript, JSON, plaintext, XML) to reduce response sizes.
  • Snockets: Electricity supports Snockets (A JavaScript concatenation tool for Node.js inspired by Sprockets). You can use Snockets to combine multiple JavaScript files into a single JavaScript file which helps minimize HTTP requests.
  • Sass: Electricity supports Sass (Sassy CSS). Among other features, Sass can be used to combine multiple CSS files into a single CSS file which helps minimize HTTP requests. NOTE: We currently only support .scss files (not .sass files written in the older syntax).
  • React JSX: Electricty transforms React JSX for you automatically using babel-core without the need for client-side translation or build steps.
  • CDN Hostname: If you're using a CDN (Content Delivery Network) that supports a custom origin (like Amazon CloudFront) you can specify the hostname you'd like Electricity to use when generating URLs.
  • Watch: Electricity watches for changes to your static files and automatically serves the latest content without the need to restart your web server (useful during development). Electricity also understands Sass and Snockets dependency graphs to ensure the parent file contents are updated if a child file has been modified.

Advanced Usage

Default options look like this:

const options = {
    babel: {},
    hashify: true,
    headers: {},
    hostname: '',
    sass: {},
    snockets: {},
    uglifyjs: {
        enabled: true
    },
    uglifycss: {
        enabled: true
    }
};

You can override the default options to look something like this:

var options = {
    babel: { // Object passed straight to @babel/core options: https://babeljs.io/docs/en/options
        generatorOpts: {
            compact: true
        },
        parserOpts: {
            errorRecovery: true
        }
    },
    hashify: false, // Do not generate hashes for URLs
    headers: { // Any additional headers you want a specify
        'Access-Control-Allow-Origin': 'https://example.com'
    },
    hostname: 'cdn.example.com', // CDN hostname
    sass: { // Object passed straight to node-sass options
        outputStyle: 'compressed',
        quietDeps: true
    },
    snockets: { // Object passed straight to snockets options: https://www.npmjs.com/package/snockets
    },
    uglifyjs: { // Object passed straight to uglify-js options: https://github.com/mishoo/UglifyJS#minify-options
        enabled: false // Do not minify Javascript
    },
    uglifycss: { // Object passed straight to uglifycss options: https://github.com/fmarcia/uglifycss
        enabled: false // Do not minify CSS
    }
};

Pass options to the middleware like this:

app.use(electricity.static('public', options));

HTTP Headers

Electricity sets proper Cache-Control, ETag, and Expires headers to help avoid unnecessary HTTP requests on subsequent page views. If you'd like to specify literal values for specific HTTP headers you can set them in the headers option. This is useful if you need to specify a Access-Control-Allow-Origin header when loading fonts or JSON data off a CDN.

app.use(electricity.static('public', {
    headers: { 'Access-Control-Allow-Origin': '*' }
}));

CSS URI Values

Electricity will automatically rewrite URIs in CSS to use SHA1 hashes (if a matching file is found). For example:

background-image: url(/background.png);

becomes this to allow caching and avoid unnecessary redirects:

background-image: url(/background-423251d722a53966eb9368c65bfd14b39649105d.png);

CDN Hostname

If you specify a hostname like this:

const express = require('express');
const electricity = require('electricity');

const options = {
    hostname: 'cdn.example.com'
};

app.use(electricity.static('public'), options);

Then render URLs using the view helper like this:

<img src="<%= electricity.url('/images/image.png') %>" />
<link href="<%= electricity.url('/styles/style.css') %>" rel="stylesheet" />
<script src="<%= electricity.url('/scripts/script.js') %>"></script>

Your HTML will ultimately get rendered using absolute URLs like this:

<img src="https://cdn.example.com/images/image-423251d722a53966eb9368c65bfd14b39649105d.png" />
<link href="https://cdn.example.com/styles/style-22a53914b39649105d66eb9368c65b423251d7fd.css" rel="stylesheet" />
<script src="http://cdn.example.com/scripts/script-5d66eb9368c22a53914b39d7fd6491065b423251.js"></script>

electricity's People

Contributors

bcullman avatar ccrutcher avatar cvan avatar dependabot[bot] avatar freshlogic avatar harrison-m avatar javamatte avatar mattschuette avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

electricity's Issues

Gzip detection is incorrect.

For example, this header should cause electricity to prefer identity.

Accept-Encoding: identity; q=1.0, gzip;q=0.5, *;q=0

Whereas this one should choose anything but gzip:

Accept-Encoding: gzip;q=0, *;q=1.0

This one should prefer gzip all of the time:

Accept-Encoding: gzip;q=1.0, *;q=0.5

This one should only allow gzip:

Accept-Encoding: gzip;q=1.0, *;q=0

The Negotiator project is a Node.js module that will appropriately select an encoding.

Test "correctly redirects for a file whose filename contains something looking like a hash" does not validate assumption

The test "correctly redirects for a file whose filename contains something looking like a hash" never verifies that the bool "redirected" bool was set to true.

I created a pull request (#36) that adds the missing assert, but it causes a new problem - the test does not pass.

The path provided in the test "/robots-3e.txt" is dehashified to "/robots.txt", which is then hashified to "/robots-ca121b5d03245bf82db00d14cee04e22.txt". This is not match for the expected url. The added assert now shows a failure.

Either we should change the expected value in the test to "/robots-ca121b5d03245bf82db00d14cee04e22.txt"
OR
We should ONLY dehashify urls that contain actual 32 character hashes

I am not certain why we would want to redirect for filenames that have something that looks like a hash, rather than actually having a hash.

Handle JS_Parse_Error

Seems like when UglifyJS throws a JS_Parse_Error, electricity stops working...
Probably this behavior should change because we don't want our app to interrupt when it tries to serve a file with some error in it...

sass-graph isn't working with ./../config.scss type paths

If I have a couple of scss files like this...
/public/styles/config.scss
/public/styles/forum/index.scss

and if /public/styles/forum/index.scss does something like this:
@import './../config.scss'

whenever the config file is updated, the cache of index.scss isn't updated

Add file system watcher

As a developer, I want electricity to update it's asset cache when static files are changed, so that I can refresh my browser and see updated CSS, JavaScript, and images without needing to restart my Express.js app

Using /public prefix

Here is how we use the express handler:

app.use('/public', express.static(path.join(__dirname, '/../../public')

What configuration can we use for electricity to use it as a replacement?

res.send(status) deprecated

Mon, 22 Sep 2014 13:50:50 GMT express deprecated res.send(status): Use res.status(status).end() instead at node_modules\electricity\lib\index.js:350:17

CSS URI value detection should ignore query parameters

For CSS rules like this:
src: url('/fonts/font-awesome/fontawesome-webfont.eot?#iefix');

Expected output:
src: url(/fonts/font-awesome/fontawesome-webfont-f7c2b4b747b1a225eb8dee034134a1b0.eot?#iefix);

Actual output:
src: url('/fonts/font-awesome/fontawesome-webfont.eot?#iefix');

Add option to specify a hostname

app.js:
app.use(electricity.static(__dirname + '/public', { hostname: 'abc.cloudfront.net' }));

view.ejs:
<%= electricity.url('/styles/main.css') %>

expected scheme relative URL:
//abc.cloudfront.net/styles/main-d131dd02c5e6eec4.css

Express view helper

Create a helper method for Express.js views in order to generate fingerprinted URLs for static assets

Have uglify-js minification support ES6 JavaScript

uglify-js supports only ES5 code.

I started a new branch using babel-minify, but I decided that uglify-es is likely the more appropriate library to use, since electricity is designed today for only minifying JS (not transpiling and minifying, which is slower and not always what developers would want). (If folks want Babel transpiling and minifying, I suppose a follow-up issue could be filed for that.)

Here are some examples of the JS that uglify-js was choking on:

(() => {

fails with

UglifyJS skipping /opt/project/static/js/index.js:
    {"message":"Unexpected token: punc ())","filename":"0","line":2,"col":2,"pos":76}

and

class Store {

fails with

UglifyJS skipping /opt/project/static/js/vendor/idb-keyval/3.1.0/dist/idb-keyval-iife.js:
    {"message":"Unexpected token: name (Store)","filename":"0","line":4,"col":6,"pos":59}

Switch from sass.renderSync to sass.compile

The existing renderSync calls are deprecated. Already, they are returning Legacy* objects. The compile method is the replacement, but the filename doesn't seem to be part of the options. I'm not sure if the other options have any changes, but our own codebase doesn't seem to use any.

Given that this is public, it's probably a major version to switch from renderSync to compile. Alternatively, there is an initCompiler which creates a long-lived object for doing the compiling that is apparently faster. That may need a little more rework to fit in.

Add support for .sass files

We support Sass files that have a .scss extension. But there's a second, older syntax known as the indented syntax that have .sass extensions.

http://stackoverflow.com/questions/5654447/whats-the-difference-between-scss-and-sass

@Harrison-M thinks that node-sass already can handle the .sass syntax, but @lox would have to handle those in his awesome sass-graph module.

https://github.com/lox/sass-graph

We might submit a PR for sass-graph in the future to support .sass files.

Need an easier way to tell it to compile but don't compress in development

Currently I'm doing this:

var electricityOpts = {
    uglifyjs: { enabled: process.env.NODE_ENV === 'production' },
    uglifycss: { enabled: process.env.NODE_ENV === 'production' },
};
app.use(electricity.static(__dirname + '/public', electricityOpts));

I'd think this would be pretty common, because during development you may need to set breakpoints (js) or edit in the browser and grab the updated sources (css), or be able to read the compiled output (sass/less).

Would rather do something like:

app.use(electricity.static(__dirname + '/public', { compress: process.env.NODE_ENV === 'production' });

... and have that turn off minification on all types of files in development.

Add support for `Range` header

The headers involved are:

  • Accept-Ranges
  • Range
  • Content-Range
  • If-Range

Also note possible changes to logic for:

  • If-Modified-Since
  • If-Unmodified-Since
  • If-Match
  • If-None-Match

RFC

Initial Express.js Middleware

Create Express.js middleware that, on app startup, reads all files in the specified directory and caches MD5 hash of file's contents

Missing URL substitutions for @font-face URLs

URLs with query strings and fragments in the standard webfont syntax aren't being replaced with electricity URLs

@font-face {
  font-family: 'Proxima Nova Regular';
  src: url(/fonts/proxima-nova-regular/webfont-eaaf761f3665045d37e8650a17d37288.eot);
  src: url('/fonts/proxima-nova-regular/webfont.eot?#iefix') format('embedded-opentype'), url(/fonts/proxima-nova-regular/webfont-d6e2bf3f97fd098b9ab019b64e9ccfbd.woff) format('woff'), url(/fonts/proxima-nova-regular/webfont-6c2a5f1081643c105208fe4cd64ae41e.ttf) format('truetype'), url('/fonts/proxima-nova-regular/webfont.svg#webfont') format('svg'); }

How could I use electricity.url with Mustache?

I had a problem with express.static (some files randomly was hanging for 2 minutes) and so I've switched to your component.

It's just GREAT!

Now I'd like to use the electricity.url function but I've a problem: I use Mustache as template engine.

Is there some easy way to render URLs using Mustache or do I have to pass URLs as parameters?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.