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:
Paulus Schoutsen 2017-08-02 21:31:04 -07:00 committed by GitHub
parent deb4f9e63f
commit 512b07963b
19 changed files with 2562 additions and 1064 deletions

View File

@ -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

7
gulp/config.js Normal file
View File

@ -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'),
};

107
gulp/tasks/build.js Normal file
View File

@ -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/'));
});

6
gulp/tasks/clean.js Normal file
View File

@ -0,0 +1,6 @@
import del from 'del';
import gulp from 'gulp';
gulp.task('clean', () => {
return del(['build', 'build-temp']);
});

9
gulp/tasks/default.js Normal file
View File

@ -0,0 +1,9 @@
import gulp from 'gulp';
import runSequence from 'run-sequence';
gulp.task('default', () => {
return runSequence.use(gulp)(
'clean',
'build',
);
});

110
gulp/tasks/gen-service-worker.js Executable file
View File

@ -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;
});

View File

@ -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;
});

29
gulp/tasks/rollup.js Normal file
View File

@ -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']);
});

View File

@ -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/');

View File

@ -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"
}
}

View File

@ -1,13 +0,0 @@
Host:
- Use feature detection
Supervisor:
- logs
- toggle beta channel (/options)
Network:
- show
- update
Home Assistant:
- Logs

View File

@ -8,13 +8,12 @@
<link rel="import" href="./ha-entity-marker.html">
<dom-module id="ha-panel-map">
<!-- this is somehow magically working with bundling, leave it for now -->
<link
rel="stylesheet"
href="../../bower_components/leaflet/dist/leaflet.css"
/>
<template>
<link
id='stylesheet'
rel="stylesheet"
href="../../bower_components/leaflet/dist/leaflet.css"
/>
<style include="ha-style">
#map {
height: calc(100% - 64px);
@ -58,7 +57,11 @@ Polymer({
// The link tag is resolved relative to the _current_ url instead of the
// source HTML file. Vulcanized this is taken care off. Fix it in dev.
if (window.HASS_DEV) {
this.$.stylesheet.href = '/static/home-assistant-polymer/bower_components/leaflet/dist/leaflet.css';
var style = document.createElement('link');
style.setAttribute(
'href', '/static/home-assistant-polymer/bower_components/leaflet/dist/leaflet.css');
style.setAttribute('rel', 'stylesheet');
this.shadowRoot.appendChild(style);
}
var map = this._map = window.L.map(this.$.map);
map.setView([51.505, -0.09], 13);

View File

@ -1,10 +1,12 @@
{
"entrypoint": "src/entrypoint.html",
"shell": "src/home-assistant.html",
"fragments": [
"panels/automation/ha-panel-automation.html",
"panels/config/ha-panel-config.html",
"panels/dev-event/ha-panel-dev-event.html",
"panels/dev-info/ha-panel-dev-info.html",
"panels/dev-mqtt/ha-panel-dev-mqtt.html",
"panels/dev-service/ha-panel-dev-service.html",
"panels/dev-state/ha-panel-dev-state.html",
"panels/dev-template/ha-panel-dev-template.html",
@ -19,11 +21,7 @@
],
"sources": [
"src/**/*",
"panels/**/*",
"bower.json"
],
"extraDependencies": [
"bower_components/webcomponentsjs/*"
"panels/**/*"
],
"lint": {
"rules": ["polymer-2-hybrid"]

View File

@ -9,22 +9,22 @@ const DEMO = !!JSON.parse(process.env.BUILD_DEMO || 'false');
const plugins = [
babel({
"babelrc": false,
"presets": [
babelrc: false,
presets: [
[
"es2015",
'es2015',
{
"modules": false
modules: false
}
]
],
"plugins": [
"external-helpers",
"transform-object-rest-spread",
plugins: [
'external-helpers',
'transform-object-rest-spread',
[
"transform-react-jsx",
'transform-react-jsx',
{
"pragma":"h"
pragma: 'h'
}
],
]

View File

@ -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));

View File

@ -6,7 +6,7 @@
<template>
<style>
:host {
@apply(--paper-font-body1)
@apply(--paper-font-body1);
}
a {
color: var(--dark-primary-color);

1
src/entrypoint.html Normal file
View File

@ -0,0 +1 @@
<!--unused, but required for polymer-build-->

3052
yarn.lock

File diff suppressed because it is too large Load Diff