Fetch Inject
Dynamically inline assets into the DOM with support for asynchronous dependency management.
Purpose
Improve website performance and UX by fetching external assets and inlining them into the DOM programmatically. Get a Promise in return.
Background
Read about why I created this on Hack Cabin.
Installing
Fetch Inject is available for testing purposes via jsDelivr, and for production via NPM and Bower.
For Testing
Add the following to your document head
and see the Use Cases to get a feel for what it can do:
<script src="https://cdn.jsdelivr.net/fetch-inject/latest/fetch-inject.min.js"></script>
For Production
Grab the library from NPM with npm i fetch-inject
or Bower with bower install fetch-inject
. Recommended placement shown here:
<head>
<meta charset="utf-8">
<script async defer "/js/async/script.js"></script>
<script>
</script>
</head>
Usage
- Call
fetchInject
with an array of URLs. - Optionally, handle the returned
Promise
.
Use Cases
Loading Utility Scripts
Problem:
You want to prototype some code using the browser as a REPL.
Solution:
Skip the emulators and use the real deal:
fetchInject([
'https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js'
]).then(() => {
console.log(`Successfully loaded Lodash ${_.VERSION}`)
})
Loading CSS Asynchronously
Problem:
PageSpeed Insights dings you for loading unnecessary styles on initial render.
Solution:
Inline your critical path CSS and load non-critical styles asynchronously:
<style>
*{box-sizing:border-box;text-rendering:geometricPrecision}
</style>
<script>
fetchInject([
'/css/non-critical.css',
'https://cdn.jsdelivr.net/fontawesome/4.7.0/css/font-awesome.min.css'
])
</script>
Preventing Script Blocking
Problem:
Remote assets can lead to jank or, worse yet, SPOF if not handled correctly.
Solution:
Asynchronously load remote scripts without blocking:
fetchInject([
'bower_components/jquery/dist/jquery.js',
'bower_components/what-input/dist/what-input.js',
'bower_components/foundation-sites/dist/js/foundation.js'
])
Loading Scripts Lazily
Problem:
You want to load a script in response to a event without optimistically preloading the asset.
Solution:
Create an event listener, respond to the event and then destroy the listener.
const el = document.querySelector('details summary')
el.onclick = (evt) => {
fetchInject([
'https://cdn.jsdelivr.net/smooth-scroll/10.2.1/smooth-scroll.min.js'
])
el.onclick = null
}
Responding to Asynchronous Scripts
Problem:
You need to perform a synchronous operation immediately after an asynchronous script is loaded.
Solution:
You could create a script
element and use the async
and onload
attributes. Or you could...
fetchInject([
'https://cdn.jsdelivr.net/momentjs/2.17.1/moment.min.js'
]).then(() => {
console.log(`Finish in less than ${moment().endOf('year').fromNow()}`)
})
Combining Resource Types
Problem:
You need to asynchronously download multiple related assets of different types.
Solution:
Specify multiple URLs of different types when calling:
fetchInject([
'https://cdn.jsdelivr.net/normalize/5.0.0/normalize.css',
'//cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js',
])
Ordering Dependent Scripts
Problem:
You have several scripts that depend on one another and you want to load them all asynchronously without causing race conditions.
Solution:
Call multiple times, forming a promise chain:
fetchInject([
'https://cdn.jsdelivr.net/jquery/3.1.1/jquery.slim.min.js',
'https://npmcdn.com/tether@1.2.4/dist/js/tether.min.js',
'https://cdn.jsdelivr.net/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css'
]).then(() => {
fetchInject([
'https://npmcdn.com/bootstrap@4.0.0-alpha.5/dist/js/bootstrap.min.js'
])
})
Loading and Handling Composite Libraries
Problem:
You want to use library composed of several resources and initialize it as soon as possible.
Solution:
This is precisely why fetchInject
was created:
const container = document.querySelector('.pswp')
const items = JSON.parse({{ .Params.gallery.main | jsonify }})
fetchInject([
'/css/photoswipe.css',
'/css/default-skin/default-skin.css',
'/js/photoswipe.min.js',
'/js/photoswipe-ui-default.min.js'
]).then(() => {
const gallery = new PhotoSwipe(container, PhotoSwipeUI_Default, items)
gallery.init()
})
Limitations
- Currently supports only
script
and style
elements. - Relative paths (e.g.
url(default-skin.png)
) may need to be adjusted.
Supported Browsers
All browsers with support for Fetch and Promises.
Development
- Clone the repo with
git clone https://github.com/jhabdas/fetch-inject
. - Install dev dependencies with
npm i
(brew install node
first on macOS). - Execute
npm run
for a listing of available commands.
Note: Build variants possible for various module types via the format
setting in rollup.config.js
. I intend to switch to ES6 modules as soon as reasonable browser support is realized.
Contributing
Please use Issues sparingly. I favor action over words. Send in a Pull Request if you feel strongly something needs to be changed. Otherwise, please create a fork and mind the licensing terms.
Related Libraries
License
MIT License 2017 © Josh Habdas