mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-24 04:39:01 +00:00
Add build using polymer-build (#344)
* Add build using polymer-build * Use bundle strategies to tweak stripExcludes * Only vulcanize hass.io panel * Rename hassio panel generate script * Remove hydrolysis * Get it all somewhat working * Fixes * Allow ES2015 + fix minify JS * Clarify we need to fix service worker minify * Move service worker template out of tasks folder * Fix broken CSS * Wrap it up * Fix maps
This commit is contained in:
@@ -1,100 +0,0 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
/*
|
||||
Generate a caching service worker for HA
|
||||
|
||||
Will be called as part of build_frontend.
|
||||
|
||||
Expects home-assistant-polymer repo as submodule of HA repo.
|
||||
Creates a caching service worker based on the CURRENT content of HA repo.
|
||||
Output service worker to build/service_worker.js
|
||||
*/
|
||||
var crypto = require('crypto');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var swPrecache = require('sw-precache');
|
||||
var uglifyJS = require('uglify-js');
|
||||
|
||||
const DEV = !!JSON.parse(process.env.BUILD_DEV || 'true');
|
||||
|
||||
var rootDir = '..';
|
||||
var panelDir = rootDir + '/panels';
|
||||
|
||||
var dynamicUrlToDependencies = {
|
||||
'/': [
|
||||
rootDir + '/frontend.html',
|
||||
rootDir + '/core.js',
|
||||
rootDir + '/compatibility.js',
|
||||
],
|
||||
};
|
||||
|
||||
var staticFingerprinted = [
|
||||
'frontend.html',
|
||||
'mdi.html',
|
||||
'core.js',
|
||||
'compatibility.js',
|
||||
];
|
||||
|
||||
// These panels will always be registered inside HA and thus can
|
||||
// be safely assumed to be able to preload.
|
||||
var panelsFingerprinted = [
|
||||
'map', 'dev-event', 'dev-info', 'dev-service', 'dev-state', 'dev-template',
|
||||
];
|
||||
|
||||
function md5(filename) {
|
||||
return crypto.createHash('md5')
|
||||
.update(fs.readFileSync(filename)).digest('hex');
|
||||
}
|
||||
|
||||
// Create fingerprinted versions of our dependencies.
|
||||
staticFingerprinted.forEach(fn => {
|
||||
var parts = path.parse(fn);
|
||||
var hash = md5(rootDir + '/' + parts.name + parts.ext);
|
||||
var url = '/static/' + parts.name + '-' + hash + parts.ext;
|
||||
var fpath = rootDir + '/' + parts.name + parts.ext;
|
||||
dynamicUrlToDependencies[url] = [fpath];
|
||||
});
|
||||
|
||||
panelsFingerprinted.forEach(panel => {
|
||||
var fpath = panelDir + '/ha-panel-' + panel + '.html';
|
||||
var hash = md5(fpath);
|
||||
var url = '/frontend/panels/' + panel + '-' + hash + '.html';
|
||||
dynamicUrlToDependencies[url] = [fpath];
|
||||
});
|
||||
|
||||
var options = {
|
||||
navigateFallback: '/',
|
||||
navigateFallbackWhitelist: [/^((?!(static|api|local|service_worker.js|manifest.json)).)*$/],
|
||||
dynamicUrlToDependencies: dynamicUrlToDependencies,
|
||||
staticFileGlobs: [
|
||||
rootDir + '/icons/favicon.ico',
|
||||
rootDir + '/icons/favicon-192x192.png',
|
||||
rootDir + '/webcomponents-lite.min.js',
|
||||
rootDir + '/fonts/roboto/Roboto-Light.ttf',
|
||||
rootDir + '/fonts/roboto/Roboto-Medium.ttf',
|
||||
rootDir + '/fonts/roboto/Roboto-Regular.ttf',
|
||||
rootDir + '/fonts/roboto/Roboto-Bold.ttf',
|
||||
rootDir + '/images/card_media_player_bg.png',
|
||||
],
|
||||
stripPrefix: '..',
|
||||
replacePrefix: 'static',
|
||||
verbose: true,
|
||||
};
|
||||
|
||||
var devBase = 'console.warn("Service worker caching disabled in development")';
|
||||
|
||||
var swHass = fs.readFileSync(path.resolve(__dirname, 'service-worker.js.tmpl'), 'UTF-8')
|
||||
|
||||
var genPromise = DEV ? Promise.resolve(devBase) : swPrecache.generate(options);
|
||||
|
||||
genPromise = genPromise.then(swString => swString + '\n' + swHass);
|
||||
|
||||
if (!DEV) {
|
||||
genPromise = genPromise.then(
|
||||
swString => uglifyJS.minify(swString, { fromString: true }).code);
|
||||
}
|
||||
|
||||
genPromise.then(
|
||||
swString =>
|
||||
fs.writeFileSync(path.resolve(__dirname, '../build/service_worker.js'), swString)
|
||||
).catch(err => console.error(err));
|
||||
@@ -1,77 +0,0 @@
|
||||
self.addEventListener("push", function(event) {
|
||||
var data;
|
||||
if (event.data) {
|
||||
data = event.data.json();
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title, data)
|
||||
.then(function(notification){
|
||||
firePushCallback({
|
||||
type: "received",
|
||||
tag: data.tag,
|
||||
data: data.data
|
||||
}, data.data.jwt);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
var url;
|
||||
|
||||
notificationEventCallback('clicked', event);
|
||||
|
||||
event.notification.close();
|
||||
|
||||
if (!event.notification.data || !event.notification.data.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = event.notification.data.url;
|
||||
|
||||
if (!url) return;
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({
|
||||
type: 'window',
|
||||
})
|
||||
.then(function (windowClients) {
|
||||
var i;
|
||||
var client;
|
||||
for (i = 0; i < windowClients.length; i++) {
|
||||
client = windowClients[i];
|
||||
if (client.url === url && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(url);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
});
|
||||
self.addEventListener('notificationclose', function(event) {
|
||||
notificationEventCallback('closed', event);
|
||||
});
|
||||
|
||||
function notificationEventCallback(event_type, event){
|
||||
firePushCallback({
|
||||
action: event.action,
|
||||
data: event.notification.data,
|
||||
tag: event.notification.tag,
|
||||
type: event_type
|
||||
}, event.notification.data.jwt);
|
||||
}
|
||||
function firePushCallback(payload, jwt){
|
||||
// Don't send the JWT in the payload.data
|
||||
delete payload.data.jwt;
|
||||
// If payload.data is empty then just remove the entire payload.data object.
|
||||
if (Object.keys(payload.data).length === 0 && payload.data.constructor === Object) {
|
||||
delete payload.data;
|
||||
}
|
||||
fetch('/api/notify.html5/callback', {
|
||||
method: 'POST',
|
||||
headers: new Headers({'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer '+jwt}),
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var Vulcanize = require('vulcanize');
|
||||
var minify = require('html-minifier');
|
||||
var hyd = require('hydrolysis');
|
||||
var fs = require('fs');
|
||||
|
||||
if (!fs.existsSync('build')) {
|
||||
fs.mkdirSync('build');
|
||||
}
|
||||
if (!fs.existsSync('build/panels')) {
|
||||
fs.mkdirSync('build/panels');
|
||||
}
|
||||
|
||||
function minifyHTML(html) {
|
||||
return minify.minify(html, {
|
||||
customAttrAssign: [/\$=/],
|
||||
removeComments: true,
|
||||
removeCommentsFromCDATA: true,
|
||||
removeCDATASectionsFromCDATA: true,
|
||||
collapseWhitespace: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
});
|
||||
}
|
||||
|
||||
const baseVulcanOptions = {
|
||||
inlineScripts: true,
|
||||
inlineCss: true,
|
||||
implicitStrip: true,
|
||||
stripComments: true,
|
||||
};
|
||||
|
||||
const panelVulcan = new Vulcanize({
|
||||
inlineScripts: true,
|
||||
inlineCss: true,
|
||||
implicitStrip: true,
|
||||
stripComments: true,
|
||||
stripExcludes: [
|
||||
'panels/hassio/hassio-main.html'
|
||||
],
|
||||
});
|
||||
|
||||
const baseExcludes = [
|
||||
'bower_components/font-roboto/roboto.html',
|
||||
'bower_components/paper-styles/color.html',
|
||||
];
|
||||
|
||||
const toProcess = [
|
||||
// This is the main entry point
|
||||
{
|
||||
source: './src/home-assistant.html',
|
||||
output: './build/frontend.html',
|
||||
vulcan: new Vulcanize(Object.assign({}, baseVulcanOptions, {
|
||||
stripExcludes: baseExcludes,
|
||||
})),
|
||||
},
|
||||
// This is the Hass.io configuration panel
|
||||
// It's build standalone because it is embedded in the supervisor.
|
||||
{
|
||||
source: './panels/hassio/hassio-main.html',
|
||||
output: './build-temp/hassio-main.html',
|
||||
vulcan: new Vulcanize(Object.assign({}, baseVulcanOptions, {
|
||||
stripExcludes: baseExcludes.concat([
|
||||
'bower_components/polymer/polymer.html',
|
||||
'bower_components/iron-meta/iron-meta.html',
|
||||
]),
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
fs.readdirSync('./panels').forEach((panel) => {
|
||||
toProcess.push({
|
||||
source: `./panels/${panel}/ha-panel-${panel}.html`,
|
||||
output: `./build/panels/ha-panel-${panel}.html`,
|
||||
vulcan: panelVulcan,
|
||||
});
|
||||
});
|
||||
|
||||
function vulcanizeEntry(entry) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('Processing', entry.source);
|
||||
entry.vulcan.process(entry.source, (err, inlinedHtml) => {
|
||||
if (err !== null) {
|
||||
reject(`${entry.source}: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Writing', entry.output);
|
||||
fs.writeFileSync(entry.output, minifyHTML(inlinedHtml));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch all dependencies of main app and exclude them from panels
|
||||
hyd.Analyzer.analyze('src/home-assistant.html')
|
||||
.then(function (analyzer) {
|
||||
return analyzer._getDependencies('src/home-assistant.html');
|
||||
})
|
||||
.then((deps) => {
|
||||
panelVulcan.stripExcludes = panelVulcan.stripExcludes.concat(deps);
|
||||
})
|
||||
// Chain all vulcanizing work as promises
|
||||
.then(() => toProcess.reduce(
|
||||
(p, entry) => p.then(() => vulcanizeEntry(entry)),
|
||||
Promise.resolve()))
|
||||
.catch(err => console.error('Something went wrong!', err));
|
||||
Reference in New Issue
Block a user