Use webpack plugin to generate HTML (#1521)

* Use webpack plugin to generate HTML

* Generate index.html

* Remove unused packages
This commit is contained in:
Paulus Schoutsen 2018-07-26 09:30:03 +02:00 committed by GitHub
parent e458cf1388
commit 6cbca6d88a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 235 additions and 765 deletions

View File

@ -1,10 +0,0 @@
/**
* UglifyJS gulp plugin that takes in a boolean to use ES or JS minification.
*/
const composer = require('gulp-uglify/composer');
const uglifyjs = require('uglify-js');
const uglifyes = require('uglify-es');
module.exports = function gulpUglify(es6, options) {
return composer(es6 ? uglifyes : uglifyjs, console)(options);
};

View File

@ -1,8 +0,0 @@
const fs = require('fs');
const crypto = require('crypto');
module.exports = function md5(filename) {
return crypto.createHash('md5')
.update(fs.readFileSync(filename)).digest('hex');
};

View File

@ -1,32 +0,0 @@
const gulpif = require('gulp-if');
const babel = require('gulp-babel');
const { gulp: cssSlam } = require('css-slam');
const htmlMinifier = require('gulp-html-minifier');
const { HtmlSplitter } = require('polymer-build');
const pump = require('pump');
const uglify = require('./gulp-uglify.js');
module.exports.minifyStream = function (stream, es6) {
const sourcesHtmlSplitter = new HtmlSplitter();
return pump([
stream,
sourcesHtmlSplitter.split(),
gulpif(!es6, gulpif(/[^app]\.js$/, babel({
sourceType: 'script',
presets: [
['es2015', { modules: false }]
]
}))),
gulpif(/\.js$/, uglify(es6, { sourceMap: false })),
gulpif(/\.css$/, cssSlam()),
gulpif(/\.html$/, cssSlam()),
gulpif(/\.html$/, htmlMinifier({
collapseWhitespace: true,
removeComments: true
})),
sourcesHtmlSplitter.rejoin(),
], (error) => {
if (error) console.log(error);
});
};

View File

@ -1,51 +0,0 @@
const gulp = require('gulp');
const path = require('path');
const replace = require('gulp-batch-replace');
const rename = require('gulp-rename');
const md5 = require('../common/md5');
const url = require('url');
const config = require('../config');
const minifyStream = require('../common/transform').minifyStream;
const buildReplaces = {
'/frontend_latest/authorize.js': 'authorize.js',
};
async function buildAuth(es6) {
const targetPath = es6 ? config.output : config.output_es5;
const targetUrl = es6 ? '/frontend_latest/' : '/frontend_es5/';
const frontendPath = es6 ? 'frontend_latest' : 'frontend_es5';
const toReplace = [
['/home-assistant-polymer/hass_frontend/authorize.js', `/${frontendPath}/authorize.js`],
];
if (!es6) {
const compatibilityPath = `/frontend_es5/compatibility-${md5(path.resolve(config.output_es5, 'compatibility.js'))}.js`;
const es5Extra = `
<script src='${compatibilityPath}'></script>
<script src='/static/custom-elements-es5-adapter.js'></script>
`;
toReplace.push([
'<!--EXTRA_SCRIPTS-->', es5Extra
]);
}
for (const [replaceSearch, filename] of Object.entries(buildReplaces)) {
const parsed = path.parse(filename);
const hash = md5(path.resolve(targetPath, filename));
toReplace.push([
replaceSearch,
url.resolve(targetUrl, `${parsed.name}-${hash}${parsed.ext}`)]);
}
const stream = gulp.src(path.resolve(config.polymer_dir, 'src/authorize.html'))
.pipe(replace(toReplace));
return minifyStream(stream, /* es6= */ es6)
.pipe(rename('authorize.html'))
.pipe(gulp.dest(es6 ? config.output : config.output_es5));
}
gulp.task('gen-authorize-html-es5', () => buildAuth(/* es6= */ false));
gulp.task('gen-authorize-html', () => buildAuth(/* es6= */ true));

View File

@ -98,7 +98,6 @@ function genHassIcons() {
gulp.task('gen-icons-mdi', () => genMDIIcons());
gulp.task('gen-icons-hass', () => genHassIcons());
gulp.task('gen-icons-hassio', () => genHassIcons());
gulp.task('gen-icons', ['gen-icons-hass', 'gen-icons-mdi'], () => {});
module.exports = {

View File

@ -1,54 +0,0 @@
const gulp = require('gulp');
const replace = require('gulp-batch-replace');
const path = require('path');
const url = require('url');
const config = require('../config');
const md5 = require('../common/md5');
const { minifyStream } = require('../common/transform');
const buildReplaces = {
'/frontend_latest/core.js': 'core.js',
'/frontend_latest/app.js': 'app.js',
};
function generateIndex(es6) {
const targetPath = es6 ? config.output : config.output_es5;
const targetUrl = es6 ? '/frontend_latest/' : '/frontend_es5/';
const toReplace = [
// Needs to look like a color during CSS minifiaction
['{{ theme_color }}', '#THEME'],
['/frontend_latest/hass-icons.js',
`/frontend_latest/hass-icons-${md5(path.resolve(config.output, 'hass-icons.js'))}.js`],
];
if (!es6) {
const compatibilityPath = `/frontend_es5/compatibility-${md5(path.resolve(config.output_es5, 'compatibility.js'))}.js`;
const es5Extra = `
<script src='${compatibilityPath}'></script>
<script src='/static/custom-elements-es5-adapter.js'></script>
`;
toReplace.push([
'<!--EXTRA_SCRIPTS-->', es5Extra
]);
}
for (const [replaceSearch, filename] of Object.entries(buildReplaces)) {
const parsed = path.parse(filename);
const hash = md5(path.resolve(targetPath, filename));
toReplace.push([
replaceSearch,
url.resolve(targetUrl, `${parsed.name}-${hash}${parsed.ext}`)]);
}
const stream = gulp.src(path.resolve(config.polymer_dir, 'index.html'))
.pipe(replace(toReplace));
return minifyStream(stream, es6)
.pipe(replace([['#THEME', '{{ theme_color }}']]))
.pipe(gulp.dest(targetPath));
}
gulp.task('gen-index-html-es5', generateIndex.bind(null, /* es6= */ false));
gulp.task('gen-index-html', generateIndex.bind(null, /* es6= */ true));

View File

@ -1,51 +0,0 @@
const gulp = require('gulp');
const path = require('path');
const replace = require('gulp-batch-replace');
const rename = require('gulp-rename');
const md5 = require('../common/md5');
const url = require('url');
const config = require('../config');
const minifyStream = require('../common/transform').minifyStream;
const buildReplaces = {
'/frontend_latest/onboarding.js': 'onboarding.js',
};
async function buildOnboarding(es6) {
const targetPath = es6 ? config.output : config.output_es5;
const targetUrl = es6 ? '/frontend_latest/' : '/frontend_es5/';
const frontendPath = es6 ? 'frontend_latest' : 'frontend_es5';
const toReplace = [
['/home-assistant-polymer/hass_frontend/onboarding.js', `/${frontendPath}/onboarding.js`],
];
if (es6) {
toReplace.push(['<!--EXTRA_SCRIPTS-->', '']);
} else {
const compatibilityPath = `/frontend_es5/compatibility-${md5(path.resolve(config.output_es5, 'compatibility.js'))}.js`;
const es5Extra = `
<script src='${compatibilityPath}'></script>
<script src='/static/custom-elements-es5-adapter.js'></script>
`;
toReplace.push(['<!--EXTRA_SCRIPTS-->', es5Extra]);
}
for (const [replaceSearch, filename] of Object.entries(buildReplaces)) {
const parsed = path.parse(filename);
const hash = md5(path.resolve(targetPath, filename));
toReplace.push([
replaceSearch,
url.resolve(targetUrl, `${parsed.name}-${hash}${parsed.ext}`)]);
}
const stream = gulp.src(path.resolve(config.polymer_dir, 'src/onboarding.html'))
.pipe(replace(toReplace));
return minifyStream(stream, /* es6= */ es6)
.pipe(rename('onboarding.html'))
.pipe(gulp.dest(es6 ? config.output : config.output_es5));
}
gulp.task('gen-onboarding-html-es5', () => buildOnboarding(/* es6= */ false));
gulp.task('gen-onboarding-html', () => buildOnboarding(/* es6= */ true));

View File

@ -95,40 +95,31 @@
"chai": "^4.1.2",
"compression-webpack-plugin": "^1.1.11",
"copy-webpack-plugin": "^4.5.1",
"css-slam": "^2.1.2",
"del": "^3.0.0",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-react": "^7.9.1",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.1",
"gulp-batch-replace": "^0.0.0",
"gulp-foreach": "^0.1.0",
"gulp-hash": "^4.2.2",
"gulp-html-minifier": "^0.1.8",
"gulp-if": "^2.0.2",
"gulp-insert": "^0.5.0",
"gulp-json-transform": "^0.4.5",
"gulp-jsonminify": "^1.1.0",
"gulp-merge-json": "^1.3.1",
"gulp-rename": "^1.3.0",
"gulp-uglify": "^3.0.0",
"html-loader": "^0.5.5",
"html-minifier": "^3.5.16",
"html-webpack-plugin": "^3.2.0",
"merge-stream": "^1.0.1",
"mocha": "^5.2.0",
"parse5": "^5.0.0",
"polymer-analyzer": "^3.0.1",
"polymer-build": "^3.0.2",
"polymer-bundler": "^4.0.1",
"polymer-cli": "^1.7.4",
"pump": "^3.0.0",
"reify": "^0.16.2",
"require-dir": "^1.0.0",
"sinon": "^6.0.0",
"uglify-es": "^3.3.9",
"uglify-js": "^3.4.1",
"uglifyjs-webpack-plugin": "^1.2.6",
"wct-browser-legacy": "^1.0.1",
"web-component-tester": "^6.7.0",

View File

@ -25,9 +25,3 @@ echo "VERSION = '`git rev-parse HEAD`'" >> $OUTPUT_DIR/__init__.py
echo "CREATED_AT = `date +%s`" >> $OUTPUT_DIR/__init__.py
echo "VERSION = '`git rev-parse HEAD`'" >> $OUTPUT_DIR_ES5/__init__.py
echo "CREATED_AT = `date +%s`" >> $OUTPUT_DIR_ES5/__init__.py
# Generate index.htmls with the MD5 hash of the builds
./node_modules/.bin/gulp \
gen-index-html gen-index-html-es5 \
gen-authorize-html gen-authorize-html-es5 \
gen-onboarding-html gen-onboarding-html-es5

View File

@ -14,7 +14,4 @@ mkdir $OUTPUT_DIR $OUTPUT_DIR_ES5
cp -r public/__init__.py $OUTPUT_DIR_ES5/
./node_modules/.bin/gulp build-translations gen-icons
cp src/authorize.html $OUTPUT_DIR
cp src/onboarding.html $OUTPUT_DIR
./node_modules/.bin/webpack --watch --progress

View File

@ -44,12 +44,6 @@ import(/* webpackChunkName: "notification-manager" */ '../managers/notification-
setPassiveTouchGestures(true);
/* LastPass createElement workaround. See #428 */
document.createElement = Document.prototype.createElement;
window.removeInitMsg = function () {
var initMsg = document.getElementById('ha-init-skeleton');
if (initMsg) {
initMsg.parentElement.removeChild(initMsg);
}
};
class HomeAssistant extends LocalizeMixin(PolymerElement) {
static get template() {

View File

@ -14,7 +14,7 @@ window.loadES5Adapter = () => {
if (!es5Loaded) {
es5Loaded = Promise.all([
loadJS(`${__PUBLIC_PATH__}custom-elements-es5-adapter.js`).catch(),
loadJS(`${__PUBLIC_PATH__}compatibility.js`),
import(/* webpackChunkName: "compat" */ './compatibility.js'),
]);
}
return es5Loaded;

View File

@ -6,13 +6,12 @@
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
<title>Home Assistant</title>
<!--EXTRA_SCRIPTS-->
<style>
body {
font-family: 'Roboto', 'Noto', sans-serif;
font-weight: 400;
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
@ -20,7 +19,11 @@
</style>
</head>
<body>
<ha-authorize>Loading</ha-authorize>
<<%= tag %>>Loading</<%= tag %>>
<% if (!latestBuild) { %>
<script src="/static/custom-elements-es5-adapter.js"></script>
<script src="<%= compatibility %>"></script>
<% } %>
<script>
var webComponentsSupported = (
'customElements' in window &&
@ -31,7 +34,7 @@
document.write(e.outerHTML);
}
</script>
<script src="/frontend_latest/authorize.js"></script>
<script src='/frontend_latest/hass-icons.js' async></script>
<script src="<%= entrypoint %>"></script>
<script src='/static/hass-icons.js' async></script>
</body>
</html>

View File

@ -9,7 +9,7 @@
<link rel='apple-touch-icon' sizes='180x180'
href='/static/icons/favicon-apple-180x180.png'>
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#3fbbf4">
<link rel='preload' href='/frontend_latest/core.js' as='script'/>
<link rel='preload' href='<%= corejs %>' as='script'/>
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
<meta name='apple-mobile-web-app-capable' content='yes'>
@ -24,10 +24,10 @@
<meta name='theme-color' content='{{ theme_color }}'>
<style>
body {
font-family: 'Roboto', 'Noto', sans-serif;
font-weight: 400;
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-weight: 400;
margin: 0;
padding: 0;
height: 100vh;
@ -36,30 +36,11 @@
#ha-init-skeleton::before {
display: block;
content: "";
height: 64px;
height: 112px;
background-color: {{ theme_color }};
}
#ha-init-skeleton .message {
transition: font-size 2s;
font-size: 0;
padding: 24px;
}
#ha-init-skeleton.error .message {
font-size: 16px;
}
#ha-init-skeleton a {
color: {{ theme_color }};
text-decoration: none;
font-weight: bold;
}
</style>
<script>
function initError() {
document.getElementById('ha-init-skeleton').classList.add('error');
};
window.noAuth = '{{ no_auth }}';
window.useOAuth = '{{ use_oauth }}'
window.Polymer = {
@ -72,13 +53,13 @@
</script>
</head>
<body>
<div id='ha-init-skeleton'>
<div class='message'>
Home Assistant had trouble<br>connecting to the server.<br><br>
<a href='/'>TRY AGAIN</a>
</div>
</div>
<home-assistant></home-assistant>
<home-assistant>
<div id='ha-init-skeleton'></div>
</home-assistant>
<% if (!latestBuild) { %>
<script src="/static/custom-elements-es5-adapter.js"></script>
<script src="<%= compatibility %>"></script>
<% } %>
<script>
var webComponentsSupported = (
'customElements' in window &&
@ -91,9 +72,8 @@
}());
}
</script>
<!--EXTRA_SCRIPTS-->
<script src='/frontend_latest/core.js'></script>
<script src='/frontend_latest/app.js'></script>
<script src='<%= corejs %>'></script>
<script src='<%= appjs %>'></script>
<script src='/frontend_latest/hass-icons.js' async></script>
{% for extra_url in extra_urls -%}
<link rel='import' href='{{ extra_url }}' async>

View File

@ -110,7 +110,6 @@ class HomeAssistantMain extends NavigateMixin(EventsMixin(PolymerElement)) {
connectedCallback() {
super.connectedCallback();
window.removeInitMsg();
if (document.location.pathname === '/') {
this.navigate(`/${localStorage.defaultPage || 'states'}`, true);
}

View File

@ -116,7 +116,6 @@ class LoginForm extends LocalizeMixin(PolymerElement) {
connectedCallback() {
super.connectedCallback();
window.removeInitMsg();
}
computeLoadingMsg(isValidating) {

View File

@ -1,37 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name='viewport' content='width=device-width, user-scalable=no'>
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
<title>Home Assistant</title>
<!--EXTRA_SCRIPTS-->
<style>
body {
font-family: 'Roboto', 'Noto', sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
margin: 0;
padding: 0;
height: 100vh;
}
</style>
</head>
<body>
<ha-onboarding>Loading</ha-onboarding>
<script>
var webComponentsSupported = (
'customElements' in window &&
'content' in document.createElement('template'));
if (!webComponentsSupported) {
var e = document.createElement('script');
e.src = '/static/webcomponents-bundle.js';
document.write(e.outerHTML);
}
</script>
<script src="/frontend_latest/onboarding.js"></script>
<script src='/frontend_latest/hass-icons.js' async></script>
</body>
</html>

View File

@ -5,6 +5,7 @@ const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const CompressionPlugin = require("compression-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const translationMetadata = require('./build-translations/translationMetadata.json');
const version = fs.readFileSync('setup.py', 'utf8').match(/\d{8}[^']*/);
@ -13,6 +14,22 @@ if (!version) {
}
const VERSION = version[0];
const generateJSPage = (entrypoint, latestBuild) => {
return new HtmlWebpackPlugin({
inject: false,
template: './src/html/extra_page.html.template',
// Default templateParameterGenerator code
// https://github.com/jantimon/html-webpack-plugin/blob/master/index.js#L719
templateParameters: (compilation, assets, option) => ({
latestBuild,
tag: `ha-${entrypoint}`,
compatibility: assets.chunks.compatibility.entry,
entrypoint: assets.chunks[entrypoint].entry,
}),
filename: `${entrypoint}.html`,
});
}
function createConfig(isProdBuild, latestBuild) {
let buildPath = latestBuild ? 'hass_frontend/' : 'hass_frontend_es5/';
const publicPath = latestBuild ? '/frontend_latest/' : '/frontend_es5/';
@ -163,9 +180,35 @@ function createConfig(isProdBuild, latestBuild) {
],
}
}),
new HtmlWebpackPlugin({
inject: false,
template: './src/html/index.html.template',
// Default templateParameterGenerator code
// https://github.com/jantimon/html-webpack-plugin/blob/master/index.js#L719
templateParameters: (compilation, assets, option) => ({
latestBuild,
compatibility: assets.chunks.compatibility.entry,
appjs: assets.chunks.app.entry,
corejs: assets.chunks.core.entry,
}),
filename: `index.html`,
}),
generateJSPage('onboarding', latestBuild),
generateJSPage('authorize', latestBuild),
].filter(Boolean),
output: {
filename: '[name].js',
filename: ({ chunk }) => {
const dontHash = new Set([
// Because we don't include it in ES5 build
// And so can't reference it there
'hass-icons',
// This is loaded from service-worker-bootstrap.js
// which is processed by Workbox, not Webpack
'service-worker-hass',
]);
if (!isProdBuild || dontHash.has(chunk.name)) return `${chunk.name}.js`;
return `${chunk.name}-${chunk.hash.substr(0, 8)}.js`;
},
chunkFilename: chunkFilename,
path: path.resolve(__dirname, buildPath),
publicPath,

616
yarn.lock

File diff suppressed because it is too large Load Diff