generated from foumart/JS.13kGames
-
Notifications
You must be signed in to change notification settings - Fork 1
/
gulpfile.js
286 lines (256 loc) · 10.5 KB
/
gulpfile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
const { src, dest, series } = require('gulp');
const gulp = require('gulp');
const minify = require('gulp-minify');
const concat = require('gulp-concat');
const htmlmin = require('gulp-htmlmin');
const replace = require('gulp-string-replace');
const htmlreplace = require('gulp-html-replace');
const cleanCSS = require('gulp-clean-css');
const browserSync = require('browser-sync').create();
const closureCompiler = require('google-closure-compiler').gulp();
const del = require('del');
const argv = require('yargs').argv;
const gulpif = require('gulp-if');
const imagemin = require('gulp-imagemin');
const zip = require('gulp-zip');
const advzip = require('gulp-advzip');
const package = require('./package.json');
const replaceOptions = { logs: { enabled: false } };
const timestamp = getDateString();
// data taken directly from package.json
const title = package.name;
const id_name = `${title.replace(/\s/g, '')}_${getDateString(true)}`;
const version = package.version;
// set the output directory
const dir = argv.dir || 'public';
// don't use versioned zip file - useful for fast testing.
const test = argv.test != undefined ? true : false;
// enable progressive web app - use a service worker, webmanifest and pwa initialization scripts. Adds 864 bytes.
const pwa = argv.pwa != undefined ? true : false;
// display service worker logs
const debug = argv.debug != undefined ? true : false;
// should html tags for mobile be included. Adds 45 bytes.
const mobile = argv.mobile != undefined || argv.all != undefined ? `
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" sizes="144x144" href="ico.png"/>` : false;
// should html tags for social media be included. Adds around 100 bytes, depending on description length.
const social = argv.social != undefined || argv.all != undefined ? `
<meta name="application-name" content="${title}"/>
<meta name="description" content="${package.description}"/>
<meta name="keywords" content="${package.keywords}"/>
<meta name="author" content="${package.author.name}"/>
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="${title}"/>
<meta name="twitter:description" content="${package.description}"/>
<meta name="twitter:image" content="ico.png"/>` : false;
// prepare a web icon to be used by html and pwa
// unfortunatelly we cannot use a svg icon: https://bugs.chromium.org/p/chromium/issues/detail?id=578122
function ico(callback) {
src(['src/ico.png'], { allowEmpty: true })
.pipe(imagemin([imagemin.optipng({optimizationLevel: 7})]))
.pipe(dest(dir + '/'))
.on('end', callback)
}
// prepare service worker script
function sw(callback) {
if (pwa) {
src(['resources/service_worker.js'], { allowEmpty: true })
.pipe(replace('var debug;', `var debug = ${debug ? 'true' : 'false'};`, replaceOptions))
.pipe(replace('{ID_NAME}', id_name, replaceOptions))
.pipe(replace('{VERSION}', version, replaceOptions))
.pipe(gulpif(!debug, replace('caches', 'window.caches', replaceOptions)))
.pipe(gulpif(!debug,
closureCompiler({
compilation_level: 'ADVANCED_OPTIMIZATIONS',
warning_level: 'QUIET',
language_in: 'ECMASCRIPT6',
language_out: 'ECMASCRIPT6'
})
))
.pipe(gulpif(!debug, replace('window.caches', 'caches', replaceOptions)))
.pipe(gulpif(!debug, replace('"use strict";', '', replaceOptions)))
.pipe(gulpif(!debug, minify({ noSource: true })))
.pipe(concat('sw.js'))
.pipe(dest(dir + '/'))
.on('end', callback)
} else {
callback();
}
}
// minify css
function css(callback) {
src('src/styles/*.css', { allowEmpty: true })
.pipe(cleanCSS())
.pipe(concat('temp.css'))
.pipe(dest(dir + '/tmp/'))
.on('end', callback)
}
// compile the pwa initialization script (if needed), as well as loader and game logic scripts
function app(callback) {
const scripts = [
'resources/loader.js',
'src/scripts/*'
];
if (pwa) {
scripts.unshift('resources/sw_init.js');
}
src(scripts, { allowEmpty: true })
.pipe(replace('let _debug;', `let _debug = ${debug ? 'true' : 'false'};`, replaceOptions))
.pipe(replace('service_worker', 'sw', replaceOptions))
.pipe(gulpif(!pwa, replace('// loader', 'window.addEventListener("load", init);', replaceOptions)))
.pipe(gulpif(!debug,
closureCompiler({
compilation_level: 'ADVANCED_OPTIMIZATIONS',
warning_level: 'QUIET',
language_in: 'ECMASCRIPT6',
language_out: 'ECMASCRIPT6'
})
))
.pipe(gulpif(!debug, minify({ noSource: true })))
.pipe(concat('app.js'))
.pipe(dest(dir + '/tmp/'))
.on('end', callback);
}
// prepare web manifest file
function mf(callback) {
if (pwa) {
src('resources/mf.webmanifest', { allowEmpty: true })
.pipe(replace('service_worker', 'sw', replaceOptions))
.pipe(replace('{TITLE}', title, replaceOptions))
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(dest(dir + '/'))
.on('end', callback);
} else {
callback();
}
}
// inline js and css into html and remove unnecessary stuff
function pack(callback) {
const fs = require('fs');
let css = fs.readFileSync(dir + '/tmp/temp.css', 'utf8');
let js = fs.readFileSync(dir + '/tmp/app.js', 'utf8');
let ontouchstartName;
let ontouchendName;
const elementIds = ['main'];
const variableNames = [];
if (!debug) {
// fix ontouchstart event bug
//let occurance = js.indexOf('ontouchstart');
//ontouchstartName = js.substr(occurance + 13, js.charAt(occurance + 15) == ',' || js.charAt(occurance + 15) == ':' ? 2 : 1);
//occurance = js.indexOf('ontouchend');
//ontouchendName = js.substr(occurance + 11, js.charAt(occurance + 13) == ',' || js.charAt(occurance + 13) == ')' ? 2 : 1);
// remove getElementById calls that are required by the compiler. The Id named html elements are directly available in js as globals.
/*const segmentedJs = js.split('=document.getElementById("');
variableNames.push(segmentedJs[0].substr(segmentedJs[0].lastIndexOf(' ') + 1));
segmentedJs[0] = segmentedJs[0].substring(13, segmentedJs[0].lastIndexOf(' ') - 5);
for (let i = 1; i < segmentedJs.length - 1; i++) {
variableNames.push(segmentedJs[i].split(',')[1]);
}
const varsToRename = [];
for (let i = 0; i < variableNames.length; i++) {
const varName = variableNames[i];
const renamedVar = varName.replace('$', '_');
if (varName != renamedVar) {
varsToRename.push([varName, renamedVar]);
variableNames[i] = renamedVar;
}
}
segmentedJs[segmentedJs.length-1] = segmentedJs[segmentedJs.length-1].substring(segmentedJs[segmentedJs.length-1].indexOf(';') + 1);
js = segmentedJs[0] + segmentedJs[segmentedJs.length-1];
for (let i = 0; i < varsToRename.length; i ++) {
js = js.split(varsToRename[i][0]).join(varsToRename[i][1]);
}*/
// replace css ids
/*for (let i = 0; i < elementIds.length; i++) {
const regex = new RegExp(elementIds[i], 'g');
css = css.replace(regex, variableNames[i]);
}*/
}
let stream = src('src/index.html', { allowEmpty: true });
/*if (!debug) for (let i = 0; i < elementIds.length; i++) {
stream = stream.pipe(replace(elementIds[i], variableNames[i], replaceOptions));
}*/
stream
.pipe(replace('{TITLE}', title, replaceOptions))
.pipe(gulpif(social != false && mobile != false, htmlreplace({'mobile': mobile, 'social': social})))
.pipe(gulpif(social === false && mobile != false, htmlreplace({'mobile': mobile, 'social': ''})))
.pipe(gulpif(social != false && mobile === false, htmlreplace({'mobile': '', 'social': social})))
.pipe(gulpif(social === false && mobile === false, htmlreplace({'mobile': '', 'social': ''})))
.pipe(gulpif(!pwa, replace('<link rel="manifest" href="mf.webmanifest">', '', replaceOptions)))
.pipe(htmlmin({ collapseWhitespace: true, removeComments: true }))
.pipe(replace('"', '', replaceOptions))
.pipe(replace('rep_css', '<style>' + css + '</style>', replaceOptions))
.pipe(replace('rep_js', '<script>' + js + '</script>', replaceOptions))
//.pipe(gulpif(!debug, replace(' ontouchstart', ` ontouchstart=${ontouchstartName}()`, replaceOptions)))
//.pipe(gulpif(!debug, replace(' ontouchend', ` ontouchend=${ontouchendName}()`, replaceOptions)))
.pipe(concat('index.html'))
.pipe(dest(dir + '/'))
.on('end', callback);
}
// delete the temporary folder generated during packaging
function clean() {
return del(dir + '/tmp/');
}
// package zip (exclude the Twemoji.ttf font if it's being used locally)
function archive(callback) {
if (debug) callback();
else src([dir + '/*', dir + '/*/*', '!'+ dir + '/*.ttf'], { allowEmpty: true })
.pipe(zip(test ? 'game.zip' : 'game_' + timestamp + '.zip'))
.pipe(advzip({ optimizationLevel: 4, iterations: 10 }))
.pipe(dest('zip/'))
.on('end', callback);
}
// output the zip filesize
function check(callback) {
if (debug) callback();
else {
var fs = require('fs');
const size = fs.statSync(test ? 'zip/game.zip' : 'zip/game_' + timestamp + '.zip').size;
const limit = 1024 * 13;
const left = limit - size;
const percent = Math.abs(Math.round((left / limit) * 10000) / 100);
console.log(` ${size} ${left} bytes ${left < 0 ? 'overhead' : 'remaining'} (${percent}%)`);
callback();
}
}
// watch for changes in the source folder
function watch(callback) {
browserSync.init({
server: './public',
ui: false,
port: 8080
});
gulp.watch('./src').on('change', () => {
exports.sync();
});
callback();
};
// reload the browser sync instance, or run a new server with live reload
function reload(callback) {
if (!browserSync.active) {
watch(callback);
} else {
browserSync.reload();
callback();
}
}
// helper function for timestamp and naming
function getDateString(shorter) {
const date = new Date();
const year = date.getFullYear();
const month = `${date.getMonth() + 1}`.padStart(2, '0');
const day =`${date.getDate()}`.padStart(2, '0');
if (shorter) return `${year}${month}${day}`;
const signiture =`${date.getHours()}`.padStart(2, '0')+`${date.getMinutes()}`.padStart(2, '0')+`${date.getSeconds()}`.padStart(2, '0');
return `${year}${month}${day}_${signiture}`;
}
// exports
exports.default = series(ico, sw, app, css, mf, pack, clean, archive, check, watch);
exports.pack = series(ico, sw, app, css, mf, pack, clean);
exports.sync = series(app, css, pack, clean, reload);
exports.zip = series(archive, check);
/*
Gulpfile by Noncho Savov
https://www.FoumartGames.com
*/