diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2ccbe46
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/node_modules/
diff --git a/add-cache-busting.js b/add-cache-busting.js
new file mode 100644
index 0000000..fe825d4
--- /dev/null
+++ b/add-cache-busting.js
@@ -0,0 +1,38 @@
+'use strict';
+
+const filter = require('gulp-filter');
+const lazypipe = require('lazypipe');
+const rev = require('gulp-rev');
+const revReplace = require('gulp-rev-replace');
+const size = require('gulp-size');
+
+const elementsFilter = filter(['elements/*'], { restore: true });
+const indexFilter = filter(['index.html', 'elements/*'], { restore: true });
+
+/**
+ * The elements.js file is imported from elements.html in the same directly.
+ * The path prefix must therefore be removed.
+ */
+function removePathFromJSfile(filename) {
+ if (filename.endsWith('js')) {
+ return filename.replace('elements/', '');
+ }
+
+ return filename;
+}
+
+/**
+ * Adds cache busting to the element/elements.html file
+ */
+module.exports = lazypipe()
+ .pipe(() => elementsFilter)
+ .pipe(rev)
+ .pipe(() => size({title: 'add-cache-busting'}))
+ .pipe(() => elementsFilter.restore)
+ .pipe(() => indexFilter)
+ .pipe(() => revReplace({
+ modifyUnreved: removePathFromJSfile,
+ modifyReved: removePathFromJSfile,
+ }))
+ .pipe(() => size({title: 'add-cache-busting (replace)'}))
+ .pipe(() => indexFilter.restore);
diff --git a/add-csp-compliance.js b/add-csp-compliance.js
new file mode 100644
index 0000000..45b8239
--- /dev/null
+++ b/add-csp-compliance.js
@@ -0,0 +1,17 @@
+'use strict';
+
+const crisper = require('gulp-crisper');
+const filter = require('gulp-filter');
+const lazypipe = require('lazypipe');
+const size = require('gulp-size');
+
+const elementsFilter = filter(['elements/elements.html'], { restore: true });
+
+/**
+ * Splits the combined Polymer file for CSP compliance
+ */
+module.exports = lazypipe()
+ .pipe(() => elementsFilter)
+ .pipe(() => size({title: 'add-csp-compliance'}))
+ .pipe(() => crisper({scriptInHead:false}))
+ .pipe(() => elementsFilter.restore);
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..3bef5f7
--- /dev/null
+++ b/index.js
@@ -0,0 +1,10 @@
+'use strict';
+
+exports.addCacheBusting = require('./add-cache-busting.js');
+exports.addCspCompliance = require('./add-csp-compliance.js');
+exports.injectCustomElementsEs5Adapter = require('./inject-custom-elements-es5-adapter.js');
+exports.optimizeAssets = require('./optimize-assets.js');
+exports.polymerBuild = require('./polymer-build.js');
+
+// Import tasks
+require('./inline-references-task.js');
diff --git a/inject-custom-elements-es5-adapter.js b/inject-custom-elements-es5-adapter.js
new file mode 100644
index 0000000..f708eca
--- /dev/null
+++ b/inject-custom-elements-es5-adapter.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const lazypipe = require('lazypipe');
+const replace = require('gulp-replace');
+const size = require('gulp-size');
+
+/**
+ * Creates a gulp.pipe() function that replaces the custom-elements-es5-adapter
+ * placeholder with the code loading the adapter.
+ *
+ * @returns
+ */
+function injectCustomElementsES5Adaptor() {
+ return replace(
+ '',
+ ''
+ );
+};
+
+module.exports = lazypipe()
+ .pipe(injectCustomElementsES5Adaptor)
+ .pipe(() => size({title: 'inject-custom-elements-es5-adapter'}));
diff --git a/inline-references-task.js b/inline-references-task.js
new file mode 100644
index 0000000..4aebb79
--- /dev/null
+++ b/inline-references-task.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const gulp = require('gulp');
+const inline = require('gulp-inline');
+const size = require('gulp-size');
+
+/**
+ * Inline referenced files into the index.html
+ *
+ * gulp-inline can't work with stream but needs to have all files written to the file system
+ */
+gulp.task('inline-references', function() {
+ return gulp.src('dist/index.html')
+ .pipe(inline({
+ base: 'dist/',
+ ignore: [
+ 'config.js',
+ 'bower_components/webcomponentsjs/webcomponents-loader.js',
+ 'elements/elements.html',
+ ],
+ }))
+ .pipe(size({title: 'inline:'}))
+ .pipe(gulp.dest('dist'));
+});
diff --git a/optimize-assets.js b/optimize-assets.js
new file mode 100644
index 0000000..301dec6
--- /dev/null
+++ b/optimize-assets.js
@@ -0,0 +1,38 @@
+'use strict';
+
+const babel = require('gulp-babel');
+const gulpIf = require('gulp-if');
+const gulpIgnore = require('gulp-ignore');
+const htmlmin = require('gulp-htmlmin');
+const lazypipe = require('lazypipe');
+const minifyCss = require('gulp-minify-css');
+const uglify = require('gulp-uglify');
+
+const size = require('gulp-size');
+const gutil = require('gulp-util');
+
+function logError(err) {
+ gutil.log(gutil.colors.red('[Error]'), err.toString());
+ this.emit('end');
+}
+
+module.exports = lazypipe()
+ // Convert ES6 -> ES5 code for IE11
+ .pipe(() => gulpIf('*.js', babel({
+ presets: [ [ 'es2015', { modules: false } ] ],
+ compact: true,
+ ignore: 'custom-elements-es5-adapter.js,webcomponents-*.js'
+ })))
+ .pipe(() => size({title: 'Babel ES6->ES5'}))
+
+ // Minify code and HTML
+ .pipe(() => gulpIf('*.js', gulpIgnore.exclude('*custom-elements-es5-adapter*', uglify().on('error', logError))))
+ .pipe(() => size({title: 'Add es5-adapter'}))
+ .pipe(() => gulpIf('*.css', minifyCss()))
+ .pipe(() => size({title: 'Minimize CSS'}))
+ .pipe(() => gulpIf('*.html', htmlmin({
+ removeAttributeQuotes: false,
+ removeEmptyAttributes: false,
+ removeRedundantAttributes: false
+ })))
+ .pipe(() => size({title: 'Minimize HTML'}));
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e7b9fe6
--- /dev/null
+++ b/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "gulp-polymer-build-utils",
+ "version": "1.0.0",
+ "description": "Utils to simplify building Polymer applications",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Collaborne/gulp-polymer-build-utils.git"
+ },
+ "author": "Ronny Roeller (Collaborne)",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/Collaborne/gulp-polymer-build-utils/issues"
+ },
+ "homepage": "https://github.com/Collaborne/gulp-polymer-build-utils#readme",
+ "devDependencies": {
+ "gulp-babel": "^6.1.2",
+ "gulp-crisper": "^1.1.0",
+ "gulp-filter": "^5.0.1",
+ "gulp-htmlmin": "^3.0.0",
+ "gulp-if": "^2.0.2",
+ "gulp-ignore": "^2.0.2",
+ "gulp-inline": "^0.1.3",
+ "gulp-minify-css": "^1.2.4",
+ "gulp-replace": "^0.6.1",
+ "gulp-rev": "^8.0.0",
+ "gulp-rev-replace": "^0.4.3",
+ "gulp-size": "^2.1.0",
+ "gulp-uglify": "^3.0.0",
+ "gulp-util": "^3.0.8",
+ "lazypipe": "^1.0.1",
+ "merge-stream": "^1.0.1",
+ "polymer-build": "^2.0.0"
+ }
+}
diff --git a/polymer-build.js b/polymer-build.js
new file mode 100644
index 0000000..ec89183
--- /dev/null
+++ b/polymer-build.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const build = require('polymer-build');
+const merge = require('merge-stream');
+const size = require('gulp-size');
+
+/**
+ * Executes the polymer-build, which a.o. vulcanizes and minifies the HTML
+ *
+ * @returns
+ */
+function polymerBuild(config) {
+ const project = new build.PolymerProject(config);
+
+ const bundler = project.bundler({
+ // XXX: sourcemaps makes V8 run out of memory
+ sourcemaps: false,
+ stripComments: true,
+ }).on('error', e => console.error(e));
+
+ return merge(project.sources(), project.dependencies())
+ .pipe(bundler)
+ .pipe(size({title: 'polymer-bundler'}));
+};
+
+module.exports = polymerBuild;