client/build: Clean up build process
Fixes incorrect URIs of iOS splash screens and OpenSans font Files get gzipped inside build script Better nginx configuration build.js uses more consistent, synchronous code
This commit is contained in:
parent
e6445b431f
commit
a5a06bf2d1
4 changed files with 222 additions and 196 deletions
|
@ -8,10 +8,7 @@ COPY . ./
|
||||||
|
|
||||||
ARG BUILD_INFO="docker-latest"
|
ARG BUILD_INFO="docker-latest"
|
||||||
ARG CLIENT_BUILD_ARGS=""
|
ARG CLIENT_BUILD_ARGS=""
|
||||||
RUN BASE_URL="__BASEURL__" node build.js ${CLIENT_BUILD_ARGS}
|
RUN BASE_URL="__BASEURL__" node build.js --gzip ${CLIENT_BUILD_ARGS}
|
||||||
|
|
||||||
RUN find public/ -name public/index.html -prune -o -type f -size +5k \
|
|
||||||
-print0 | xargs -0 -- gzip -6 -k
|
|
||||||
|
|
||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
@ -23,7 +20,7 @@ RUN \
|
||||||
echo 'sed -i "s|__BACKEND__|${BACKEND_HOST}|" /etc/nginx/nginx.conf' \
|
echo 'sed -i "s|__BACKEND__|${BACKEND_HOST}|" /etc/nginx/nginx.conf' \
|
||||||
>> /init && \
|
>> /init && \
|
||||||
echo 'sed -i "s|__BASEURL__|${BASE_URL:-/}|" /var/www/index.htm' >> /init && \
|
echo 'sed -i "s|__BASEURL__|${BASE_URL:-/}|" /var/www/index.htm' >> /init && \
|
||||||
echo 'exec nginx -g "daemon off;"' >> /init && \
|
echo 'exec nginx' >> /init && \
|
||||||
chmod a+x /init
|
chmod a+x /init
|
||||||
|
|
||||||
CMD ["/init"]
|
CMD ["/init"]
|
||||||
|
|
283
client/build.js
Normal file → Executable file
283
client/build.js
Normal file → Executable file
|
@ -1,5 +1,35 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
|
||||||
|
const webapp_icons = [
|
||||||
|
{name: 'android-chrome-192x192.png', size: 192},
|
||||||
|
{name: 'android-chrome-512x512.png', size: 512},
|
||||||
|
{name: 'apple-touch-icon.png', size: 180},
|
||||||
|
{name: 'mstile-150x150.png', size: 150}
|
||||||
|
];
|
||||||
|
|
||||||
|
const webapp_splash_screens = [
|
||||||
|
{w: 640, h: 1136, center: 320},
|
||||||
|
{w: 750, h: 1294, center: 375},
|
||||||
|
{w: 1125, h: 2436, center: 565},
|
||||||
|
{w: 1242, h: 2148, center: 625},
|
||||||
|
{w: 1536, h: 2048, center: 770},
|
||||||
|
{w: 1668, h: 2224, center: 820},
|
||||||
|
{w: 2048, h: 2732, center: 1024}
|
||||||
|
];
|
||||||
|
|
||||||
|
const external_js = [
|
||||||
|
'underscore',
|
||||||
|
'superagent',
|
||||||
|
'mousetrap',
|
||||||
|
'js-cookie',
|
||||||
|
'nprogress',
|
||||||
|
];
|
||||||
|
|
||||||
|
// -------------------------------------------------
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
@ -10,48 +40,18 @@ function readTextFile(path) {
|
||||||
return fs.readFileSync(path, 'utf-8');
|
return fs.readFileSync(path, 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeFile(path, content) {
|
function gzipFile(file) {
|
||||||
return fs.writeFileSync(path, content);
|
file = path.normalize(file);
|
||||||
|
execSync('gzip -6 -k ' + file);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVersion() {
|
// -------------------------------------------------
|
||||||
let build_info = process.env.BUILD_INFO;
|
|
||||||
if (build_info) {
|
|
||||||
return build_info.trim();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
build_info = execSync('git describe --always --dirty --long --tags')
|
|
||||||
.toString();
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Cannot find build version');
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
||||||
return build_info.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfig() {
|
function bundleHtml() {
|
||||||
let config = {
|
const underscore = require('underscore');
|
||||||
meta: {
|
const babelify = require('babelify');
|
||||||
version: getVersion(),
|
|
||||||
buildDate: new Date().toUTCString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return config;
|
const baseUrl = process.env.BASE_URL ? process.env.BASE_URL : '/';
|
||||||
}
|
|
||||||
|
|
||||||
function copyFile(source, target) {
|
|
||||||
fs.createReadStream(source).pipe(fs.createWriteStream(target));
|
|
||||||
}
|
|
||||||
|
|
||||||
function minifyJs(path) {
|
|
||||||
return require('terser').minify(fs.readFileSync(path, 'utf-8'), {compress: {unused: false}}).code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function minifyCss(css) {
|
|
||||||
return require('csso').minify(css).css;
|
|
||||||
}
|
|
||||||
|
|
||||||
function minifyHtml(html) {
|
function minifyHtml(html) {
|
||||||
return require('html-minifier').minify(html, {
|
return require('html-minifier').minify(html, {
|
||||||
|
@ -61,23 +61,20 @@ function minifyHtml(html) {
|
||||||
}).trim();
|
}).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleHtml() {
|
const baseHtml = readTextFile('./html/index.htm')
|
||||||
const underscore = require('underscore');
|
.replace('<!-- Base HTML Placeholder -->', `<base href="${baseUrl}"/>`);
|
||||||
const babelify = require('babelify');
|
fs.writeFileSync('./public/index.htm', minifyHtml(baseHtml));
|
||||||
const baseHtml = readTextFile('./html/index.htm', 'utf-8');
|
|
||||||
const baseUrl = process.env.BASE_URL ? process.env.BASE_URL : '/';
|
|
||||||
const finalHtml = baseHtml.replace(
|
|
||||||
'<!-- Base HTML Placeholder -->', `<base href="${baseUrl}"/>`);
|
|
||||||
writeFile('./public/index.htm', minifyHtml(finalHtml));
|
|
||||||
|
|
||||||
glob('./html/**/*.tpl', {}, (er, files) => {
|
let compiledTemplateJs = [
|
||||||
let compiledTemplateJs = '\'use strict\'\n';
|
`'use strict';`,
|
||||||
compiledTemplateJs += 'let _ = require(\'underscore\');';
|
`let _ = require('underscore');`,
|
||||||
compiledTemplateJs += 'let templates = {};';
|
`let templates = {};`
|
||||||
for (const file of files) {
|
];
|
||||||
|
|
||||||
|
for (const file of glob.sync('./html/**/*.tpl')) {
|
||||||
const name = path.basename(file, '.tpl').replace(/_/g, '-');
|
const name = path.basename(file, '.tpl').replace(/_/g, '-');
|
||||||
const placeholders = [];
|
const placeholders = [];
|
||||||
let templateText = readTextFile(file, 'utf-8');
|
let templateText = readTextFile(file);
|
||||||
templateText = templateText.replace(
|
templateText = templateText.replace(
|
||||||
/<%.*?%>/ig,
|
/<%.*?%>/ig,
|
||||||
(match) => {
|
(match) => {
|
||||||
|
@ -92,145 +89,175 @@ function bundleHtml() {
|
||||||
|
|
||||||
const functionText = underscore.template(
|
const functionText = underscore.template(
|
||||||
templateText, {variable: 'ctx'}).source;
|
templateText, {variable: 'ctx'}).source;
|
||||||
compiledTemplateJs += `templates['${name}'] = ${functionText};`;
|
|
||||||
|
compiledTemplateJs.push(`templates['${name}'] = ${functionText};`);
|
||||||
}
|
}
|
||||||
compiledTemplateJs += 'module.exports = templates;';
|
compiledTemplateJs.push('module.exports = templates;');
|
||||||
writeFile('./js/.templates.autogen.js', compiledTemplateJs);
|
|
||||||
|
fs.writeFileSync('./js/.templates.autogen.js', compiledTemplateJs.join('\n'));
|
||||||
console.info('Bundled HTML');
|
console.info('Bundled HTML');
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleCss() {
|
function bundleCss() {
|
||||||
const stylus = require('stylus');
|
const stylus = require('stylus');
|
||||||
glob('./css/**/*.styl', {}, (er, files) => {
|
|
||||||
let css = '';
|
|
||||||
for (const file of files) {
|
|
||||||
css += stylus.render(
|
|
||||||
readTextFile(file), {filename: file});
|
|
||||||
}
|
|
||||||
writeFile('./public/css/app.min.css', minifyCss(css));
|
|
||||||
|
|
||||||
copyFile(
|
function minifyCss(css) {
|
||||||
|
return require('csso').minify(css).css;
|
||||||
|
}
|
||||||
|
|
||||||
|
let css = '';
|
||||||
|
for (const file of glob.sync('./css/**/*.styl')) {
|
||||||
|
css += stylus.render(readTextFile(file), {filename: file});
|
||||||
|
}
|
||||||
|
fs.writeFileSync('./public/css/app.min.css', minifyCss(css));
|
||||||
|
if (process.argv.includes('--gzip')) {
|
||||||
|
gzipFile('./public/css/app.min.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.copyFileSync(
|
||||||
'./node_modules/font-awesome/css/font-awesome.min.css',
|
'./node_modules/font-awesome/css/font-awesome.min.css',
|
||||||
'./public/css/vendor.min.css');
|
'./public/css/vendor.min.css');
|
||||||
|
if (process.argv.includes('--gzip')) {
|
||||||
|
gzipFile('./public/css/vendor.min.css');
|
||||||
|
}
|
||||||
|
|
||||||
console.info('Bundled CSS');
|
console.info('Bundled CSS');
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleJs() {
|
function bundleJs() {
|
||||||
const browserify = require('browserify');
|
const browserify = require('browserify');
|
||||||
const external = [
|
|
||||||
'underscore',
|
|
||||||
'superagent',
|
|
||||||
'mousetrap',
|
|
||||||
'js-cookie',
|
|
||||||
'nprogress',
|
|
||||||
];
|
|
||||||
|
|
||||||
function writeJsBundle(b, path, message, compress) {
|
function minifyJs(path) {
|
||||||
|
return require('terser').minify(
|
||||||
|
fs.readFileSync(path, 'utf-8'), {compress: {unused: false}}).code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeJsBundle(b, path, compress, callback) {
|
||||||
let outputFile = fs.createWriteStream(path);
|
let outputFile = fs.createWriteStream(path);
|
||||||
b.bundle().pipe(outputFile);
|
b.bundle().pipe(outputFile);
|
||||||
outputFile.on('finish', function() {
|
outputFile.on('finish', () => {
|
||||||
if (compress) {
|
if (compress) {
|
||||||
writeFile(path, minifyJs(path));
|
fs.writeFileSync(path, minifyJs(path));
|
||||||
}
|
}
|
||||||
console.info(message);
|
callback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
glob('./js/**/*.js', {}, (er, files) => {
|
|
||||||
if (!process.argv.includes('--no-vendor-js')) {
|
if (!process.argv.includes('--no-vendor-js')) {
|
||||||
let b = browserify();
|
let b = browserify();
|
||||||
for (let lib of external) {
|
for (let lib of external_js) {
|
||||||
b.require(lib);
|
b.require(lib);
|
||||||
}
|
}
|
||||||
if (!process.argv.includes('--no-transpile')) {
|
if (!process.argv.includes('--no-transpile')) {
|
||||||
b.add(require.resolve('babel-polyfill'));
|
b.add(require.resolve('babel-polyfill'));
|
||||||
}
|
}
|
||||||
writeJsBundle(
|
const file = './public/js/vendor.min.js';
|
||||||
b, './public/js/vendor.min.js', 'Bundled vendor JS', true);
|
writeJsBundle(b, file, true, () => {
|
||||||
|
if (process.argv.includes('--gzip')) {
|
||||||
|
gzipFile(file);
|
||||||
|
}
|
||||||
|
console.info('Bundled vendor JS');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.argv.includes('--no-app-js')) {
|
if (!process.argv.includes('--no-app-js')) {
|
||||||
let outputFile = fs.createWriteStream('./public/js/app.min.js');
|
|
||||||
let b = browserify({debug: process.argv.includes('--debug')});
|
let b = browserify({debug: process.argv.includes('--debug')});
|
||||||
if (!process.argv.includes('--no-transpile')) {
|
if (!process.argv.includes('--no-transpile')) {
|
||||||
b = b.transform('babelify');
|
b = b.transform('babelify');
|
||||||
}
|
}
|
||||||
writeJsBundle(
|
b = b.external(external_js).add(glob.sync('./js/**/*.js'));
|
||||||
b.external(external).add(files),
|
const compress = !process.argv.includes('--debug');
|
||||||
'./public/js/app.min.js',
|
const file = './public/js/app.min.js';
|
||||||
'Bundled app JS',
|
writeJsBundle(b, file, compress, () => {
|
||||||
!process.argv.includes('--debug'));
|
if (process.argv.includes('--gzip')) {
|
||||||
|
gzipFile(file);
|
||||||
}
|
}
|
||||||
|
console.info('Bundled app JS');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function bundleConfig(config) {
|
function bundleConfig() {
|
||||||
writeFile(
|
function getVersion() {
|
||||||
'./js/.config.autogen.json', JSON.stringify(config));
|
let build_info = process.env.BUILD_INFO;
|
||||||
glob('./node_modules/font-awesome/fonts/*.*', {}, (er, files) => {
|
if (!build_info) {
|
||||||
for (let file of files) {
|
try {
|
||||||
if (fs.lstatSync(file).isDirectory()) {
|
build_info = execSync('git describe --always --dirty --long --tags').toString();
|
||||||
continue;
|
} catch (e) {
|
||||||
|
console.warn('Cannot find build version');
|
||||||
|
build_info = 'unknown';
|
||||||
}
|
}
|
||||||
copyFile(file, path.join('./public/fonts/', path.basename(file)));
|
|
||||||
}
|
}
|
||||||
});
|
return build_info.trim();
|
||||||
|
}
|
||||||
|
const config = {
|
||||||
|
meta: {
|
||||||
|
version: getVersion(),
|
||||||
|
buildDate: new Date().toUTCString()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync('./js/.config.autogen.json', JSON.stringify(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
function bundleBinaryAssets() {
|
function bundleBinaryAssets() {
|
||||||
copyFile('./img/favicon.png', './public/img/favicon.png');
|
fs.copyFileSync('./img/favicon.png', './public/img/favicon.png');
|
||||||
copyFile('./img/transparency_grid.png', './public/img/transparency_grid.png');
|
fs.copyFileSync('./img/transparency_grid.png', './public/img/transparency_grid.png');
|
||||||
|
|
||||||
|
for (let file of glob.sync('./node_modules/font-awesome/fonts/*.*')) {
|
||||||
|
if (fs.lstatSync(file).isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fs.copyFileSync(file, path.join('./public/fonts/', path.basename(file)));
|
||||||
|
}
|
||||||
|
if (process.argv.includes('--gzip')) {
|
||||||
|
for (let file of glob.sync('./public/fonts/*.*')) {
|
||||||
|
if (file.endsWith('woff2')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
gzipFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.info('Copied Fonts')
|
||||||
|
}
|
||||||
|
|
||||||
|
function bundleWebAppFiles() {
|
||||||
const Jimp = require('jimp');
|
const Jimp = require('jimp');
|
||||||
|
|
||||||
for (let icon of [
|
fs.copyFileSync('./app/manifest.json', './public/manifest.json');
|
||||||
{name: 'android-chrome-192x192.png', size: 192},
|
|
||||||
{name: 'android-chrome-512x512.png', size: 512},
|
Promise.all(webapp_icons.map(icon => {
|
||||||
{name: 'apple-touch-icon.png', size: 180},
|
return Jimp.read('./img/app.png')
|
||||||
{name: 'mstile-150x150.png', size: 150}
|
.then(file => {
|
||||||
]) {
|
file.resize(icon.size, Jimp.AUTO, Jimp.RESIZE_BEZIER)
|
||||||
Jimp.read('./img/app.png', (err, infile) => {
|
|
||||||
infile
|
|
||||||
.resize(icon.size, Jimp.AUTO, Jimp.RESIZE_BEZIER)
|
|
||||||
.write(path.join('./public/img/', icon.name));
|
.write(path.join('./public/img/', icon.name));
|
||||||
});
|
});
|
||||||
}
|
}))
|
||||||
|
.then(() => {
|
||||||
console.info('Generated webapp icons');
|
console.info('Generated webapp icons');
|
||||||
|
});
|
||||||
|
|
||||||
for (let dim of [
|
Promise.all(webapp_splash_screens.map(dim => {
|
||||||
{w: 640, h: 1136, center: 320},
|
return Jimp.read('./img/splash.png')
|
||||||
{w: 750, h: 1294, center: 375},
|
.then(file => {
|
||||||
{w: 1125, h: 2436, center: 565},
|
file.resize(dim.center, Jimp.AUTO, Jimp.RESIZE_BEZIER)
|
||||||
{w: 1242, h: 2148, center: 625},
|
|
||||||
{w: 1536, h: 2048, center: 770},
|
|
||||||
{w: 1668, h: 2224, center: 820},
|
|
||||||
{w: 2048, h: 2732, center: 1024}
|
|
||||||
]) {
|
|
||||||
Jimp.read('./img/splash.png', (err, infile) => {
|
|
||||||
infile
|
|
||||||
.resize(dim.center, Jimp.AUTO, Jimp.RESIZE_BEZIER)
|
|
||||||
.background(0xFFFFFFFF)
|
.background(0xFFFFFFFF)
|
||||||
.contain(dim.w, dim.center,
|
.contain(dim.w, dim.center,
|
||||||
Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
|
Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
|
||||||
.contain(dim.w, dim.h,
|
.contain(dim.w, dim.h,
|
||||||
Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
|
Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE)
|
||||||
.write(path.join('./public/img/',
|
.write(path.join('./public/img/',
|
||||||
'apple-touch-startup-image-'
|
'apple-touch-startup-image-' + dim.w + 'x' + dim.h + '.png'));
|
||||||
+ dim.w + '-' + dim.h + '.png'));
|
});
|
||||||
|
}))
|
||||||
|
.then(() => {
|
||||||
|
console.info('Generated splash screens');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.info('Generated splash screens');
|
|
||||||
}
|
|
||||||
|
|
||||||
function bundleWebAppFiles() {
|
// -------------------------------------------------
|
||||||
copyFile('./app/manifest.json', './public/manifest.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = getConfig();
|
bundleConfig();
|
||||||
bundleConfig(config);
|
|
||||||
bundleBinaryAssets();
|
bundleBinaryAssets();
|
||||||
bundleWebAppFiles();
|
bundleWebAppFiles();
|
||||||
if (!process.argv.includes('--no-html')) {
|
if (!process.argv.includes('--no-html')) {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Open Sans'), local('OpenSans'), url(fonts/open_sans.woff2) format('woff2');
|
src: local('Open Sans'), local('OpenSans'), url(../fonts/open_sans.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
|
|
||||||
/* make <body> cover entire viewport */
|
/* make <body> cover entire viewport */
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
worker_processes 1;
|
worker_processes 1;
|
||||||
|
user nginx;
|
||||||
|
|
||||||
error_log /dev/stderr warn;
|
error_log /dev/stderr warn;
|
||||||
pid /var/run/nginx.pid;
|
pid /var/run/nginx.pid;
|
||||||
|
@ -54,3 +55,4 @@ http {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
daemon off;
|
||||||
|
|
Loading…
Reference in a new issue