├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── demo ├── index.html └── index.ts ├── package.json ├── readme.md ├── rollup.config.ts ├── src ├── loaders │ ├── image-loader.ts │ └── media-loader.ts ├── loaderz.ts ├── logger.ts └── models │ ├── loading-data.ts │ ├── media-data.ts │ └── resource-type.ts ├── test ├── helpers │ ├── ImageMock.ts │ ├── MediaMock.ts │ └── setup-browser-env.js ├── image-loader.ts └── media-loader.ts ├── tools └── semantic-release-prepare.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 120 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* text eol=lf 2 | 3 | *.png binary 4 | *.jpg binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | demo/dist 5 | 6 | yarn-error.log 7 | package-lock.json 8 | 9 | .cache 10 | .DS_Store 11 | .rpt2_cache 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | test/ 3 | demo/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - lts/* 5 | 6 | cache: 7 | directories: 8 | - node_modules 9 | sudo: false 10 | 11 | script: 12 | - yarn test && yarn build 13 | 14 | notifications: 15 | email: 16 | on_success: never 17 | on_failure: never 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Cazade 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Loaderz dev-env 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/index.ts: -------------------------------------------------------------------------------- 1 | // Consuming the module in your project would look like: 2 | // import { Loader } from 'loaderz'; 3 | // tslint:disable:import-name 4 | import Loader, { Logger } from '../src/loaderz'; 5 | 6 | // A list of heavy images to load, it could be art-assets for your HTML5 game 7 | const images = [ 8 | 'https://images.unsplash.com/photo-1549360336-6a77ea5193eb', 9 | 'https://images.unsplash.com/photo-1549379458-e8f7034360a9', 10 | 'https://images.unsplash.com/photo-1548175850-b5a765959436', 11 | ]; 12 | 13 | // Some audio elements to spice-up your HTML5 game 14 | const audios = [ 15 | 'http://www.sample-videos.com/audio/mp3/crowd-cheering.mp3', 16 | 'http://www.sample-videos.com/audio/mp3/wave.mp3', 17 | ]; 18 | 19 | // Instance the loader, you can easily implement it anywhere in your project 20 | const loader = new Loader(); 21 | 22 | // Additionnal step, instanciate the Logger (this is not required for a normal 23 | // usage) 24 | const logger = new Logger(); 25 | 26 | // Queue all our different resources (we can chain since) 27 | loader 28 | .queue('image', images) 29 | .queue('audio', audios); 30 | 31 | // Start loading the resources and have a full control of the loading state 32 | // using a promise and return a response with all elements loaded 33 | loader.start() 34 | .then(response => logger.log('All urls have been loaded, do whatever you want here:', response)); 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loaderz", 3 | "version": "1.2.0", 4 | "description": "A very easy-to-use asset-loader using promises. Support images, audios and videos.", 5 | "main": "dist/loaderz.umd.js", 6 | "module": "dist/loaderz.es5.js", 7 | "typings": "dist/types/loaderz.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/TotomInc/loaderz.git" 11 | }, 12 | "scripts": { 13 | "prebuild": "rimraf dist", 14 | "build": "tsc --module commonjs && rollup -c rollup.config.ts", 15 | "demo": "rm -rf demo/dist && parcel demo/index.html -p 8080 -d demo/dist", 16 | "demo:build": "rm -rf demo/dist && parcel build demo/index.html -d demo/dist --public-url ./", 17 | "lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts'", 18 | "test": "NODE_ENV=test ava --verbose -s", 19 | "test:watch": "NODE_ENV=test ava --verbose --watch -s", 20 | "semantic-release": "semantic-release", 21 | "semantic-release-prepare": "ts-node tools/semantic-release-prepare" 22 | }, 23 | "author": "TotomInc ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/TotomInc/loaderz/issues" 27 | }, 28 | "keywords": [ 29 | "typescript", 30 | "promise", 31 | "asset-loader", 32 | "preloader", 33 | "game-utility" 34 | ], 35 | "homepage": "https://github.com/TotomInc/loaderz#readme", 36 | "devDependencies": { 37 | "@types/node": "^11.9.0", 38 | "ava": "^1.2.1", 39 | "browser-env": "^3.2.5", 40 | "colors": "^1.3.3", 41 | "husky": "^1.3.1", 42 | "lodash.camelcase": "^4.3.0", 43 | "parcel-bundler": "^1.11.0", 44 | "rimraf": "^2.6.3", 45 | "rollup": "^1.1.2", 46 | "rollup-plugin-commonjs": "^9.2.0", 47 | "rollup-plugin-json": "^3.1.0", 48 | "rollup-plugin-node-resolve": "^4.0.0", 49 | "rollup-plugin-sourcemaps": "^0.4.2", 50 | "rollup-plugin-typescript2": "^0.19.2", 51 | "semantic-release": "^15.13.3", 52 | "ts-node": "^8.0.2", 53 | "tslint": "^5.12.1", 54 | "tslint-config-airbnb": "^5.11.1", 55 | "typescript": "^3.3.3" 56 | }, 57 | "files": [ 58 | "dist" 59 | ], 60 | "ava": { 61 | "compileEnhancements": false, 62 | "require": [ 63 | "ts-node/register", 64 | "./test/helpers/setup-browser-env.js" 65 | ], 66 | "extensions": [ 67 | "ts" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![Build Status](https://travis-ci.org/TotomInc/loaderz.svg?branch=master)](https://travis-ci.org/TotomInc/loaderz) [![license](https://img.shields.io/david/dev/totominc/loaderz.svg)]() [![license](https://img.shields.io/npm/v/loaderz.svg)]() [![license](https://img.shields.io/github/license/mashape/apistatus.svg)]() 6 | 7 | > A very easy-to-use asset-loader using promises. Supports images, audio and video. Fully documented for a perfect usage in your TypeScript projects. 8 | 9 | ## Installation 10 | 11 | Install using `yarn` or `npm`: 12 | 13 | - `yarn add loaderz` 14 | - `npm install loaderz --save` 15 | 16 | ## Usage 17 | 18 | ```typescript 19 | // Default export of Loaderz is the Loader. 20 | import Loader from 'loaderz'; 21 | 22 | // A list of heavy images to load, it could be art-assets for your HTML5 game 23 | const images = [ 24 | 'https://images.unsplash.com/photo-1549360336-6a77ea5193eb', 25 | 'https://images.unsplash.com/photo-1549379458-e8f7034360a9', 26 | 'https://images.unsplash.com/photo-1548175850-b5a765959436', 27 | ]; 28 | 29 | // Some audio elements to spice-up your HTML5 game 30 | const audios = [ 31 | 'http://www.sample-videos.com/audio/mp3/crowd-cheering.mp3', 32 | 'http://www.sample-videos.com/audio/mp3/wave.mp3', 33 | ]; 34 | 35 | // Instanciate the loader, you can easily implement it anywhere in your project 36 | const loader = new Loader(); 37 | 38 | // Queue all our different resources (we can chain since queue returns the 39 | // instance of loader) 40 | loader 41 | .queue('image', images) 42 | .queue('audio', audios); 43 | 44 | // Start loading the resources and have a full control of the global loading 45 | // state using a promise and return a response with all elements loaded 46 | loader.start() 47 | .then(response => console.log('All urls have been loaded, do whatever you want here:', response)); 48 | ``` 49 | 50 | ## Docs 51 | 52 | - `Loader#queue(type: string, src: string | string[])`: accepts 3 different types of medias (audio, image, video). 53 | - `Loader#start()`: used to load all the queued resources. Returns a global promise of the resources loading. 54 | 55 | - `Loader#queuedImages`: an array of URLs of images queued to load. 56 | - `Loader#queuedMedias`: an array of `MediaData` elements queued to load. 57 | 58 | ## Contribute 59 | 60 | All the code is written in TypeScript. Feel free to contribute by creating issues, PRs or suggesting new features: 61 | 62 | 1. Fork and clone the repo: `git@github.com:username/loaderz.git` 63 | 2. Install all dev-deps: `yarn install` or `npm install` 64 | 3. Run the demo: `yarn demo` (`localhost:8080`) 65 | 4. Edit some files 66 | 5. Run tests: `yarn test` or `npm test` 67 | - (optional) run `yarn lint` or `npm run lint` to automatically lint the files 68 | 6. Commit and push your edits on a separate branch 69 | 7. Create a PR which points on the `develop` branch 70 | 71 | ## License 72 | 73 | Under MIT license, view the license file for more informations. 74 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import rollupPluginNodeResolve from 'rollup-plugin-node-resolve'; 2 | import rollupPluginCommonjs from 'rollup-plugin-commonjs'; 3 | import rollupPluginSourcemaps from 'rollup-plugin-sourcemaps'; 4 | import lodashCamelcase from 'lodash.camelcase'; 5 | import rollupPluginTypescript2 from 'rollup-plugin-typescript2'; 6 | import rollupPluginJson from 'rollup-plugin-json'; 7 | 8 | const pkg = require('./package.json'); 9 | 10 | const libraryName = 'loaderz'; 11 | 12 | export default { 13 | input: `src/${libraryName}.ts`, 14 | output: [ 15 | { file: pkg.main, name: lodashCamelcase(libraryName), format: 'umd', sourcemap: true }, 16 | { file: pkg.module, format: 'es', sourcemap: true }, 17 | ], 18 | 19 | watch: { 20 | include: 'src/**', 21 | }, 22 | 23 | plugins: [ 24 | // Allow json resolution 25 | rollupPluginJson(), 26 | 27 | // Compile TypeScript files 28 | rollupPluginTypescript2({ 29 | useTsconfigDeclarationDir: true, 30 | // By default we use `commonjs` in the `tsconfig`, we must use `es2015` 31 | // or `esnext` when bundling the module 32 | tsconfigOverride: { 33 | compilerOptions: { 34 | module: 'es2015', 35 | }, 36 | }, 37 | }), 38 | 39 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 40 | rollupPluginCommonjs(), 41 | 42 | // Allow node_modules resolution, so you can use 'external' to control 43 | // which external modules to include in the bundle 44 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 45 | rollupPluginNodeResolve(), 46 | 47 | // Resolve source maps to the original source 48 | rollupPluginSourcemaps(), 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /src/loaders/image-loader.ts: -------------------------------------------------------------------------------- 1 | import { LoadingData } from '../models/loading-data'; 2 | 3 | /** 4 | * The `ImageLoader` class make sure to pre-load an image URL by using the 5 | * `HTMLImageElement`. It creates a local `new Image()` with the specified URL 6 | * as a `src` and ensure it is entirely loaded or failed by using 7 | * `image.onload()` and `image.onerror()` events. 8 | */ 9 | export class ImageLoader { 10 | /** 11 | * List of URLs to load when calling the `start()` function. 12 | */ 13 | public urls: string[] = []; 14 | 15 | /** 16 | * Add URLs to load. Return an array of queued URLs to load. 17 | * 18 | * @param newURLs an array of URLs to add for the loading sequence 19 | * @returns an array of already queued URLs for the loading sequence 20 | */ 21 | public queue(newURLs: string[]) { 22 | newURLs.forEach(url => this.urls.push(url)); 23 | 24 | return this.urls; 25 | } 26 | 27 | /** 28 | * Start the loading of all URls registered, create an array of promises 29 | * generated by the `promise` function. Return a `Promise.all()` of all 30 | * image promises. 31 | * 32 | * @returns a `Promise.all()` of all image-promises 33 | */ 34 | public start() { 35 | const promises = this.urls.map(url => this.promise(url)); 36 | 37 | return Promise.all(promises); 38 | } 39 | 40 | /** 41 | * Create a dummy `HTMLImageElement` with the URL as the src attribute inside 42 | * a promise, that is resolved with the `image.onload` event or rejected with 43 | * the `image.onerror` event. 44 | * 45 | * @param url image-url to load 46 | * @returns the image-promise generated 47 | */ 48 | private promise(url: string) { 49 | return new Promise((resolve, reject) => { 50 | const image = new Image(); 51 | 52 | image.onload = () => resolve({ url, loaded: true, type: 'image' }); 53 | image.onerror = () => reject({ url, loaded: false, type: 'image' }); 54 | image.src = url; 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/loaders/media-loader.ts: -------------------------------------------------------------------------------- 1 | import { LoadingData } from '../models/loading-data'; 2 | import { MediaData } from '../models/media-data'; 3 | 4 | /** 5 | * The `MediaLoader` class make sure to pre-load a media (video/audio) URL by 6 | * using the `HTMLAudioElement` or `HTMLVideoElement`. It creates a local 7 | * `new Audio()` or `new Video()` with the specified URL as a `src` and ensure 8 | * it is entirely loaded or failed by using `element.oncanplaythrough()` and 9 | * `element.onerror()` events. 10 | */ 11 | export class MediaLoader { 12 | /** 13 | * Array of `MediaData` to load when calling the `start()` function. 14 | */ 15 | public medias: MediaData[] = []; 16 | 17 | /** 18 | * Add URLs to load. Return an array of queued URLs to load. 19 | * 20 | * @param newURLs an array of URLs to add for the loading sequence 21 | * @returns an array of already queued URLs for the loading sequence 22 | */ 23 | public queue(media: MediaData[]): void { 24 | media.forEach(media => this.medias.push(media)); 25 | } 26 | 27 | /** 28 | * Start the loading of all URls registered, create an array of promises 29 | * generated by the `promise` function. Return a `Promise.all()` of all 30 | * media promises. 31 | * 32 | * @returns a `Promise.all()` of all media-promises 33 | */ 34 | public start() { 35 | const promises = this.medias.map(media => this.promise(media)); 36 | 37 | return Promise.all(promises); 38 | } 39 | 40 | /** 41 | * Create dummy `HTMLAudioElement` or `HTMLVideoElement` with the URL as the 42 | * src attribute inside a promise, that is resolved with the 43 | * `element.oncanplaythrough` event or rejected with the `element.onerror` 44 | * event. 45 | * 46 | * @param media media-data containing its type and the URL 47 | * @returns the media-promise generated 48 | */ 49 | private promise(media: MediaData) { 50 | return new Promise((resolve, reject) => { 51 | const element = (media.type === 'audio') 52 | ? new Audio() 53 | : document.createElement('video'); 54 | 55 | element.oncanplaythrough = () => resolve({ loaded: true, url: media.url, type: media.type }); 56 | element.onerror = () => reject({ loaded: false, url: media.url, type: media.type }); 57 | element.src = media.url; 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/loaderz.ts: -------------------------------------------------------------------------------- 1 | import { LoadingData } from './models/loading-data'; 2 | import { ResourceType } from './models/resource-type'; 3 | import { MediaData } from './models/media-data'; 4 | import { ImageLoader } from './loaders/image-loader'; 5 | import { MediaLoader } from './loaders/media-loader'; 6 | import { Logger } from './logger'; 7 | 8 | class Loader { 9 | private logger: Logger; 10 | private imageLoader: ImageLoader; 11 | private mediaLoader: MediaLoader; 12 | 13 | constructor() { 14 | this.logger = new Logger(); 15 | 16 | this.imageLoader = new ImageLoader(); 17 | this.mediaLoader = new MediaLoader(); 18 | } 19 | 20 | /** 21 | * Array of queued images URLs. 22 | */ 23 | get queuedImages() { 24 | return this.imageLoader.urls; 25 | } 26 | 27 | /** 28 | * Array of queued media-data. 29 | */ 30 | get queuedMedias() { 31 | return this.mediaLoader.medias; 32 | } 33 | 34 | /** 35 | * Add a new resource to the loading queue, automatically handle what type 36 | * of resource and how to load it. 37 | * 38 | * @param type type of the resource to load 39 | * @param src an url or array of urls to load 40 | * @returns returns the instance of itself 41 | */ 42 | public queue(type: ResourceType, src: string | string[]): this { 43 | const urls = !Array.isArray(src) ? [src] : src; 44 | 45 | if (type === 'image') { 46 | this.imageLoader.queue(urls); 47 | } else if (type === 'audio' || type === 'video') { 48 | const medias: MediaData[] = []; 49 | 50 | urls.forEach(url => medias.push({ type, url })); 51 | 52 | this.mediaLoader.queue(medias); 53 | } 54 | 55 | return this; 56 | } 57 | 58 | /** 59 | * Start the loading sequence. Return an array of `LoadingData` to check the 60 | * status of loaded resources. 61 | */ 62 | public start() { 63 | const allResources: LoadingData[] = []; 64 | 65 | return this.imageLoader.start() 66 | .then((res) => { 67 | const imagesNotLoaded = res.filter(status => !status.loaded); 68 | 69 | res.forEach(element => allResources.push(element)); 70 | 71 | if (imagesNotLoaded.length > 0) { 72 | this.logger.warn('some image(s) have failed to load:', imagesNotLoaded); 73 | } 74 | 75 | return this.mediaLoader.start(); 76 | }) 77 | .then((res) => { 78 | const mediasNotLoaded = res.filter(status => !status.loaded); 79 | 80 | res.forEach(element => allResources.push(element)); 81 | 82 | if (mediasNotLoaded.length > 0) { 83 | this.logger.warn('some media(s) have failed to load:', mediasNotLoaded); 84 | } 85 | 86 | return allResources; 87 | }); 88 | } 89 | } 90 | 91 | export { Logger }; 92 | export default Loader; 93 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Alternative to the default `console.log()` but with extra styling to 3 | * differentiate logs from loaderz and something else. This is used internally 4 | * by Loaderz, but you can also use it by importing it from Loaderz. 5 | */ 6 | export class Logger { 7 | private prefix: string; 8 | 9 | constructor(prefix: string = 'loaderz') { 10 | this.prefix = prefix; 11 | } 12 | 13 | /** 14 | * Log something in the console like you would do with `console.log()`, but 15 | * with extra styling. 16 | * 17 | * @param messages messages to send (includes variables) 18 | */ 19 | public log(...messages: any[]) { 20 | const args: any[] = [].slice.call(messages); 21 | 22 | args.unshift( 23 | `%c[${this.prefix}]%c`, 24 | 'color: #57AE5B; font-weight: bold;', 25 | 'color: #05400A; font-weight: normal;', 26 | ); 27 | 28 | console.log(...args); 29 | } 30 | 31 | /** 32 | * Log something in the console like you would do with `console.warn()`, but 33 | * with extra styling, with the warn background and a siren emoji. 34 | * 35 | * @param messages messages to send (includes variables) 36 | */ 37 | public warn(...messages: any[]) { 38 | const args: any[] = [].slice.call(messages); 39 | 40 | args.unshift( 41 | `%c[${this.prefix}]%c 🚨`, 42 | 'color: #F6B93B; font-weight: bold;', 43 | 'color: #05400A; font-weight: normal;', 44 | ); 45 | 46 | console.log(...args); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/models/loading-data.ts: -------------------------------------------------------------------------------- 1 | export interface LoadingData { 2 | loaded: boolean; 3 | url: string; 4 | type: 'audio' | 'image' | 'video'; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/media-data.ts: -------------------------------------------------------------------------------- 1 | export interface MediaData { 2 | url: string; 3 | type: 'audio' | 'video'; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/resource-type.ts: -------------------------------------------------------------------------------- 1 | export type ResourceType = 'image' | 'audio' | 'video'; 2 | -------------------------------------------------------------------------------- /test/helpers/ImageMock.ts: -------------------------------------------------------------------------------- 1 | export class ImageMock { 2 | public src: string = ''; 3 | 4 | // tslint:disable:variable-name 5 | private _onload: Function; 6 | 7 | // tslint:disable:variable-name 8 | private _onerror: Function; 9 | 10 | private fakeTime = 2000; 11 | 12 | constructor() { 13 | this._onload = () => {}; 14 | this._onerror = () => {}; 15 | } 16 | 17 | /** 18 | * When setting the `Image.onload`, store the handler into an internal 19 | * variable and instantly call the handler with a timeout to fake the 20 | * *fetching time*, so the promise won't be resolved instantly. 21 | */ 22 | set onload(handler: Function) { 23 | this._onload = handler; 24 | 25 | setTimeout(() => handler(), this.fakeTime); 26 | } 27 | 28 | set onerror(handler: Function) { 29 | this._onerror = handler; 30 | } 31 | 32 | get onerror() { 33 | return this._onerror(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/helpers/MediaMock.ts: -------------------------------------------------------------------------------- 1 | export class MediaMock { 2 | public src: string = ''; 3 | 4 | // tslint:disable:variable-name 5 | private _oncanplaythrough: Function; 6 | 7 | // tslint:disable:variable-name 8 | private _onerror: Function; 9 | 10 | private fakeTime = 2000; 11 | 12 | constructor() { 13 | this._oncanplaythrough = () => {}; 14 | this._onerror = () => {}; 15 | } 16 | 17 | set oncanplaythrough(handler: Function) { 18 | this._oncanplaythrough = handler; 19 | 20 | setTimeout(() => handler(), this.fakeTime); 21 | } 22 | 23 | set onerror(handler: Function) { 24 | this._onerror = handler; 25 | } 26 | 27 | get onerror() { 28 | return this._onerror(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/helpers/setup-browser-env.js: -------------------------------------------------------------------------------- 1 | import browserEnv from 'browser-env'; 2 | 3 | /** 4 | * browser-env will add all global browser variables to the Node.js global 5 | * scope, creating a full browser environment. We can pass an array of global 6 | * browser globals if we know exactly what we need as a parameter of 7 | * `browserEnv`. 8 | */ 9 | browserEnv(['window', 'document'], { 10 | resources: 'usable', 11 | }); 12 | -------------------------------------------------------------------------------- /test/image-loader.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:import-name 2 | import test from 'ava'; 3 | 4 | import { ImageMock } from './helpers/ImageMock'; 5 | import Loader from '../src/loaderz'; 6 | 7 | /** 8 | * Before running all `ImageLoader` tests, we need to register the `ImageMock` 9 | * class globally, so it will take over the default `HTMLImageElement` used by 10 | * the `ImageLoader`. By doing this, we have a full control over the default 11 | * `Image` functions such as `onload` and `onerror`. 12 | */ 13 | test.before((t) => { 14 | // @ts-ignore 15 | global.Image = ImageMock; 16 | }); 17 | 18 | test('add an image URL to the image-loader array', (t) => { 19 | const loader = new Loader(); 20 | 21 | loader.queue('image', 'https://example.com/image.jpg'); 22 | 23 | t.is(loader.queuedImages.length, 1); 24 | }); 25 | 26 | test('add an array of images URLs to the image-loader array', (t) => { 27 | const loader = new Loader(); 28 | const urls = [ 29 | 'https://example.com/image.jpg', 30 | 'https://example.com/image.png', 31 | 'https://example.com/image.gif', 32 | ]; 33 | 34 | loader.queue('image', urls); 35 | 36 | t.is(loader.queuedImages.length, urls.length); 37 | t.deepEqual(loader.queuedImages, urls); 38 | }); 39 | 40 | test('successfully load an image', async (t) => { 41 | const loader = new Loader(); 42 | 43 | loader.queue('image', 'https://images.unsplash.com/photo-1549403610-177341eb0f67'); 44 | 45 | const loaderResponse = await loader.start(); 46 | const allLoaded = loaderResponse.every(element => element.loaded); 47 | 48 | t.true(allLoaded); 49 | t.is(loaderResponse.length, 1); 50 | }); 51 | 52 | test('successfully load an array of images', async (t) => { 53 | const loader = new Loader(); 54 | const urls = [ 55 | 'https://example.com/image.jpg', 56 | 'https://example.com/image.png', 57 | 'https://example.com/image.gif', 58 | ]; 59 | 60 | loader.queue('image', urls); 61 | 62 | const loaderResponse = await loader.start(); 63 | const allLoaded = loaderResponse.every(element => element.loaded); 64 | 65 | t.true(allLoaded); 66 | t.is(loaderResponse.length, urls.length); 67 | }); 68 | -------------------------------------------------------------------------------- /test/media-loader.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:import-name 2 | import test from 'ava'; 3 | 4 | import { MediaMock } from './helpers/MediaMock'; 5 | import Loader from '../src/loaderz'; 6 | 7 | /** 8 | * Before running all `MediaLoader` tests, we need to register the `MediaMock` 9 | * class globally, so it will take over the default `HTMLAudioElement` and 10 | * `HTMLVideoElement` used by the `MediaLoader`. By doing this, we have a full 11 | * control over the defaults `Audio` and `Media` classes functions such as 12 | * `oncanplaythrough` and `onerror`. 13 | */ 14 | test.before((t) => { 15 | // @ts-ignore 16 | global.Audio = MediaMock; 17 | }); 18 | 19 | test('add an audio URL to the media-loader array', (t) => { 20 | const loader = new Loader(); 21 | 22 | loader.queue('audio', 'https://example.com/audio.mp3'); 23 | 24 | t.is(loader.queuedMedias.length, 1); 25 | }); 26 | 27 | test('add an array of audios URLs to the media-loader array', (t) => { 28 | const loader = new Loader(); 29 | const urls = [ 30 | 'https://example.com/audio.mp3', 31 | 'https://example.com/audio.wav', 32 | 'https://example.com/audio.flac', 33 | ]; 34 | 35 | loader.queue('audio', urls); 36 | 37 | const queuedMediasAreAudios = loader.queuedMedias.every(media => media.type === 'audio'); 38 | 39 | t.is(loader.queuedMedias.length, urls.length); 40 | t.true(queuedMediasAreAudios); 41 | }); 42 | 43 | test('successfully load an audio', async (t) => { 44 | const loader = new Loader(); 45 | 46 | loader.queue('audio', 'https://example.com/audio.mp3'); 47 | 48 | const loaderResponse = await loader.start(); 49 | const allLoaded = loaderResponse.every(element => element.loaded); 50 | 51 | t.true(allLoaded); 52 | t.is(loaderResponse.length, 1); 53 | }); 54 | 55 | test('successfully load an array of audios URLs', async (t) => { 56 | const loader = new Loader(); 57 | const urls = [ 58 | 'https://example.com/audio.mp3', 59 | 'https://example.com/audio.wav', 60 | 'https://example.com/audio.flac', 61 | ]; 62 | 63 | loader.queue('audio', urls); 64 | 65 | const loaderResponse = await loader.start(); 66 | const allLoaded = loaderResponse.every(element => element.loaded); 67 | 68 | t.true(allLoaded); 69 | t.is(loaderResponse.length, urls.length); 70 | }); 71 | -------------------------------------------------------------------------------- /tools/semantic-release-prepare.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { fork } = require('child_process'); 3 | const colors = require('colors'); 4 | 5 | const { readFileSync, writeFileSync } = require('fs'); 6 | const pkg = JSON.parse( 7 | readFileSync(path.resolve(__dirname, '..', 'package.json')), 8 | ); 9 | 10 | pkg.scripts.prepush = 'npm run test:prod && npm run build'; 11 | pkg.scripts.commitmsg = 'commitlint -E HUSKY_GIT_PARAMS'; 12 | 13 | writeFileSync( 14 | path.resolve(__dirname, '..', 'package.json'), 15 | JSON.stringify(pkg, null, 2), 16 | ); 17 | 18 | // Call husky to set up the hooks 19 | fork(path.resolve(__dirname, '..', 'node_modules', 'husky', 'lib', 'installer', 'bin'), ['install']); 20 | 21 | console.log(); 22 | console.log(colors.green('Done!!')); 23 | console.log(); 24 | 25 | if (pkg.repository.url.trim()) { 26 | console.log(colors.cyan('Now run:')); 27 | console.log(colors.cyan(' npm install -g semantic-release-cli')); 28 | console.log(colors.cyan(' semantic-release-cli setup')); 29 | console.log(); 30 | console.log( 31 | colors.cyan('Important! Answer NO to "Generate travis.yml" question'), 32 | ); 33 | console.log(); 34 | console.log( 35 | colors.gray( 36 | 'Note: Make sure "repository.url" in your package.json is correct before', 37 | ), 38 | ); 39 | } else { 40 | console.log( 41 | colors.red( 42 | 'First you need to set the "repository.url" property in package.json', 43 | ), 44 | ); 45 | console.log(colors.cyan('Then run:')); 46 | console.log(colors.cyan(' npm install -g semantic-release-cli')); 47 | console.log(colors.cyan(' semantic-release-cli setup')); 48 | console.log(); 49 | console.log( 50 | colors.cyan('Important! Answer NO to "Generate travis.yml" question'), 51 | ); 52 | } 53 | 54 | console.log(); 55 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "module": "commonjs", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "declarationDir": "dist/types", 14 | "outDir": "dist/lib", 15 | "typeRoots": ["node_modules/@types"] 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-airbnb", 3 | "rules": { 4 | "max-line-length": [false] 5 | } 6 | } 7 | --------------------------------------------------------------------------------