-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgulpfile.js
153 lines (137 loc) · 5.97 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
const childProcess = require('child_process');
const fs = require('fs');
const { watch, task, series, parallel } = require('gulp');
const md5 = require('md5');
const rimraf = require('rimraf');
const argv = require('yargs').argv;
const OUT_DIR = './public/gen';
const DEFAULT_PROJECT = 'mk-time-2';
task('delete', (callback) => {
const rimraf = require('rimraf');
rimraf.sync(outDir);
callback();
});
const FIREBASE_PATH = './node_modules/firebase-tools/lib/bin/firebase.js';
function generateIndexHtml(mainJsFilePath, cssFilePath, manifestFilePath) {
// - maximum-scale=1.0 disables zoom in Chrome and Webkit.
// - user-scalable=yes reenables user driven zoom in Webkit but not Chrome.
// - The combination of those two enables user driven zoom but disables
// automated browser zooming of form controls.
return `<!DOCTYPE html><html><head><meta charset='utf-8'>
<script>
const ua = navigator.userAgent;
const isSafari = !ua.includes('AppleWebKit/537.36') && ua.includes('AppleWebKit/');
document.write(
\`<meta name="viewport" content="width=device-width,initial-scale=1.0\${isSafari ? ',maximum-scale=1.0,user-scalable=yes' : ''}">\`
);
</script>
<script src="https://apis.google.com/js/api.js"></script>
<script type=module src="${mainJsFilePath}"></script>
<link rel=stylesheet href="${cssFilePath}">
<link rel="manifest" href="${manifestFilePath}">
</head><body></body></html>`;
}
async function checksumFileAndReturnNewPath(fileName, extension) {
const path = `public/${fileName}${extension}`;
const checksum = md5(await fs.promises.readFile(path, 'utf8'));
const checksummedPath = `gen/${fileName}-${checksum}${extension}`;
await fs.promises.copyFile(path, `public/${checksummedPath}`);
return checksummedPath;
}
async function checksumMainJs() {
const bundleMain = OUT_DIR + '/main.js';
// TODO: Run esbuild from gulp instead of commandline so that we
// can get the bundle JS without writing it to disk just to read it out again.
// Reading the file out here takes 150ms. esbuild takes 300ms to produce and
// write out the file. So that should make it 2x faster.
const mainFileContents = await fs.promises.readFile(bundleMain, 'utf8');
const mainFileChecksum = md5(mainFileContents);
const checksummedMainPath = `gen/main-${mainFileChecksum}.js`;
const publicChecksummedMainPath = `public/${checksummedMainPath}`;
// Technically appending the sourceMappingURL would change the checksum, but
// we don't need to care as long as we're consistent.
await fs.promises.writeFile(
publicChecksummedMainPath,
`${mainFileContents}
//# sourceMappingURL=/${checksummedMainPath}.map`,
);
await fs.promises.rename(`${bundleMain}.map`, `${publicChecksummedMainPath}.map`);
return checksummedMainPath;
}
task('bundle-once', (cb) => {
// Delete the contents of the out directory instead of the whole directory.
// Deleting the whole directly confuses tsc watch and has it start an
// incremental compilation that never finishes.
rimraf.sync(`${OUT_DIR}/*`);
const minify = argv.noMinify ? '' : '--minify';
const esbuild = childProcess.exec(
`npx esbuild --bundle static/main.ts --bundle static/HeaderFocusPainter.ts ${minify} --outdir=${OUT_DIR} --target=esnext --sourcemap=external`,
async () => {
try {
const paths = await Promise.all([
checksumMainJs(),
checksumFileAndReturnNewPath('generic', '.css'),
checksumFileAndReturnNewPath('manifest', '.json'),
]);
const indexHtmlContents = generateIndexHtml(...paths);
// Blech, firebse requires index.html stored at the root of the public
// directory. So we write it out there and gitignore it instead of
// putting it in the gen directory.
await fs.promises.writeFile(`public/index.html`, indexHtmlContents);
} catch (err) {
// If the compile is broken, no main.js file will be written out, but
// we still want to exit cleanly so bundle's watch can continue.
if (err.code === 'ENOENT') {
console.log(`File not found: ${err}`);
} else {
throw err;
}
}
cb();
},
);
esbuild.stdout.on('data', (data) => process.stdout.write(data.toString()));
esbuild.stderr.on('data', (data) => process.stderr.write(data.toString()));
});
const execAndPipe = (command) => {
return (cb) => {
const x = childProcess.exec(command, cb);
x.stdout.on('data', (data) => process.stdout.write(data.toString()));
x.stderr.on('data', (data) => process.stderr.write(data.toString()));
};
};
task('deploy-firebase-no-functions', (cb) => {
const project = argv.project || DEFAULT_PROJECT;
// Don't deploy functions by default since it takes so long.
const output = execAndPipe(`${FIREBASE_PATH} deploy --project ${project} --except functions`);
output(cb);
});
task('deploy-firebase-functions', (cb) => {
const project = argv.project || DEFAULT_PROJECT;
// Don't deploy functions by default since it takes so long.
const output = execAndPipe(`${FIREBASE_PATH} deploy --project ${project} --only functions`);
output(cb);
});
task('serve-firebase', (cb) => {
const port = argv.project && argv.project !== DEFAULT_PROJECT ? 8000 : 5000;
const output = execAndPipe(`${FIREBASE_PATH} serve --project ${DEFAULT_PROJECT} --port=${port}`);
output(cb);
});
task(
'tsc',
execAndPipe('./node_modules/typescript/bin/tsc --project tsconfig.json --watch --noEmit'),
);
// Always run bundle-once the first time to ensure index.html gets generated if
// this is a new checkout or it got deleted.
task('bundle', () => {
return watch(
['static/**/*.ts', 'static/**/*.css'],
{ ignoreInitial: false },
task('bundle-once'),
);
});
task('serve', parallel(['serve-firebase', 'bundle', 'tsc']));
task('install', execAndPipe('npm install --no-fund'));
task('install-and-serve', series(['install', 'serve']));
task('deploy', series('bundle-once', 'deploy-firebase-no-functions'));
task('deploy-all', series('bundle-once', 'deploy-firebase-no-functions', 'deploy-firebase-functions'));