diff --git a/.travis.yml b/.travis.yml
index 22ce3a7c66..22d870d277 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,7 +15,7 @@ addons:
packages:
- google-chrome-stable
script:
- - npm run js_prod
+ - npm run build
- npm run test
- xvfb-run wct
- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --plugin sauce; fi
diff --git a/gulp/config.js b/gulp/config.js
new file mode 100644
index 0000000000..ff4ef9a25f
--- /dev/null
+++ b/gulp/config.js
@@ -0,0 +1,7 @@
+var path = require('path');
+
+module.exports = {
+ static_dir: path.resolve(__dirname, '../..'),
+ polymer_dir: path.resolve(__dirname, '..'),
+ build_dir: path.resolve(__dirname, '../build'),
+};
diff --git a/script/service-worker.js.tmpl b/gulp/service-worker.js.tmpl
similarity index 100%
rename from script/service-worker.js.tmpl
rename to gulp/service-worker.js.tmpl
diff --git a/gulp/tasks/build.js b/gulp/tasks/build.js
new file mode 100644
index 0000000000..a20c03ef15
--- /dev/null
+++ b/gulp/tasks/build.js
@@ -0,0 +1,107 @@
+import buble from 'gulp-buble';
+import uglify from 'gulp-uglify';
+import { gulp as cssSlam } from 'css-slam';
+import gulp from 'gulp';
+import filter from 'gulp-filter';
+import htmlMinifier from 'gulp-html-minifier';
+import gulpif from 'gulp-if';
+import { PolymerProject, HtmlSplitter } from 'polymer-build';
+import {
+ composeStrategies,
+ generateShellMergeStrategy,
+} from 'polymer-bundler';
+import mergeStream from 'merge-stream';
+import rename from 'gulp-rename';
+
+import polymerConfig from '../../polymer';
+
+function minifyStream(stream) {
+ const sourcesHtmlSplitter = new HtmlSplitter();
+ return stream
+ .pipe(sourcesHtmlSplitter.split())
+ .pipe(gulpif(/\.js$/, buble()))
+ .pipe(gulpif(/\.js$/, uglify({ sourceMap : false })))
+ .pipe(gulpif(/\.css$/, cssSlam()))
+ .pipe(gulpif(/\.html$/, cssSlam()))
+ .pipe(gulpif(/\.html$/, htmlMinifier({
+ collapseWhitespace: true,
+ removeComments: true
+ })))
+ .pipe(sourcesHtmlSplitter.rejoin());
+}
+
+function renamePanel(path) {
+ // Rename panels to be panels/* and not their subdir
+ if (path.basename.substr(0, 9) === 'ha-panel-' && path.extname === '.html') {
+ path.dirname = 'panels/';
+ }
+
+ // Rename frontend
+ if (path.dirname === 'src' && path.basename === 'home-assistant' &&
+ path.extname === '.html') {
+ path.dirname = '';
+ path.basename = 'frontend';
+ }
+}
+
+/**
+ * Polymer build strategy to strip imports, even if explictely imported
+ */
+function generateStripStrategy(urls) {
+ return (bundles) => {
+ for (const bundle of bundles) {
+ for (const url of urls) {
+ bundle.stripImports.add(url);
+ }
+ }
+ return bundles;
+ };
+}
+
+/**
+ * Polymer build strategy to strip everything but the entrypoints
+ * for bundles that match a specific entry point.
+ */
+function stripAllButEntrypoint(entryPoint) {
+ return (bundles) => {
+ for (const bundle of bundles) {
+ if (bundle.entrypoints.size === 1 &&
+ bundle.entrypoints.has(entryPoint)) {
+ for (const file of bundle.files) {
+ if (!bundle.entrypoints.has(file)) {
+ bundle.stripImports.add(file);
+ }
+ }
+ }
+ }
+ return bundles;
+ };
+}
+
+
+gulp.task('build', ['ru_all'], () => {
+ const strategy = composeStrategies([
+ generateShellMergeStrategy(polymerConfig.shell),
+ generateStripStrategy([
+ 'bower_components/font-roboto/roboto.html',
+ 'bower_components/paper-styles/color.html',
+ ]),
+ stripAllButEntrypoint('panels/hassio/ha-panel-hassio.html')
+ ]);
+ const project = new PolymerProject(polymerConfig);
+
+ return mergeStream(minifyStream(project.sources()),
+ minifyStream(project.dependencies()))
+ .pipe(project.bundler({
+ strategy,
+ strip: true,
+ sourcemaps: false,
+ stripComments: true,
+ inlineScripts: true,
+ inlineCss: true,
+ implicitStrip: true,
+ }))
+ .pipe(rename(renamePanel))
+ .pipe(filter(['**', '!src/entrypoint.html']))
+ .pipe(gulp.dest('build/'));
+});
diff --git a/gulp/tasks/clean.js b/gulp/tasks/clean.js
new file mode 100644
index 0000000000..5110b23a0c
--- /dev/null
+++ b/gulp/tasks/clean.js
@@ -0,0 +1,6 @@
+import del from 'del';
+import gulp from 'gulp';
+
+gulp.task('clean', () => {
+ return del(['build', 'build-temp']);
+});
diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js
new file mode 100644
index 0000000000..3c0cce5f35
--- /dev/null
+++ b/gulp/tasks/default.js
@@ -0,0 +1,9 @@
+import gulp from 'gulp';
+import runSequence from 'run-sequence';
+
+gulp.task('default', () => {
+ return runSequence.use(gulp)(
+ 'clean',
+ 'build',
+ );
+});
diff --git a/gulp/tasks/gen-service-worker.js b/gulp/tasks/gen-service-worker.js
new file mode 100755
index 0000000000..55db581769
--- /dev/null
+++ b/gulp/tasks/gen-service-worker.js
@@ -0,0 +1,110 @@
+/*
+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
+
+TODO:
+ - Use gulp streams
+ - Fix minifying the stream
+*/
+var gulp = require('gulp');
+var crypto = require('crypto');
+var fs = require('fs');
+var path = require('path');
+var swPrecache = require('sw-precache');
+var uglifyJS = require('uglify-js');
+
+const config = require('../config');
+
+const DEV = !!JSON.parse(process.env.BUILD_DEV || 'true');
+
+var rootDir = config.static_dir;
+var panelDir = path.resolve(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',
+ 'dev-mqtt',
+];
+
+function md5(filename) {
+ return crypto.createHash('md5')
+ .update(fs.readFileSync(filename)).digest('hex');
+}
+
+gulp.task('gen-service-worker', () => {
+ // 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);
+
+ // Fix this
+ // if (!DEV) {
+ // genPromise = genPromise.then(
+ // swString => uglifyJS.minify(swString, { fromString: true }).code);
+ // }
+
+ genPromise.then(swString => {
+ fs.writeFileSync(path.resolve(config.build_dir, 'service_worker.js'), swString);
+ });
+
+ return genPromise;
+});
diff --git a/script/vulcanize.js b/gulp/tasks/hassio-panel.js
similarity index 53%
rename from script/vulcanize.js
rename to gulp/tasks/hassio-panel.js
index 4ccec5bbdc..df3dc096c9 100755
--- a/script/vulcanize.js
+++ b/gulp/tasks/hassio-panel.js
@@ -1,17 +1,13 @@
-#!/usr/bin/env node
-
+/*
+TODO:
+ - Use gulp streams
+ - Use polymer bundler to vulcanize
+*/
+var gulp = require('gulp');
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: [/\$=/],
@@ -33,30 +29,12 @@ const baseVulcanOptions = {
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.
{
@@ -71,14 +49,6 @@ const toProcess = [
},
];
-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);
@@ -95,16 +65,14 @@ function vulcanizeEntry(entry) {
});
}
-// 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));
+gulp.task('hassio-panel', () => {
+ if (!fs.existsSync('build-temp')) {
+ fs.mkdirSync('build-temp');
+ }
+
+ toProcess.reduce(
+ (p, entry) => p.then(() => vulcanizeEntry(entry)),
+ Promise.resolve());
+
+ return toProcess;
+});
diff --git a/gulp/tasks/rollup.js b/gulp/tasks/rollup.js
new file mode 100644
index 0000000000..f6b449e5ef
--- /dev/null
+++ b/gulp/tasks/rollup.js
@@ -0,0 +1,29 @@
+import gulp from 'gulp';
+import rollupEach from 'gulp-rollup-each';
+import rollupConfig from '../../rollup.config';
+
+gulp.task('run_rollup', () => {
+ return gulp.src([
+ 'js/core.js',
+ 'js/compatibility.js',
+ 'js/editor/editor.js',
+ 'demo_data/demo_data.js',
+ ])
+ .pipe(rollupEach(rollupConfig))
+ .pipe(gulp.dest('build-temp'));
+});
+
+gulp.task('ru_all', ['run_rollup'], () => {
+ gulp.src([
+ 'build-temp/core.js',
+ 'build-temp/compatibility.js',
+ ])
+ .pipe(gulp.dest('build/'));
+});
+
+gulp.task('watch_ru_all', ['ru_all'], () => {
+ gulp.watch([
+ 'js/**/*.js',
+ 'demo_data/**/*.js'
+ ], ['ru_all']);
+});
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index 54e9de899c..f3cb47a7e6 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -1,31 +1,3 @@
-'use strict';
+var requireDir = require('require-dir');
-import gulp from 'gulp';
-import rollupEach from 'gulp-rollup-each';
-import rollupConfig from './rollup.config';
-
-gulp.task('run_rollup', () => {
- return gulp.src([
- 'js/core.js',
- 'js/compatibility.js',
- 'js/editor/editor.js',
- 'demo_data/demo_data.js',
- ])
- .pipe(rollupEach(rollupConfig))
- .pipe(gulp.dest('build-temp'));
-});
-
-gulp.task('ru_all', ['run_rollup'], () => {
- gulp.src([
- 'build-temp/core.js',
- 'build-temp/compatibility.js',
- ])
- .pipe(gulp.dest('build/'));
-});
-
-gulp.task('watch_ru_all', ['ru_all'], () => {
- gulp.watch([
- 'js/**/*.js',
- 'demo_data/**/*.js'
- ], ['ru_all']);
-});
+requireDir('./gulp/tasks/');
diff --git a/package.json b/package.json
index 786227750b..4ed8b33fc1 100644
--- a/package.json
+++ b/package.json
@@ -9,16 +9,11 @@
"scripts": {
"clean": "rm -rf build/* build-temp/*",
"gulp": "gulp",
- "js_dev": "node script/gen-service-worker.js && npm run gulp watch_ru_all",
- "js_dev_demo": "BUILD_DEMO=1 npm run gulp watch_ru_all",
- "js_prod": "BUILD_DEV=0 npm run gulp ru_all",
- "js_demo": "BUILD_DEV=0 BUILD_DEMO=1 npm run gulp ru_all",
- "frontend_html": "node script/vulcanize.js",
- "frontend_prod": "npm run js_prod && npm run frontend_html",
- "frontend_demo": "npm run js_demo && npm run frontend_html",
- "ru_all": "npm run gulp ru_all",
- "watch_ru_all": "npm run gulp watch_ru_all",
- "lint_js": "eslint src panels preact-src --ext js,html",
+ "build": "BUILD_DEV=0 gulp",
+ "build_demo": "BUILD_DEV=0 BUILD_DEMO=1 gulp",
+ "dev": "npm run gulp ru_all",
+ "dev-watch": "npm run gulp watch_ru_all",
+ "lint_js": "eslint src panels js --ext js,html",
"lint_html": "ls -1 src/home-assistant.html panels/**/ha-panel-*.html | xargs polymer lint --input",
"test": "npm run lint_js && npm run lint_html"
},
@@ -33,29 +28,43 @@
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
+ "babel-preset-babili": "^0.1.4",
"babel-preset-es2015": "^6.24.1",
"bower": "^1.8.0",
+ "css-slam": "^1.2.1",
+ "del": "^3.0.0",
"eslint": "^3.19.0",
"eslint-config-airbnb-base": "^11.1.3",
"eslint-plugin-html": "^2.0.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-react": "^7.0.0",
"gulp": "^3.9.1",
+ "gulp-babel": "^6.1.2",
+ "gulp-buble": "^0.8.0",
+ "gulp-filter": "^5.0.0",
+ "gulp-html-minifier": "^0.1.8",
+ "gulp-if": "^2.0.2",
+ "gulp-rename": "^1.2.2",
"gulp-rollup-each": "^1.0.2",
- "html-minifier": "^3.4.3",
- "hydrolysis": "^1.24.1",
- "polymer-cli": "^0.17.0",
+ "gulp-uglify": "^3.0.0",
+ "html-minifier": "^3.5.3",
+ "merge-stream": "^1.0.1",
+ "polymer-build": "^2.0.0",
+ "polymer-bundler": "^3.0.1",
+ "polymer-cli": "^1.3.1",
"polymer-lint": "^0.8.3",
- "rollup": "^0.41.6",
+ "require-dir": "^0.3.2",
+ "rollup": "^0.45.2",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-replace": "^1.1.1",
- "rollup-plugin-uglify": "^1.0.1",
- "rollup-watch": "^3.2.2",
- "sw-precache": "^5.1.0",
- "uglify-js": "^2.8.22",
- "vulcanize": "^1.15.4",
+ "rollup-plugin-uglify": "^2.0.1",
+ "rollup-watch": "^4.3.1",
+ "run-sequence": "^2.1.0",
+ "sw-precache": "^5.2.0",
+ "uglify-js": "^3.0.27",
+ "vulcanize": "^1.16.0",
"web-component-tester": "^5.0.1"
}
}
diff --git a/panels/hassio/todo.md b/panels/hassio/todo.md
deleted file mode 100644
index c534a0e23c..0000000000
--- a/panels/hassio/todo.md
+++ /dev/null
@@ -1,13 +0,0 @@
-Host:
- - Use feature detection
-
-Supervisor:
- - logs
- - toggle beta channel (/options)
-
-Network:
- - show
- - update
-
-Home Assistant:
- - Logs
diff --git a/panels/map/ha-panel-map.html b/panels/map/ha-panel-map.html
index c95ac51d97..c64a43b932 100644
--- a/panels/map/ha-panel-map.html
+++ b/panels/map/ha-panel-map.html
@@ -8,13 +8,12 @@
+
+
-
-