Compile using Gulp handlebars partials to single HTML file - javascript

I would like to create a gulpfile to compile from partials into a single HTML file. This is my file structure:
| css/
| - main.css
| gulpfile.js
| less/
| - main.less
| index.html
| templates/
| - index.handlebars
| - partials /
| -- header.hbs
| -- footer.hbs
My gulpfile.js looks like this:
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var hbsAll = require('gulp-handlebars-all');
var handlebars = require('handlebars');
var gulpHandlebars = require('gulp-compile-handlebars')(handlebars); //default to require('handlebars') if not provided
var rename = require('gulp-rename');
var less = require('gulp-less-sourcemap');
var autoprefixer = require('gulp-autoprefixer');
var watch = require('gulp-watch');
handlebars.registerPartial('header', '{{header}}'),
handlebars.registerPartial('footer', '{{footer}}')
gulp.task('default', function () {
options = {
partialsDirectory : ['./templates/partials']
}
return gulp.src('templates/index.handlebars')
.pipe(gulpHandlebars( options))
.pipe(rename('index.html'))
.pipe(gulp.dest(''));
});
gulp.task('hbsToHTML', function() {
gulp.src('templates/*.hbs')
.pipe(hbsAll('html', {
context: {foo: 'bar'},
partials: ['templates/partials/*.hbs'],
}))
.pipe(gulp.dest('templates'));
});
gulp.task('less', function () {
gulp.src('./less/*.less')
.pipe(less({
sourceMap: {
sourceMapRootpath: '../less' // Optional absolute or relative path to your LESS files
}
}))
.pipe(gulp.dest('./css'));
});
gulp.task('prefix', function () {
return gulp.src('css/main.css')
.pipe(autoprefixer({
browsers: ['last 2 versions'],
cascade: false
}))
.pipe(gulp.dest('./css'));
});
gulp.task('default', ['hbsToHTML', 'less', 'prefix']);
and my index.handlebars file looks like this:
{{> header}}
<p>Hello </p>
<p>there </p>
{{> footer}}
So, everthing else looks fine, but I can't get the hbsToHTML function to work. Any help is welcome! I know there could be more than a few bugs :(

There's an excellent gulp plugin which does exactly this. You can find it on npm here: https://www.npmjs.com/package/gulp-compile-handlebars
Set the path to your partials using the batch option.
Set the path to your templates using gulp.src
and set the destination using gulp.dest
The example code on the npm page demonstrates this, though in your case you will not need the partials option and you may want to set the ignorePartials to false so that it picks up all the files you have in your partials directory

I figured it out btw and uploaded it to git. Check it.
https://github.com/nikdelig/hbsToHTML
gulp.task('hbsToHTML', function() {
gulp.src('templates/*.hbs')
.pipe(hbsAll('html', {
context: {foo: 'bar'},
partials: ['templates/partials/**/*.hbs'],}))
.pipe(rename('index.html'))
.pipe(htmlmin({collapseWhitespace: true}))
.pipe(gulp.dest(''));
});

Related

Gulp not creating CSS file in destination

I am trying to set the Gulp tasks for styles with postcss, CSS modules etc., but for now only the watch task for the main HTML file is working, and it is not even creating the main CSS file nor the temp folder so I get the error in the console that it can not find the styles.css. It's driving me crazy for days but just cannot get to the bottom of it. Here is my file structure, pretty basic:
application -> views -> index.html
public -> gulp -> tasks -> styles.js, watch.js
-> styles (styles.css inside, base and modules for CSS subfolders)
And here are the styles and watch tasks:
var gulp = require('gulp'),
watch = require('gulp-watch'),
browserSync = require('browser-sync').create();
gulp.task('watch', function() {
browserSync.init({
notify: false,
server: {
baseDir: "../application/views"
}
});
watch('../application/views/index.html', function(){
browserSync.reload();
});
watch('../application/public/styles/**/*.css', function(){
gulp.start('cssInject');
});
});
gulp.task('cssInject', ['styles'], function(){
return gulp.src('../application/temp/styles')
.pipe(browserSync.stream());
});
Styles:
var gulp = require('gulp'),
postcss = require('gulp-postcss'),
autoprefixer = require('autoprefixer'),
cssvars = require('postcss-simple-vars'),
nested = require('postcss-nested'),
cssImport = require('postcss-import'),
mixins = require('postcss-mixins'),
hexrgba = require('postcss-hexrgba');
gulp.task('styles', function(){
return gulp.src('../application/public/styles/styles.css')
.pipe(postcss([cssImport, mixins, cssvars, nested, hexrgba, autoprefixer]))
.on('error', function(errorInfo){
console.log(errorInfo.toString());
this.emit('end');
})
.pipe(gulp.dest('../application/temp/styles'));
});
If someone can give a piece of advice I would be really grateful.

gulp-browserify output different than it should be

I am using gulp with browserify to concatenate, babelify, and browserify several js libraries. This is in my gulpfile.js:
gulp.task('scripts', function () {
var b = browserify({
entries: ['src/scripts/modernizr.js', 'src/scripts/main.js'],
debug: true
}).transform(babelify, { presets: ["latest"] });
return b.bundle()
.pipe(source('main.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
// Add transformation tasks to the pipeline here.
// .pipe(uglify())
.on('error', gutil.log)
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('build/js/'));
});
And my package.json:
"browserify": {
"transform": [
[
"babelify",
{
"presets": [
"es2015"
]
}
]
]
}
In my main.js this is how I am including the libraries:
var $ = window.jquery;
import tinymce from 'tinymce/tinymce';
import 'tinymce/themes/modern/theme';
// import spica from 'spica';
// import localsocket from 'localsocket';
var spica = require('spica');
var localsocket = require('localsocket');
import Pjax from 'pjax-api';
However I’m getting the following error in the console:
pjax-api.js:8Uncaught Error: Cannot find module 'spica'
at s (http://localhost:8000/static/articles/build/js/main.js:1:148)
at http://localhost:8000/static/articles/build/js/main.js:1:305
at s (http://localhost:8000/static/articles/build/js/main.js:12699:28)
at http://localhost:8000/static/articles/build/js/main.js:12708:24
at Object.r.42.../../../lib/dom (http://localhost:8000/static/articles/build/js/main.js:14446:27)
at s (http://localhost:8000/static/articles/build/js/main.js:12706:21)
at http://localhost:8000/static/articles/build/js/main.js:12708:24
at Object.r.41../gui (http://localhost:8000/static/articles/build/js/main.js:14429:25)
at s (http://localhost:8000/static/articles/build/js/main.js:12706:21)
at http://localhost:8000/static/articles/build/js/main.js:12708:24
at Object.r.4../layer/interface/service/api (http://localhost:8000/static/articles/build/js/main.js:12738:25)
at s (http://localhost:8000/static/articles/build/js/main.js:12706:21)
at http://localhost:8000/static/articles/build/js/main.js:12708:24
at Object.r.pjax-api../src/export (http://localhost:8000/static/articles/build/js/main.js:15017:22)
at s (http://localhost:8000/static/articles/build/js/main.js:12706:21)
at e (http://localhost:8000/static/articles/build/js/main.js:12715:9)
at Object.3 (http://localhost:8000/static/articles/build/js/main.js:12717:2)
at s (http://localhost:8000/static/articles/build/js/main.js:1:254)
at http://localhost:8000/static/articles/build/js/main.js:1:305
at Object.8.jquery (http://localhost:8000/static/articles/build/js/main.js:67062:16)
at s (http://localhost:8000/static/articles/build/js/main.js:1:254)
at e (http://localhost:8000/static/articles/build/js/main.js:1:425)
at http://localhost:8000/static/articles/build/js/main.js:1:443
This is my directory structure:
articles
|--gulpfile.js
|--package_json
|--build
| |--img
| |--css
| |--js
| | |--main.js
|--src
| |--img
| |--styles
| |--scripts
| | |--main.js
|--node_modules
| |--spica
| |--localsocket
| |--pjax-api
| |--etc.
What is going wrong?
The full output of main.js is too big to paste, but here it is in a gist. And here is the rest of my code in another gist.
Do you have the spica-package installed? If not, try this:
npm install --save-dev spica
if your 'spica' is present in the same directory where main.js is present then its fine or else
You have to specify
like this ,
var spica = require(dir_path/spica.js)
share your directory structure so i can provide you exact syntax.

How to process *.js and *.min.js bower files in gulp

In my gulp file to inject bower components I have this bad style code duplication. But I do not have any ideas how to get rid of it.
Generally speaking we cannot say just bower_components/**/*.js because we don't want to import all files, plus for production we want to import just .min files. Again. I cannot guaranty that every package I use have .js and .min.js files. So just *.js and *.min.js may not work.
gulp.task('inject', () => {
let sources = gulp.src([
// jquery
'public/bower_components/jquery/dist/jquery.js',
// bootstrap
'public/bower_components/bootstrap/dist/js/bootstrap.js',
'public/bower_components/bootstrap/dist/css/bootstrap.css',
// angular
'public/bower_components/angular/angular.js',
'public/bower_components/angular/angular-csp.css',
// angular route
'public/bower_components/angular-route/angular-route.js',
],{read: false});
let min_sources = gulp.src([
// jquery
'public/bower_components/jquery/dist/jquery.min.js',
// bootstrap
'public/bower_components/bootstrap/dist/js/bootstrap.min.js',
'public/bower_components/bootstrap/dist/css/bootstrap.min.css',
// angular
'public/bower_components/angular/angular.min.js',
'public/bower_components/angular/angular-csp.css',
// angular route
'public/bower_components/angular-route/angular-route.min.js',
],{read: false});
return gulp.src('public/build/index.html')
.pipe(gulpif(!argv.production, inject(sources, {relative: true})))
.pipe(gulpif(argv.production, inject(min_sources, {relative: true})))
.pipe(gulp.dest('public/build/'));
});
But this code duplication isn't solution. I think. How can I improve this part, besides to move this two array in bower.js file ?
Maybe you can use config.js. Use var config = require('../config'); to read the variables in config.js so you can separate file paths and task.
If you want to separate .js and .min.js , you can use
'src' : [
'src/**/*.js',
'!src/**/*.min.js',
]
For example below I concat .min.js / .js files and uglify it, and also concate .css files and use cssnano() to compress it. In the end vendor task will output vendor.bundle.js and vendor.bundle.css
config.js:
'use strict';
module.exports = {
'vendor': {
'scripts': {
'src': [
'bower_components/jquery/dist/jquery.min.js',
'bower_components/lodash/dist/lodash.min.js',
// Moment
'bower_components/moment/min/moment.min.js',
'bower_components/moment/locale/zh-tw.js',
// Ionic & Angular
'bower_components/ionic/js/ionic.bundle.min.js',
'bower_components/ngCordova/dist/ng-cordova.min.js'
// ...
],
'dest': 'www/js',
'output': 'vendor.bundle.js'
},
'styles': {
'src': [
// Mobiscroll
'bower_external/mobiscroll/css/mobiscroll.custom-2.17.0.min.css',
],
'dest': 'www/css',
'output': 'vendor.bundle.css'
}
}
}
}
vendor.js
'use strict';
var config = require('../config');
var gulp = require('gulp');
var gulpif = require('gulp-if');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var postcss = require('gulp-postcss');
var cssnano = require('cssnano');
var handleErrors = require('../util/handleErrors');
var browserSync = require('browser-sync');
var pkg = require('../../package.json');
gulp.task('vendorScripts', function () {
return gulp.src(config.vendor.scripts.src)
.pipe(concat(config.vendor.scripts.output))
.pipe(uglify())
.pipe(gulp.dest(config.vendor.scripts.dest));
});
gulp.task('vendorStyles', function () {
return gulp.src(config.vendor.styles.src)
.pipe(concat(config.vendor.styles.output))
.pipe(postcss([ cssnano() ]))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(config.vendor.styles.dest));
});
gulp.task('vendor', ['vendorScripts', 'vendorStyles']);

Multiple gulp sources and destinations

I am trying to have following directory structure
-- src
|__ app
|__ x.ts
|__ test
|__ y.ts
-- build
|__ app
|__ js
|__ test
|__ js
I want that on "gulp compile", I have my generated js files inside build/app and build/test. i.e. I have multiple sources and multiple destination.
I dont want to create a new gulp target for test one. Following are two various methods which I am trying to accomplish the task
gulp.task('compile', function () {
//path to src/app typescript files
var app_js = gulp.src('./src/app/**/*.ts')
.pipe(tsc(tsProject))
//path to src/test typescript files
var test_js = gulp.src('./src/test/**/*.ts')
.pipe(tsc(tsProject));
return merge([
app_js.js.pipe(gulp.dest('./build/src/app/')),
test_js.js.pipe(gulp.dest('./build/src/test/'))
]);
});
gulp.task('bundle', function () {
var paths = [
{ src: './src/app/**/*.ts', dest: './build/src/app/' },
{ src: './src/test/**/*.ts', dest: './build/src/test/' }
];
var tasks = paths.map(function (path) {
return gulp.src(path.src).pipe(tsc(tsProject)).pipe(gulp.dest(path.dest));
})
return merge(tasks);
});
However, every time, I run "gulp compile" or "gulp bundle", I hit following issues
events.js:141
throw er; // Unhandled 'error' event
^Error: stream.push() after EOF
at readableAddChunk (_stream_readable.js:132:15)
Can somebody tell me that what am I doing wrong here?
NOTE: I tried using both merge-stream and merge2 packages.
Ower! Thanks for your prompt response.
I end up with following solution
gulp.task('compile', function () {
return gulp.src(['src/**/*.ts'] )
.pipe(sourcemaps.init())
.pipe(tsc(tsProject))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('build'));
});"
I have the same project structure in one of my projects and I use the following for the build task:
var tsProject = tsc.createProject({
removeComments : false,
noImplicitAny : false,
target : "ES5",
module : "commonjs",
declarationFiles : false
});
gulp.task("build-source", function() {
return gulp.src(__dirname + "/source/**/**.ts")
.pipe(tsc(tsProject))
.js.pipe(gulp.dest(__dirname + "/build/source/"));
});
var tsTestProject = tsc.createProject({
removeComments : false,
noImplicitAny : false,
target : "ES5",
module : "commonjs",
declarationFiles : false
});
gulp.task("build-test", function() {
return gulp.src(__dirname + "/test/*.test.ts")
.pipe(tsc(tsTestProject))
.js.pipe(gulp.dest(__dirname + "/build/test/"));
});
gulp.task("build", function(cb) {
runSequence("lint", "build-source", "build-test", cb);
});
I'm using two separated tasks because using one tsProject object with multiple sources and multiple destinations can lead to unexpected behavior.
You must create the project outside of the task. You can't use the same project in multiple tasks. Instead, create multiple projects or use a single task to compile your sources. - Source
I also use the following for the bundle task:
gulp.task("bundle-source", function () {
var b = browserify({
standalone : 'inversify',
entries: __dirname + "/build/source/inversify.js",
debug: true
});
return b.bundle()
.pipe(source("inversify.js"))
.pipe(buffer())
.pipe(gulp.dest(__dirname + "/bundled/source/"));
});
gulp.task("bundle-test", function () {
var b = browserify({
entries: __dirname + "/build/test/inversify.test.js",
debug: true
});
return b.bundle()
.pipe(source("inversify.test.js"))
.pipe(buffer())
.pipe(gulp.dest(__dirname + "/bundled/test/"));
});
gulp.task("bundle", function(cb) {
runSequence("build", "bundle-source", "bundle-test", "document", cb);
});
My entire build script is available here. Hope it helps!

Keep original typescript source maps after using browserify

Background: I am compiling 2 dependent TypeScript files to js, which produces also source maps (one source map per file) using tsc 1.0
I'm using -m commonjs and then use browserify to generate a single bundle.js
However I noticed that I get the original source map references twice in the bundle, which doesn't seem to work.
Passing --debug doesn't seem to do the trick either.
I had a feeling this issue: https://github.com/substack/node-browserify/issues/325 is somewhat related, but I couldn't figure out how the issue was resolved.
Also https://github.com/substack/browser-pack was suggested, but again I don't fully understand how to use it, is it a replacement to browserify?
Bottom line, I would like to merge the 2 js files but "merge" the js to ts source maps using browserify. Is that possible?
tsify is a browserify plugin that is better and replaces e.g. typescriptifier.
npm install tsify browserify watchify
You use tsify like this:
browserify src/index.ts -p tsify --debug -o build/index.js
Notice that this supports browserify --debug switch, no extra tricks required. So you can also use it with watchify like this:
watchify src/index.ts -p tsify --debug -o build/index.js
Using the minifyify browserify plugin I believe you can use TypeScript with Browserify and retain the source maps. After compiling the TypeScript files you should be able to pass the "entry" file (the one that imports the other one via commonjs syntax) through browserify with the minifyify plugin.
var browserify = require('browserify'),
bundler = new browserify();
bundler.add('entry.js');
bundler.plugin('minifyify', {map: 'bundle.js.map'});
bundler.bundle({debug: true}, function (err, src, map) {
if (err) console.log(err);
fs.writeFileSync('bundle.js', src);
fs.writeFileSync('bundle.js.map', map);
});
Here is my working solution:
var settings = {
projectName : "test"
};
gulp.task("bundle", function() {
var mainTsFilePath = "src/main.ts";
var outputFolder = "bundle/src/";
var outputFileName = settings.projectName + ".min.js";
var pkg = require("./package.json");
var banner = [
"/**",
" * <%= pkg.name %> v.<%= pkg.version %> - <%= pkg.description %>",
" * Copyright (c) 2015 <%= pkg.author %>",
" * <%= pkg.license %>",
" */", ""
].join("\n");
var bundler = browserify({
debug: true,
standalone : settings.projectName
});
// TS compiler options are in tsconfig.json file
return bundler.add(mainTsFilePath)
.plugin(tsify)
.bundle()
.pipe(source(outputFileName))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(uglify())
.pipe(header(banner, { pkg : pkg } ))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(outputFolder));
});
I created example project.
You can run it with $(npm bin)/gulp build --env=dev for development environment and source maps will be generated.
There is gulpfile.js:
'use strict';
var path = require('path'),
gulp = require('gulp'),
del = require('del'),
typescript = require('gulp-typescript'),
sourcemaps = require('gulp-sourcemaps'),
browserify = require('browserify'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer'),
uglify = require('gulp-uglify'),
gutil = require('gulp-util'),
inject = require('gulp-inject'),
babel = require('gulp-babel'),
argv = require('yargs').argv;
var devEnvironment = 'dev',
prodEnvironment = 'prod',
environment = argv.env || prodEnvironment,
isDevelopment = environment === devEnvironment;
var projectPath = __dirname,
srcDir = 'src',
srcPath = path.join(projectPath, srcDir),
buildDir = path.join('build', environment),
buildPath = path.join(projectPath, buildDir),
distDir = 'dist',
distRelativePath = path.join(buildDir, distDir),
distPath = path.join(buildPath, distDir);
var tsSrcPath = path.join(srcPath, 'typescript'),
tsGlob = path.join(tsSrcPath, '**', '*.ts'),
tsBuildPath = path.join(buildPath, 'tsc');
var indexHtmlName = 'index.html',
indexJsName = 'index.js';
var distIndexJsPath = path.join(distPath, 'index.js'),
distIndexHtmlPath = path.join(distPath, indexHtmlName);
var tsProject = typescript.createProject('tsconfig.json');
console.log('Environment: ' + environment);
gulp.task('clean', function () {
return del([buildPath]);
});
gulp.task('tsc', ['clean'], function () {
var stream = gulp.src([tsGlob]);
if (isDevelopment) {
stream = stream
.pipe(sourcemaps.init());
}
stream = stream
.pipe(typescript(tsProject))
.pipe(babel({
presets: ['es2015']
}));
if (isDevelopment) {
stream = stream.pipe(sourcemaps.write({sourceRoot: tsSrcPath}));
}
return stream.pipe(gulp.dest(tsBuildPath));
});
gulp.task('bundle', ['tsc'], function () {
var b = browserify({
entries: path.join(tsBuildPath, indexJsName),
debug: isDevelopment
});
var stream = b.bundle()
.pipe(source(indexJsName))
.pipe(buffer());
if (!isDevelopment) {
stream = stream.pipe(uglify());
}
return stream
.on('error', gutil.log)
.pipe(gulp.dest(distPath));
});
gulp.task('build', ['bundle'], function() {
return gulp.src(path.join(srcPath, indexHtmlName))
.pipe(inject(gulp.src([distIndexJsPath], {read: false}), {ignorePath: distRelativePath, addRootSlash: true}))
.pipe(gulp.dest(distPath));
});
You should pay attention to lines:
stream = stream.pipe(sourcemaps.write('', {sourceRoot: tsSrcPath})); - write inline source maps with sourceRoot pointing to your typescript sources path. Inline maps are written directly to .js files generated by tsc to build/dev/tsc.
debug: isDevelopment - in development environment make browserify generate his own source maps for resulting bundle build/dev/dist/index.js file so it will have source maps referencing .js files from build/dev/tsc which in turn have source maps referencing .ts files from src/typescript.
With this setup you will be able to see and debug .ts files in browser:
I faced similar issue when trying to debug my Angular2 app running in Chrome in Visual Studio Code (Using Debugger for Chrome extension)
I use gulp as my task runner and my setup is as follows:
Typescript files -> tsc -> intermediate es5 js -> browserify (plus uglify in production build) -> compiled bundle
My directory structure is as follows:
|- src
|- my .ts files here
|- main.ts - my entry file
|- dist
|- intermediate files go here
|- web
|- app.js - final bundle
|- app.js.map - final bundle map
|- gulpfile.js
gulpfile.js:
var gulp = require('gulp'),
tsc = require('gulp-typescript'),
browserify = require('browserify'),
uglify = require('gulp-uglify'),
sourcemaps = require('gulp-sourcemaps'),
source = require('vinyl-source-stream'),
buffer = require('vinyl-buffer');
gulp.task('tsc', [], () => {
return gulp.src(['src/**/*.ts'])
.pipe(sourcemaps.init())
.pipe(tsc({
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [ "es2015", "dom" ],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}))
.pipe(sourcemaps.write(null, {
"sourceRoot": function(file) {
let parts = file.relative.split('\\');
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
}))
.pipe(gulp.dest('dist/'));
});
gulp.task('bundle', ['tsc'], () => {
let b = browserify({
entries: 'dist/main.js',
debug: true,
});
return b.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(sourcemaps.write('./', {
"sourceRoot": "../",
}))
.pipe(gulp.dest('web/'));
})
gulp.task('default', ['bundle']);
Explanation/reasoning:
For some reason browserify doesn't read and parse .js.map files linked in .js file (via special comment at the end) but it does when the source map is embedded in js file. So, by passing null instead of path to sourcemaps it will embed it at the end of generated .js file.
Next issue I noticed was that sourcemaps doesn't automatically follow directory structure (add '../' to sourceRoot when it goes to next directory level), so I made a quick function to complement this. Keep in mind that it only works on Windows - on Linux you'd have to change split character.
function(file) {
let parts = file.relative.split('\\'); // put '/' here on Linux
let root = Array(parts.length + 1).join('../') + 'src';
return root;
}
Certainly there is a way to detect correct path separator, I'm debugging only on Windows thus it's not important for my purposes.
I hope it helps someone, cause I've spent whole Sunday morning tracking down this problem.

Categories