Last active
March 28, 2018 12:27
-
-
Save ninetails/86a22f9f258cdc2704592686f534f329 to your computer and use it in GitHub Desktop.
resource-hint-webpack-plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const assert = require('assert'); | |
const objectAssign = require('object-assign'); | |
const minimatch = require('minimatch'); | |
const path = require('path'); | |
const preloadDirective = { | |
'.js': 'script', | |
'.css': 'style', | |
'.woff': 'font', | |
'.woff2': 'font', | |
'.jpeg': 'image', | |
'.jpg': 'image', | |
'.gif': 'image', | |
'.png': 'image', | |
'.svg': 'image' | |
}; | |
// By default all files are prefetched and preload | |
const defaultFilter = ['**/*.*']; | |
class ResourceHintWebpackPlugin { | |
constructor (options) { | |
assert.equal(options, undefined, 'The ResourceHintWebpackPlugin does not accept any options'); | |
} | |
apply (compiler) { | |
// Hook into the html-webpack-plugin processing | |
if (compiler.hooks) { | |
// Webpack 4+ Plugin System | |
compiler.hooks.compilation.tap('ResourceHintWebpackPlugin', compilation => { | |
if (compilation.hooks.htmlWebpackPluginAlterAssetTags) { | |
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('ResourceHintWebpackPluginAlterAssetTags', | |
resourceHintWebpackPluginAlterAssetTags | |
); | |
} | |
}); | |
} else { | |
// Webpack 1-3 Plugin System | |
compiler.plugin('compilation', compilation => { | |
compilation.plugin('html-webpack-plugin-alter-asset-tags', | |
resourceHintWebpackPluginAlterAssetTags | |
); | |
}); | |
} | |
} | |
} | |
/** | |
* The main processing function | |
*/ | |
function resourceHintWebpackPluginAlterAssetTags (htmlPluginData, callback) { | |
const htmlWebpackPluginOptions = htmlPluginData.plugin.options; | |
const pluginData = objectAssign({}, htmlPluginData); | |
const tags = { | |
prefetch: [], | |
// https://w3c.github.io/preload/#link-type-preload | |
preload: [] | |
}; | |
// Create Resource tags | |
Object.keys(tags).forEach(resourceHintType => { | |
// Check if it is disabled for the current htmlWebpackPlugin instance: | |
// e.g. | |
// new HtmlWebpackPlugin({ | |
// prefetch: false | |
// }) | |
if (htmlWebpackPluginOptions[resourceHintType] === false) { | |
return; | |
} | |
// If no options are found all files are prefetched / preload | |
const fileFilters = htmlWebpackPluginOptions[resourceHintType] | |
? [].concat(htmlWebpackPluginOptions[resourceHintType]) | |
: defaultFilter; | |
// Process every filter | |
fileFilters.forEach(filter => { | |
if (filter.indexOf('*') !== -1) { | |
Array.prototype.push.apply(tags[resourceHintType], addResourceHintTags( | |
resourceHintType, | |
filter, | |
pluginData.body, | |
htmlWebpackPluginOptions | |
)); | |
} else { | |
tags[resourceHintType].push(createResourceHintTag(filter, resourceHintType, htmlWebpackPluginOptions)); | |
} | |
}); | |
}); | |
// Add all Resource tags to the head | |
Array.prototype.push.apply(pluginData.head, tags.preload.map(addPreloadType)); | |
Array.prototype.push.apply(pluginData.head, tags.prefetch); | |
callback(null, pluginData); | |
} | |
/** | |
* Adds Resource hint tags | |
*/ | |
function addResourceHintTags (resourceHintType, filter, assetTags, htmlWebpackPluginOptions) { | |
const urls = assetTags | |
.map(tag => tag.attributes.src || tag.attributes.href) | |
.filter(url => url) | |
.filter(minimatch.filter(filter)); | |
// Add a ResourceHint for every match | |
return urls.map(url => createResourceHintTag(url, resourceHintType, htmlWebpackPluginOptions)); | |
} | |
function createResourceHintTag (url, resourceHintType, htmlWebpackPluginOptions) { | |
return { | |
tagName: 'link', | |
selfClosingTag: !!htmlWebpackPluginOptions.xhtml, | |
attributes: { | |
rel: resourceHintType, | |
href: url | |
} | |
}; | |
} | |
/** | |
* The as attribute's value must be a valid request destination. | |
* If the provided value is omitted, the value is initialized to the empty string. | |
* | |
* @see https://w3c.github.io/preload/#link-element-interface-extensions | |
* @param {[type]} tag [description] | |
*/ | |
function addPreloadType (tag) { | |
const ext = path.extname(tag.attributes.href); | |
if (preloadDirective[ext]) { | |
tag.attributes.as = preloadDirective[ext]; | |
} | |
return tag; | |
} | |
module.exports = ResourceHintWebpackPlugin; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-env jasmine */ | |
const fs = require('fs'); | |
const path = require('path'); | |
const MemoryFileSystem = require('memory-fs'); | |
const webpack = require('webpack'); | |
const HtmlWebpackPlugin = require('html-webpack-plugin'); | |
const HtmlResourceHintPlugin = require('../'); | |
const OUTPUT_DIR = path.join(__dirname, '../dist'); | |
describe('HtmlResourceHintPlugin', () => { | |
it('adds prefetch tags by default', (done) => { | |
const expected = fs.readFileSync(path.resolve(__dirname, 'fixtures/expected.html')).toString(); | |
const compiler = webpack({ | |
entry: { | |
main: path.join(__dirname, 'fixtures', 'entry.js') | |
}, | |
output: { | |
path: OUTPUT_DIR, | |
filename: '[name].js' | |
}, | |
plugins: [ | |
new HtmlWebpackPlugin(), | |
new HtmlResourceHintPlugin() | |
] | |
}, (err, result) => { | |
expect(err).toBeFalsy(); | |
expect(JSON.stringify(result.compilation.errors)).toBe('[]'); | |
const html = result.compilation.assets['index.html'].source(); | |
expect(html).toBe(expected); | |
done(); | |
}); | |
compiler.outputFileSystem = new MemoryFileSystem(); | |
}); | |
}); | |
describe('HtmlResourceHintPlugin', () => { | |
it('adds prefetch tags', (done) => { | |
const expected = fs.readFileSync(path.resolve(__dirname, 'fixtures/expected.html')).toString(); | |
const compiler = webpack({ | |
entry: { | |
main: path.join(__dirname, 'fixtures', 'entry.js') | |
}, | |
output: { | |
path: OUTPUT_DIR, | |
filename: '[name].js' | |
}, | |
plugins: [ | |
new HtmlWebpackPlugin({ | |
prefetch: '*.js', | |
preload: '*.js' | |
}), | |
new HtmlResourceHintPlugin() | |
] | |
}, (err, result) => { | |
expect(err).toBeFalsy(); | |
expect(JSON.stringify(result.compilation.errors)).toBe('[]'); | |
const html = result.compilation.assets['index.html'].source(); | |
expect(html).toBe(expected); | |
done(); | |
}); | |
compiler.outputFileSystem = new MemoryFileSystem(); | |
}); | |
}); | |
describe('HtmlResourceHintPlugin', () => { | |
it('adds no file which do not match the filter', (done) => { | |
const compiler = webpack({ | |
entry: { | |
main: path.join(__dirname, 'fixtures', 'entry.js') | |
}, | |
output: { | |
path: OUTPUT_DIR, | |
filename: '[name].js' | |
}, | |
plugins: [ | |
new HtmlWebpackPlugin({ | |
prefetch: '*.json', | |
preload: false | |
}), | |
new HtmlResourceHintPlugin() | |
] | |
}, (err, result) => { | |
expect(err).toBeFalsy(); | |
expect(JSON.stringify(result.compilation.errors)).toBe('[]'); | |
const html = result.compilation.assets['index.html'].source(); | |
expect(html.indexOf('rel="prefetch"') === -1).toBe(true); | |
expect(html.indexOf('rel="preload"') === -1).toBe(true); | |
done(); | |
}); | |
compiler.outputFileSystem = new MemoryFileSystem(); | |
}); | |
}); | |
describe('HtmlResourceHintPlugin', () => { | |
it('allows to add fixed prefetch url', (done) => { | |
const compiler = webpack({ | |
entry: { | |
main: path.join(__dirname, 'fixtures', 'entry.js') | |
}, | |
output: { | |
path: OUTPUT_DIR, | |
filename: '[name].js' | |
}, | |
plugins: [ | |
new HtmlWebpackPlugin({ | |
prefetch: ['demo.json'] | |
}), | |
new HtmlResourceHintPlugin() | |
] | |
}, (err, result) => { | |
expect(err).toBeFalsy(); | |
expect(JSON.stringify(result.compilation.errors)).toBe('[]'); | |
const html = result.compilation.assets['index.html'].source(); | |
expect(!!html.indexOf('<link rel="prefetch" href="demo.json">')).toBe(true); | |
done(); | |
}); | |
compiler.outputFileSystem = new MemoryFileSystem(); | |
}); | |
}); | |
describe('HtmlResourceHintPlugin', () => { | |
it('allows to add fixed preload url', (done) => { | |
const compiler = webpack({ | |
entry: { | |
main: path.join(__dirname, 'fixtures', 'entry.js') | |
}, | |
output: { | |
path: OUTPUT_DIR, | |
filename: '[name].js' | |
}, | |
plugins: [ | |
new HtmlWebpackPlugin({ | |
preload: ['*.js', 'demo.json'] | |
}), | |
new HtmlResourceHintPlugin() | |
] | |
}, (err, result) => { | |
expect(err).toBeFalsy(); | |
expect(JSON.stringify(result.compilation.errors)).toBe('[]'); | |
const html = result.compilation.assets['index.html'].source(); | |
expect(!!html.indexOf('<link rel="preload" href="demo.json">')).toBe(true); | |
done(); | |
}); | |
compiler.outputFileSystem = new MemoryFileSystem(); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment