Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

electricity

Package Overview
Dependencies
Maintainers
3
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

electricity - npm Package Compare versions

Comparing version 2.9.0 to 3.0.0

.nyc_output/processinfo/15c13163-7ae4-464f-ab56-14d6159a2c38.json

9

CHANGELOG.md

@@ -9,2 +9,11 @@ # Changelog

## [3.0.0] - 2021-11-17
### Changed
- Version 3.0.0 is a complete rewrite designed to improve application startup times by lazily processing files as they are requested (instead of eagerly processing all files at application start).
- Because files are processed lazily as they are requested, many users should experience reduced memory footprints for their apps.
- We dropped support for the `If-Modified-Since` HTTP header due to the complexities in calculating this accurately for Snockets and SASS dependency graphs. The ETag HTTP header is still supported.
- In v2.9.0 we replaced `react-tools` with `@babel/preset-react`. You can now pass along options to babel.
- When using a CDN hostname we always return absolute URLs with the https:// origin (instead of protocol-relative URLs since this is now an anti-pattern: https://www.paulirish.com/2010/the-protocol-relative-url/).
- We no longer GZip content that is less than 1500 bytes (the size of a single TCP packet).
## [2.9.0] - 2021-11-02

@@ -11,0 +20,0 @@ ### Changed

2

lib/gzipContentTypes.js

@@ -11,2 +11,2 @@ module.exports = [

'text/plain'
];
];

@@ -16,8 +16,9 @@ const crypto = require('crypto');

const availableEncodings = ['gzip', 'identity'];
const gzipContentTypes = require('./gzipContentTypes.js');
exports.static = function static(directory, options) {
directory = path.resolve(directory || 'public');
exports.static = function(directory, options) {
// Default to 'public' if the directory is not specified
directory = directory || 'public';
// Options are optional
if (!options) {

@@ -27,3 +28,8 @@ options = {};

if (!('hashify' in options)) {
if (!options.babel) {
options.babel = {};
}
// Hashify by default
if (!Object.prototype.hasOwnProperty.call(options, 'hashify')) {
options.hashify = true;

@@ -40,8 +46,6 @@ }

if (!options.jsx) {
options.jsx = {};
}
// Snockets must be processed syncronously to produce consistent output
options.snockets.async = false;
// UglifyCSS by default
if (!options.uglifycss) {

@@ -53,2 +57,3 @@ options.uglifycss = {

// UglifyJS by default
if (!options.uglifyjs) {

@@ -60,306 +65,306 @@ options.uglifyjs = {

// Don't watch for changes by default
if (!options.watch) {
options.watch = {
enabled: true
enabled: false
};
}
if (options.hostname) {
if (typeof options.hostname !== 'string') {
throw Error('hostname must be a string');
} else if (options.hostname.slice(-1) === '/') {
options.hostname = options.hostname.slice(0, options.hostname.length - 1);
}
}
// Create a local cache to hold the files
const files = {};
if (options.sass.imagePath) {
if (typeof options.sass.imagePath !== 'string') {
throw Error('sass.imagePath must be a string');
}
}
const snockets = new Snockets();
if (!options.sass.functions) {
options.sass.functions = {};
}
let watcher;
options.sass.functions['image-url($img)'] = function(img) {
return new sass.types.String('url("' +
(options.sass.imagePath ?
path.join('/', options.sass.imagePath, img.getValue()) :
path.join('/', img.getValue())
) +
'")');
};
if (options.watch.enabled) {
// Setup the watcher
watcher = chokidar.watch(directory, { ignoreInitial: true });
var files = {};
var snockets = new Snockets();
var stylesheets = [];
var watcher;
watcher.on('all', function(eventName, filePath) {
removeFile(filePath);
});
}
function cacheFile(filePath, stat) {
var data;
var ext = path.extname(filePath);
var relativeUrl = toRelativeUrl(filePath);
/**
* Tries to read a file from local cache.
* Reads the file from disk if it's not present in the local cache.
* @param {string} urlPath
*/
function fetchFile(urlPath) {
// Try to get the file from local cache
let file = files[urlPath];
// Compilation steps
if (ext === '.scss' && !shouldIgnore(relativeUrl, options.sass.ignore)) {
// Sass
options.sass.file = filePath;
data = sass.renderSync(options.sass).css.toString();
// Future steps should treat this as plain CSS
relativeUrl = relativeUrl.replace(/.scss$/, '.css');
ext = '.css';
} else if (ext === '.js' && !shouldIgnore(relativeUrl, options.snockets.ignore)) {
// Snockets
try {
data = snockets.getConcatenation(filePath, options.snockets);
} catch (e) {
// Snockets can't parse, so just pass the js file along
console.warn(`Snockets skipping ${filePath}:\n ${e}`);
data = fs.readFileSync(filePath).toString();
}
// Return the file from cache if found
if (file) {
return file;
}
if (ext === '.js' && !shouldIgnore(relativeUrl, options.jsx.ignore)) {
// React
data = data || fs.readFileSync(filePath).toString();
// Read the file from disk
file = readFile(urlPath);
try {
let result = babel.transformSync(data, {
presets: ['@babel/preset-react']
});
// Put the file in local cache
files[urlPath] = file;
data = result.code;
} catch(e) {
// React can't transform, so just pass the js file along
console.warn(`JSX compiler skipping ${filePath}:\n ${e}`);
data = fs.readFileSync(filePath).toString();
}
}
return file;
}
// Postprocessing steps
if (ext === '.css') {
if (options.uglifycss.enabled) {
// Uglifycss
data = data ? UglifyCss.processString(data, options.uglifycss) : UglifyCss.processFiles([filePath], options.uglifycss);
}
} else if (ext === '.js' && options.uglifyjs.enabled && !shouldIgnore(relativeUrl, options.uglifyjs.ignore)) {
var uglifyjsOptions = JSON.parse(JSON.stringify(options.uglifyjs));
delete uglifyjsOptions.enabled;
delete uglifyjsOptions.ignore;
if (uglifyjsOptions.sourceMap === true) {
uglifyjsOptions.sourceMap.filename = path.basename(relativeUrl);
uglifyjsOptions.sourceMap.out = `${uglifyjsOptions.sourceMap.filename}.map`;
}
var result = UglifyJS.minify(data, uglifyjsOptions);
if (result.error) {
console.warn(`UglifyJS skipping ${filePath}:\n ${JSON.stringify(result.error)}`);
} else {
data = result.code;
}
/**
* Converts a URL (/robots.txt) to a URL that includes the file's hash (/robots-3f54004ef6fc21b24a9e6069fc114fd9070b77a1.txt)
* @param {string} url
* @param {string} hash
*/
function hashifyUrl(url, hash) {
if (!url.includes('.')) {
return url.replace(/([?#].*)?$/, `-${hash}$1`);
}
// Have data?
data = data || fs.readFileSync(filePath);
return url.replace(/\.([^.]*)([?#].*)?$/, `-${hash}.$1$2`);
}
// Cache file and hash, generate hash later if it's CSS because we're going to modify the contents
files[relativeUrl] = {
content: data,
contentLength: typeof data === 'string' ? Buffer.byteLength(data) : data.length,
contentType: mime.getType(relativeUrl),
hash: ext !== '.css' ? crypto.createHash('md5').update(data).digest('hex') : '',
modified: stat.mtime
};
/**
* Parses a URL path potentially containing a hash (/robots-3f54004ef6fc21b24a9e6069fc114fd9070b77a1.txt)
* into an object with a hash and path properties ({ hash: '3f54004ef6fc21b24a9e6069fc114fd9070b77a1', path: '/robots.txt' })
* @param {object} req
*/
function parseUrlPath(urlPath) {
// https://regex101.com/r/j5hvRj/1
const regex = /\/.+(-([0-9a-f]{40}))/;
const matches = urlPath.match(regex);
// If-Modified-Since doesn't support millisecond precision
files[relativeUrl].modified.setMilliseconds(0);
if (ext === '.css') {
stylesheets.push(relativeUrl);
}
return relativeUrl;
}
function dehashifyPath(filePath) {
if (!options.hashify) {
if (!matches) {
return {
path: filePath,
hash: undefined
path: urlPath
};
}
var hashRegex = /-[0-9a-f]+(\.[^.]*$)/;
var hashMatch = filePath.match(hashRegex);
var hash = hashMatch ? hashMatch[0].slice(1).replace(/\.([^.]*$)/, '') : '';
return {
path: hash.length == 32 ? filePath.replace(hashRegex, '$1') : filePath,
hash: hash.length == 32 ? hash : null
hash: matches[2],
path: urlPath.replace(matches[1], '')
};
}
function gzip(file, callback) {
if (file.gzippedContent && file.gzippedContentLength) {
return callback(null, file);
}
function readCascadingStyleSheetsFile(filePath) {
let data;
zlib.gzip(file.content, function(err, gzippedContent) {
if (err) {
return callback(err);
// CSS
try {
data = fs.readFileSync(filePath).toString();
} catch(err) {
// Handle ENOENT (No such file or directory): https://nodejs.org/api/errors.html#common-system-errors
if (err.code !== 'ENOENT') {
throw err;
}
file.gzippedContent = gzippedContent;
file.gzippedContentLength = gzippedContent.length;
// SASS
const basename = path.basename(filePath, path.extname(filePath));
options.sass.file = path.join(path.dirname(filePath), `${basename}.scss`);
const result = sass.renderSync(options.sass);
data = result.css.toString();
callback(null, file);
// SASS (watcher)
if (watcher) {
result.stats.includedFiles.forEach(file => {
watcher.add(file);
});
}
}
// Update URLs in CSS: https://regex101.com/r/FxrppP/4
data = data.replace(/url\(['"]?(.*?)['"]?\)/g, function(match, p1) {
return `url(${urlBuilder(p1)})`;
});
// UglifyCSS
if (options.uglifycss.enabled) {
data = UglifyCss.processString(data, options.uglifycss);
}
return data;
}
function hashifyCss(cssPath) {
return files[cssPath].content.toString().replace(/url\(['"]?(.*?)['"]?\)/g, function(match, filename) {
var file;
function readFile(urlPath) {
let filePath = toFilePath(urlPath);
let extension = path.extname(filePath);
let data;
if (filename[0] === '/') {
file = files[filename];
} else {
var cssDir = path.dirname(cssPath);
filename = path.normalize(path.join(cssDir, filename)).replace(/\\/g, '/');
file = files[filename];
}
if (extension === '.css') {
data = readCascadingStyleSheetsFile(filePath);
} else if (extension === '.js') {
data = readJavaScriptFile(filePath);
} else {
data = fs.readFileSync(filePath);
}
if (file) {
return 'url(' + urlBuilder(filename) + ')';
}
const file = {
content: data,
contentLength: data.length,
contentType: mime.getType(urlPath),
hash: crypto.createHash('sha1').update(data).digest('hex')
};
return match;
});
}
// Don't gzip any content less that 1500 bytes (the size of a TCP packet). Only gzip specific content types.
if (file.contentLength > 1500 && gzipContentTypes.includes(file.contentType)) {
const gzipContent = zlib.gzipSync(file.content);
function hashifyPath(filePath, hash) {
if (!filePath.includes('.')) {
return filePath.replace(/([?#].*)?$/, `-${hash}$1`);
file.gzip = {
content: gzipContent,
contentLength: gzipContent.length
};
}
return filePath.replace(/\.([^.]*)([?#].*)?$/, `-${hash}.$1$2`);
return file;
}
function prefixSlash(path) {
return path[0] === '/' ? path : '/' + path;
}
function readJavaScriptFile(filePath) {
let data = fs.readFileSync(filePath).toString();
function reloadFile(filePath, stat) {
stat = typeof stat === 'object' ? stat : fs.statSync(filePath);
// Snockets
try {
data = snockets.getConcatenation(filePath, options.snockets);
} catch(err) {
// Snockets can't parse, so just pass the js file along
console.warn(`Snockets skipping ${filePath}:\n ${err}`);
}
var ext = path.extname(filePath);
// Snockets (watcher)
if (watcher) {
try {
// Get all files in the snockets chain
const compiledChain = snockets.getCompiledChain(filePath, options.snockets);
if (ext === '.scss') {
sassGraph.parseDir(directory).index[filePath].importedBy.forEach(reloadFile);
} else if (ext === '.js') {
// Clear snockets cache
snockets.cache = {};
snockets.concatCache = {};
snockets.scan(filePath);
snockets.depGraph.parentsOf(filePath).forEach(reloadFile);
// Add each file of the snockets chain to the watcher
compiledChain.forEach(c => {
watcher.add(c.filename);
});
} catch(err) {
// Snockets can't parse, so skip watch
console.warn(`Snockets skipping watch for ${filePath}:\n ${err}`);
}
}
var relativeUrl = cacheFile(filePath, stat);
// Babel
try {
let result = babel.transformSync(data, {
...options.babel,
presets: [require('@babel/preset-react')]
});
if (stylesheets.includes(relativeUrl)) {
files[relativeUrl].content = hashifyCss(relativeUrl);
files[relativeUrl].hash = crypto.createHash('md5').update(files[relativeUrl].content).digest('hex');
data = result.code;
} catch(err) {
// Babel can't transform, so just pass the file along
console.warn(`Babel skipping ${filePath}:\n ${err}`);
}
}
function toRelativeUrl(filePath) {
var relativeUrl = path.relative(directory, path.resolve(filePath));
// UglifyJS
if (options.uglifyjs.enabled) {
const uglifyjsOptions = JSON.parse(JSON.stringify(options.uglifyjs));
delete uglifyjsOptions.enabled;
// Make URI-friendly and prepend a /
return prefixSlash(relativeUrl).replace(/\\/g, '/');
}
const result = UglifyJS.minify(data, uglifyjsOptions);
function shouldIgnore(filePath, ignore) {
if (!ignore) {
// Not ignoring anything
return false;
if (result.error) {
console.warn(`UglifyJS skipping ${filePath}:\n ${JSON.stringify(result.error)}`);
} else {
data = result.code;
}
}
if (Array.isArray(ignore)) {
// Multiple things to ignore
return ignore.some(function ignoreElementMatch(ignored) {
return shouldIgnore(filePath, ignored);
});
return data;
}
/**
* Removes a file from the local cache.
* @param {*} filePath
*/
function removeFile(filePath) {
let extension = path.extname(filePath);
if (extension === '.js') {
return removeJavaScriptFile(filePath);
} else if (extension === '.scss') {
return removeSassFile(filePath);
}
return filePath.match(ignore) !== null;
// Remove the changed file from the local cache
delete files[toUrlPath(filePath)];
}
function stripQueryAndTarget(filePath) {
return filePath.replace(/[?#].*$/, '');
/**
* Removes a JavaScript file from the local cache.
* @param {string} filePath
*/
function removeJavaScriptFile(filePath) {
// Remove the changed file from the local cache
delete files[toUrlPath(filePath)];
// Resolve the absolute file path for the changed file
const absoluteFilePath = path.resolve(filePath);
// Find any parents that have a dependency on this file and remove them too
snockets.depGraph.parentsOf(absoluteFilePath).forEach(removeJavaScriptFile);
}
function urlBuilder(filePath) {
var file = files[stripQueryAndTarget(prefixSlash(filePath))];
/**
* Removes a SASS file from the local cache.
* @param {string} filePath
*/
function removeSassFile(filePath) {
const basename = path.basename(filePath, path.extname(filePath));
const cssFilePath = path.join(path.dirname(filePath), `${basename}.css`);
const urlPath = toUrlPath(cssFilePath);
if (file) {
var uri = prefixSlash(filePath);
// Remove the changed file from the local cache
delete files[urlPath];
if (options.hashify) {
uri = hashifyPath(uri, file.hash);
}
// Resolve the absolute file path for the changed file
const absoluteFilePath = path.resolve(filePath);
if (options && options.hostname) {
uri = '//' + options.hostname + uri;
}
const graph = sassGraph.parseDir(directory);
const sassFile = graph.index[absoluteFilePath];
return uri;
} else {
return filePath;
if (sassFile) {
sassFile.importedBy.forEach(removeSassFile);
}
}
// Load all files synchronously
(function loadFiles(workingDir) {
var contents = fs.readdirSync(workingDir);
function urlBuilder(urlPath) {
let url = urlPath;
contents.forEach(function loadDirectory(file) {
var filePath = path.join(workingDir, file);
var stat = fs.statSync(filePath);
if (options.hashify) {
try {
const request = parseUrlPath(urlPath);
const file = fetchFile(request.path);
if (stat.isDirectory()) {
return loadFiles(filePath);
url = hashifyUrl(request.path, file.hash);
} catch(err) {
// If we don't have a file that matches the specified URL path simply return the original URL path
}
}
cacheFile(filePath, stat);
});
})(directory);
if (options.hostname) {
url = `https://${options.hostname}${url}`;
}
// Hashify URLs in stylesheets
stylesheets.forEach(function(stylesheet) {
files[stylesheet].content = hashifyCss(stylesheet);
files[stylesheet].hash = crypto.createHash('md5').update(files[stylesheet].content).digest('hex');
});
return url;
}
// Watch for changes
if (options.watch.enabled) {
const jsDirectories = Array.from(new Set(Object.keys(snockets.depGraph.map).map(d => path.dirname(d))));
const sassDirectories = Array.from(new Set(Object.keys(sassGraph.parseDir(directory).index).map(d => path.dirname(d))));
/**
* Converts a URL path (/robots.txt) to a file path (/Users/username/site/public/robots.txt).
* @param {string} urlPath
*/
function toFilePath(urlPath) {
const myURL = new URL(urlPath, 'https://example.org/');
const pathname = myURL.pathname.replace(/^\//, '');
return path.resolve(directory, pathname);
}
watcher = chokidar.watch([directory, ...jsDirectories, ...sassDirectories], {
ignoreInitial: true
});
/**
* Converts a file path (/Users/username/site/public/robots.txt) to a URL path (/robots.txt).
* @param {string} urlPath
*/
function toUrlPath(filePath) {
const urlPath = path.posix.relative(directory, path.resolve(filePath));
watcher.on('add', reloadFile);
watcher.on('change', reloadFile);
watcher.on('unlink', function(path) {
var compiledPath = path.replace(/\.scss$/, '.css');
delete files[toRelativeUrl(compiledPath)];
});
return `/${urlPath}`;
}

@@ -369,8 +374,8 @@

// Ignore anything that's not a GET or HEAD request
if (req.method !== 'GET' && req.method !== 'HEAD') {
if (!['GET', 'HEAD'].includes(req.method)) {
return next();
}
// Register view helper if we haven't already
if (!req.app.locals.electricity) {
// Register function in app.locals to help views build URLs: https://expressjs.com/en/api.html#app.locals
if (req.app && !req.app.locals.electricity) {
req.app.locals.electricity = {

@@ -381,31 +386,46 @@ url: urlBuilder

var reqInfo = dehashifyPath(req.path);
var file = files[reqInfo.path];
let file;
const request = parseUrlPath(req.path);
if (!file) {
return next();
try {
file = fetchFile(request.path);
} catch(err) {
// Handle EISDIR (Is a directory): https://nodejs.org/api/errors.html#common-system-errors
if (err.code === 'EISDIR') {
return next();
}
// Handle ENOENT (No such file or directory): https://nodejs.org/api/errors.html#common-system-errors
if (err.code === 'ENOENT') {
return next();
}
return next(err);
}
// Verify file matches the requested hash, otherwise 302
if (options.hashify && reqInfo.hash !== file.hash) {
if (options.hashify && request.hash !== file.hash) {
res.set({
'Cache-Control': 'no-cache',
'Expires': '0',
'Pragma': 'no-cache'
'cache-control': 'no-cache',
'expires': '0',
'pragma': 'no-cache'
});
return res.redirect(hashifyPath(reqInfo.path, file.hash));
const url = hashifyUrl(request.path, file.hash);
return res.redirect(url);
}
var expires = new Date();
expires.setYear(expires.getFullYear() + 1);
// Set a far-future expiration date
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
res.set({
'Cache-Control': 'public, max-age=31536000',
'Content-Type': file.contentType,
'ETag': file.hash,
'Expires': expires.toUTCString(),
'Last-Modified': file.modified.toUTCString()
'cache-control': 'public, max-age=31536000',
'content-Type': file.contentType,
etag: file.hash,
expires: expires.toUTCString()
});
// Set any other headers specified in options
if (options.headers) {

@@ -415,36 +435,36 @@ res.set(options.headers);

if (req.get('If-None-Match') === file.hash) {
res.status(304);
res.end();
} else if (new Date(req.get('If-Modified-Since')) >= file.modified) {
res.status(304);
res.end();
} else {
res.set({ 'Content-Length': file.contentLength });
res.status(200);
const ifNoneMatch = req.get('if-none-match');
if (req.method === 'HEAD') {
return res.end();
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match
if (ifNoneMatch && ifNoneMatch.includes(file.hash)) {
return res.sendStatus(304);
}
var negotiator = new Negotiator(req);
// By default, send the file's content (without gzip)
let content = file.content;
let contentLength = file.contentLength;
if (negotiator.encodings(availableEncodings).indexOf('gzip') === 0 && gzipContentTypes.indexOf(file.contentType) !== -1) {
gzip(file, function(err, gzippedFile) {
if (err) {
return next(err);
}
// Check to see if the file could be gzipped
if (file.gzip && file.gzip.content) {
const negotiator = new Negotiator(req);
res.set({
'Content-Encoding': 'gzip',
'Content-Length': gzippedFile.gzippedContentLength
});
// Ensure the request supports gzip
if (negotiator.encodings().includes('gzip')) {
content = file.gzip.content;
contentLength = file.gzip.contentLength;
res.send(file.gzippedContent);
});
} else {
res.send(file.content);
res.set('content-encoding', 'gzip');
}
}
// Set the content-length header
res.set('content-length', contentLength);
// Return early without sending content for HEAD requests
if (req.method === 'HEAD') {
return res.sendStatus(200);
}
res.send(content);
};
};
};

@@ -8,3 +8,3 @@ {

"negotiator": "~0.6.2",
"sass": "~1.43.2",
"sass": "~1.32.13",
"sass-graph": "~3.0.4",

@@ -16,3 +16,2 @@ "snockets": "~1.3.8",

"devDependencies": {
"buffer-compare": "*",
"coveralls": "*",

@@ -37,5 +36,5 @@ "fs-extra": "*",

"coveralls": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
"test": "mocha --exit -R spec -t 21000"
"test": "mocha --exit -R spec"
},
"version": "2.9.0"
"version": "3.0.0"
}

@@ -17,5 +17,5 @@ # Electricity

```javascript
var express = require('express');
const express = require('express');
app.use(express.static(__dirname + '/public'));
app.use(express.static('public'));
```

@@ -26,6 +26,6 @@

```javascript
var express = require('express');
var electricity = require('electricity');
const express = require('express');
const electricity = require('electricity');
app.use(electricity.static(__dirname + '/public'));
app.use(electricity.static('public'));
```

@@ -43,3 +43,3 @@

```ejs
<img src="<%= electricity.url('/apple-touch-icon-precomposed.png') %>" />
<img src="<%= electricity.url('/images/image.png') %>" />
<link href="<%= electricity.url('/styles/style.css') %>" rel="stylesheet" />

@@ -52,5 +52,5 @@ <script src="<%= electricity.url('/scripts/script.js') %>"></script>

```html
<img src="/apple-touch-icon-precomposed-d131dd02c5e6eec4.png" />
<link href="/styles/style-693d9a0698aff95c.css" rel="stylesheet" />
<script src="/scripts/script-2fcab58712467eab.js"></script>
<img src="/images/image-423251d722a53966eb9368c65bfd14b39649105d.png" />
<link href="/styles/style-22a53914b39649105d66eb9368c65b423251d7fd.css" rel="stylesheet" />
<script src="/scripts/script-5d66eb9368c22a53914b39d7fd6491065b423251.js"></script>
```

@@ -62,3 +62,3 @@

- **HTTP Headers:** Electricity sets proper `Cache-Control`, `ETag`, `Expires`, and `Last-Modified` headers to help avoid unnecessary HTTP requests on subsequent page views.
- **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.

@@ -77,3 +77,4 @@ - **Gzip:** Electricity gzips many content types (CSS, HTML, JavaScript, JSON, plaintext, XML) to reduce response sizes.

```javascript
var options = {
const options = {
babel: {},
hashify: true,

@@ -85,6 +86,3 @@ headers: {},

uglifyjs: {
enabled: true,
compress: {
sequences: false
}
enabled: true
},

@@ -101,20 +99,26 @@ uglifycss: {

var options = {
babel: { // Object passed straight to @babel/core options: https://babeljs.io/docs/en/options
generatorOpts: {
compact: false
},
parserOpts: {
errorRecovery: true
}
},
hashify: false, // Do not generate hashes for URLs
headers: { 'Access-Control-Allow-Origin': 'http://foo.example' },
headers: { // Any additional headers you want a specify
'Access-Control-Allow-Origin': 'https://example.com'
},
hostname: 'cdn.example.com', // CDN hostname
jsx: { // Object passed straight to react-tools options
ignore: ['raw', /donotcompile/] // Files to skip compilation on, can be a single argument to String.prototype.match or an array
}
sass: { // Object passed straight to node-sass options
imagePath: '/images', // Image path for sass image-url helper
ignore: ['raw', /donotcompile/] // Files to skip compilation on, can be a single argument to String.prototype.match or an array
outputStyle: 'compressed',
quietDeps: true
},
snockets: { // Object passed straight to snockets options
ignore: ['raw', /donotcompile/] // Files to skip compilation on, can be a single argument to String.prototype.match or an array
snockets: { // Object passed straight to snockets options: https://www.npmjs.com/package/snockets
},
uglifyjs: { // Object passed straight to uglify-js options
enabled: true // Minify Javascript
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
enabled: true // Minify CSS
uglifycss: { // Object passed straight to uglifycss options: https://github.com/fmarcia/uglifycss
enabled: false // Do not minify CSS
}

@@ -127,3 +131,3 @@ };

```javascript
app.use(electricity.static(__dirname + '/public', options));
app.use(electricity.static('public', options));
```

@@ -133,6 +137,6 @@

Electricity sets proper `Cache-Control`, `ETag`, `Expires`, and `Last-Modified` 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.
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(__dirname + '/public', {
app.use(electricity.static('public', {
headers: { 'Access-Control-Allow-Origin': '*' }

@@ -144,6 +148,6 @@ }));

Electricity will automatically rewrite URIs in CSS to use MD5 hashes (if a matching file is found). For example:
Electricity will automatically rewrite URIs in CSS to use SHA1 hashes (if a matching file is found). For example:
```css
background-image: url(/apple-touch-icon-precomposed.png);
background-image: url(/background.png);
```

@@ -154,3 +158,3 @@

```css
background-image: url(/apple-touch-icon-precomposed-d131dd02c5e6eec4.png);
background-image: url(/background-423251d722a53966eb9368c65bfd14b39649105d.png);
```

@@ -162,10 +166,10 @@

```javascript
var express = require('express');
var electricity = require('electricity');
const express = require('express');
const electricity = require('electricity');
var options = {
const options = {
hostname: 'cdn.example.com'
};
app.use(electricity.static(__dirname + '/public'), options);
app.use(electricity.static('public'), options);
```

@@ -175,3 +179,3 @@

```ejs
<img src="<%= electricity.url('/apple-touch-icon-precomposed.png') %>" />
<img src="<%= electricity.url('/images/image.png') %>" />
<link href="<%= electricity.url('/styles/style.css') %>" rel="stylesheet" />

@@ -181,7 +185,7 @@ <script src="<%= electricity.url('/scripts/script.js') %>"></script>

Your HTML will ultimately get rendered using protocol-relative URLs like this:
Your HTML will ultimately get rendered using absolute URLs like this:
```html
<img src="//cdn.example.com/apple-touch-icon-precomposed-d131dd02c5e6eec4.png" />
<link href="//cdn.example.com/styles/style-693d9a0698aff95c.css" rel="stylesheet" />
<script src="//cdn.example.com/scripts/script-2fcab58712467eab.js"></script>
```
<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>
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc