├── LICENSE ├── README.md ├── create └── myAwesomeProject ├── css └── main.css ├── favicon.png ├── index.html ├── js ├── aux.js └── main.js ├── manifest.json └── sw.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Victor Ribeiro 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create 2 | 3 | A simple bash script to create web apps that can be installed on devices as progressive web apps (pwa). 4 | 5 | ## About 6 | 7 | This is a very simple but very useful script to create a web app template. For Chrome to consider your web app a valid app, it needs to have a cache manifest, a web app manifest, a bigger than 144x144 pixel favicon and a registered service worker (read more about it [here](https://developers.google.com/web/tools/lighthouse/audits/install-prompt)). This script takes care of it for you. 8 | 9 | ## How to use 10 | 11 | Put it in your scrips folder. 12 | 13 | Give it execute permisson 14 | ``` 15 | chmod +x create 16 | ``` 17 | 18 | Run it with a project's name as argument 19 | ``` 20 | create myAwesomeProject 21 | ``` 22 | 23 | ## Files created 24 | 25 | [index.html](myAwesomeProject/index.html) 26 | [css/main.css](myAwesomeProject/css/main.css) 27 | [js/aux.js](myAwesomeProject/js/aux.js) 28 | [js/main.js](myAwesomeProject/js/main.js) 29 | [sw.js](myAwesomeProject/sw.js) 30 | [favicon.png](myAwesomeProject/favicon.png) 31 | [manifest.json](myAwesomeProject/manifest.json) 32 | -------------------------------------------------------------------------------- /create: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [[ $1 ]] || { echo "Usage: $0 "; exit; } 4 | 5 | dir="$1"; 6 | date=$(date -I); 7 | 8 | if [ ! -d $dir ]; 9 | then 10 | mkdir -p $dir/{js,css}; 11 | touch $dir/js/main.js; 12 | cat > "$dir/sw.js" << EOF 13 | const filesToCache = [ 14 | './', 15 | './index.html', 16 | './js/main.js', 17 | './css/main.css', 18 | './favicon.png', 19 | './manifest.json' 20 | ]; 21 | 22 | const staticCacheName = '$dir-v1'; 23 | 24 | self.addEventListener('install', event => { 25 | event.waitUntil( 26 | caches.open(staticCacheName) 27 | .then(cache => { 28 | return cache.addAll(urlsToPrefetch.map(function(filesToCache) { 29 | return new Request(filesToCache, { mode: 'no-cors' }); 30 | })); 31 | }) 32 | ); 33 | }); 34 | 35 | self.addEventListener('fetch', event => { 36 | event.respondWith( 37 | caches.match(event.request) 38 | .then(response => { 39 | if (response) { 40 | return response; 41 | } 42 | 43 | return fetch(event.request) 44 | 45 | .then(response => { 46 | return caches.open(staticCacheName).then(cache => { 47 | cache.put(event.request.url, response.clone()); 48 | return response; 49 | }); 50 | }); 51 | 52 | }).catch(error => {}) 53 | ); 54 | }); 55 | 56 | self.addEventListener('activate', event => { 57 | 58 | const cacheWhitelist = [staticCacheName]; 59 | 60 | event.waitUntil( 61 | caches.keys().then(cacheNames => { 62 | return Promise.all( 63 | cacheNames.map(cacheName => { 64 | if (cacheWhitelist.indexOf(cacheName) === -1) { 65 | return caches.delete(cacheName); 66 | } 67 | }) 68 | ); 69 | }) 70 | ); 71 | }); 72 | EOF 73 | cat > "$dir/js/aux.js" << EOF 74 | if('serviceWorker' in navigator) { 75 | navigator.serviceWorker 76 | .register('/$dir/sw.js', {scope: './'}) 77 | .then(response => response) 78 | .catch(reason => reason); 79 | } 80 | 81 | let deferredPrompt; 82 | const addBtn = document.createElement('button'); 83 | 84 | window.addEventListener('beforeinstallprompt', (e) => { 85 | e.preventDefault(); 86 | deferredPrompt = e; 87 | addBtn.style.display = 'block'; 88 | addBtn.addEventListener('click', (e) => { 89 | addBtn.style.display = 'none'; 90 | deferredPrompt.prompt(); 91 | deferredPrompt.userChoice.then((choiceResult) => { 92 | deferredPrompt = null; 93 | }); 94 | }); 95 | }); 96 | EOF 97 | cat > "$dir/css/main.css" << EOF 98 | html, body{ 99 | margin: 0; 100 | padding: 0; 101 | } 102 | canvas{ 103 | display: block; 104 | } 105 | EOF 106 | cat > "$dir/index.html" << EOF 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | $dir 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | EOF 134 | cat > "$dir/manifest.json" << EOF 135 | { 136 | "name": " ", 137 | "short_name": " ", 138 | "description": " ", 139 | "start_url": "index.html", 140 | "display": "standalone", 141 | "orientation": "portrait", 142 | "background_color": "#FFF", 143 | "theme_color": "#FFF", 144 | "icons": [ 145 | { 146 | "src": "favicon.png", 147 | "sizes": "256x256", 148 | "type": "image/png", 149 | "density": 1 150 | }, 151 | { 152 | "src": "favicon.png", 153 | "sizes": "512x512", 154 | "type": "image/png", 155 | "density": 1 156 | } 157 | ] 158 | } 159 | EOF 160 | echo "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAQAAAD2e2DtAAABu0lEQVR42u3SQREAAAzCsOHf9F6oIJXQS07Tx\ 161 | QIABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACA\ 162 | ABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAAgAASAABIAAEAACQAA\ 163 | IAAEgAASAABAAAgAACwAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAAgAASAA\ 164 | BIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABI\ 165 | AAEgAAQAAJAAAgAASAABIAAEAACQAAIAAAsAEAACAABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAAB\ 166 | AAAkAACAABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAAgAASAABIA\ 167 | AEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAKg9kK0BATSHu+YAAAAASUVORK5CYII=" | base64 -d > "$dir/favicon.png"; 168 | echo "Created $dir"; 169 | fi 170 | -------------------------------------------------------------------------------- /myAwesomeProject/css/main.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | canvas{ 6 | display: block; 7 | } 8 | -------------------------------------------------------------------------------- /myAwesomeProject/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorqribeiro/create/87f52a9ee66a4ef7accfb75ece93e3d44068956f/myAwesomeProject/favicon.png -------------------------------------------------------------------------------- /myAwesomeProject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | myAwesomeProject 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /myAwesomeProject/js/aux.js: -------------------------------------------------------------------------------- 1 | if('serviceWorker' in navigator) { 2 | navigator.serviceWorker 3 | .register('/myAwesomeProject/sw.js', {scope: './'}) 4 | .then(response => response) 5 | .catch(reason => reason); 6 | } 7 | 8 | let deferredPrompt; 9 | const addBtn = document.createElement('button'); 10 | 11 | window.addEventListener('beforeinstallprompt', (e) => { 12 | e.preventDefault(); 13 | deferredPrompt = e; 14 | addBtn.style.display = 'block'; 15 | addBtn.addEventListener('click', (e) => { 16 | addBtn.style.display = 'none'; 17 | deferredPrompt.prompt(); 18 | deferredPrompt.userChoice.then((choiceResult) => { 19 | deferredPrompt = null; 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /myAwesomeProject/js/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorqribeiro/create/87f52a9ee66a4ef7accfb75ece93e3d44068956f/myAwesomeProject/js/main.js -------------------------------------------------------------------------------- /myAwesomeProject/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": " ", 3 | "short_name": " ", 4 | "description": " ", 5 | "start_url": "index.html", 6 | "display": "standalone", 7 | "orientation": "portrait", 8 | "background_color": "#FFF", 9 | "theme_color": "#FFF", 10 | "icons": [ 11 | { 12 | "src": "favicon.png", 13 | "sizes": "256x256", 14 | "type": "image/png", 15 | "density": 1 16 | }, 17 | { 18 | "src": "favicon.png", 19 | "sizes": "512x512", 20 | "type": "image/png", 21 | "density": 1 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /myAwesomeProject/sw.js: -------------------------------------------------------------------------------- 1 | const filesToCache = [ 2 | './', 3 | './index.html', 4 | './js/main.js', 5 | './css/main.css', 6 | './favicon.png', 7 | './manifest.json' 8 | ]; 9 | 10 | const staticCacheName = 'myAwesomeProject-v1'; 11 | 12 | self.addEventListener('install', event => { 13 | event.waitUntil( 14 | caches.open(staticCacheName) 15 | .then(cache => { 16 | return cache.addAll(filesToCache); 17 | }) 18 | ); 19 | }); 20 | 21 | self.addEventListener('fetch', event => { 22 | event.respondWith( 23 | caches.match(event.request) 24 | .then(response => { 25 | if (response) { 26 | return response; 27 | } 28 | 29 | return fetch(event.request) 30 | 31 | .then(response => { 32 | return caches.open(staticCacheName).then(cache => { 33 | cache.put(event.request.url, response.clone()); 34 | return response; 35 | }); 36 | }); 37 | 38 | }).catch(error => {}) 39 | ); 40 | }); 41 | 42 | self.addEventListener('activate', event => { 43 | 44 | const cacheWhitelist = [staticCacheName]; 45 | 46 | event.waitUntil( 47 | caches.keys().then(cacheNames => { 48 | return Promise.all( 49 | cacheNames.map(cacheName => { 50 | if (cacheWhitelist.indexOf(cacheName) === -1) { 51 | return caches.delete(cacheName); 52 | } 53 | }) 54 | ); 55 | }) 56 | ); 57 | }); 58 | --------------------------------------------------------------------------------