Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add build using polymer-build #344

Merged
merged 13 commits into from
Aug 3, 2017
Merged
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions gulp/config.js
Original file line number Diff line number Diff line change
@@ -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 changes: 107 additions & 0 deletions gulp/tasks/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { gulp as cssSlam } from 'css-slam';
import gulp from 'gulp';
import babel from 'gulp-babel';
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$/, babel({
presets: ['babili'], // 'es2015'
// plugins: ['external-helpers']
})))
.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.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 changes: 6 additions & 0 deletions gulp/tasks/clean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import del from 'del';
import gulp from 'gulp';

gulp.task('clean', () => {
return del(['build', 'build-temp']);
});
9 changes: 9 additions & 0 deletions gulp/tasks/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import gulp from 'gulp';
import runSequence from 'run-sequence';

gulp.task('default', () => {
return runSequence.use(gulp)(
'clean',
'build',
);
});
109 changes: 109 additions & 0 deletions gulp/tasks/gen-service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
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
*/
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;
});
66 changes: 17 additions & 49 deletions script/vulcanize.js → gulp/tasks/hassio-panel.js
Original file line number Diff line number Diff line change
@@ -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: [/\$=/],
Expand All @@ -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.
{
Expand All @@ -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);
Expand All @@ -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 changes: 29 additions & 0 deletions gulp/tasks/rollup.js
Original file line number Diff line number Diff line change
@@ -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']);
});
File renamed without changes.