After some research on the benefits of the isomorphic/universal javascript apps and server side rendering I have decided to use it in a project.
I am using Express.js and React.js to achieve server side and client side rendering.
One problem I have faced recently is my browser javascript cannot find a React variable which is a React component. It gives the error message of well known ReferenceError: Can't find variable: QuestionBox.
This react component is defined in the questionbox.js and this file used for server side after transpiled by babel in node.js and for client side rendering after browserifyed and rectifyed in a gulp task.
What can be the point here I am missing? It can be the gulp generated transformed file that is loaded by the browser by a script tag. The full code is in this gist.
questionbox.js:
var React = require('react');
var marked = require('marked');
/*
-QuestionBox
-QuestionList
-Question
*/
var Question = React.createClass({//Omitted here for simplicity: https://gist.github.com/isikfsc/b19ccb5e396fd57693d2f5b876ea20a0});
var QuestionList = React.createClass({//Omitted here for simplicity: https://gist.github.com/isikfsc/b19ccb5e396fd57693d2f5b876ea20a0});
var QuestionBox = React.createClass({
loadQuestionsFromServer: function() {
return true;
},
getInitialState: function() {
return {data: []};
},
componentWillMount: function() {
this.loadQuestionsFromServer();
setInterval(this.loadQuestionsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="questionBox">
<h1>Questions</h1>
<QuestionList data={this.state.data} />
</div>
);
}
});
module.exports.QuestionBox = QuestionBox;
gulpfile.js:
var gulp = require('gulp');
var browserify = require('browserify');
var watchify = require('watchify');
var source = require('vinyl-source-stream');
var reactify = require('reactify');
var production = process.env.NODE_ENV === 'production';
function scripts(watch) {
var bundler, rebundle;
bundler = browserify('src/components/questionbox.js', {
basedir: __dirname,
debug: !production,
cache: {}, // required for watchify
packageCache: {}, // required for watchify
fullPaths: watch // required to be true only for watchify
});
if(watch) {
bundler = watchify(bundler)
}
bundler.transform(reactify);
rebundle = function() {
var stream = bundler.bundle();
//stream.on('error', handleError('Browserify'));
stream = stream.pipe(source('bundle.js'));
return stream.pipe(gulp.dest('./dist/components'));
};
bundler.on('update', rebundle);
return rebundle();
}
gulp.task('watchScripts', function() {
gulp.watch('./src/components/*.js', ['scripts']);
});
gulp.task('scripts', function() {
return scripts(false);
});
gulp.task('watchScripts', function() {
return scripts(true);
});
I believe the problem is that the component is not (and really should not be) exposed to the global scope.
All the code inside the browserify bundle in not accessible from outside. So what should be done next is moving render function into the bundle.
To begin with, you can create new file (say, entry.js) and set it as an entry point for browserify in gulpfile.js:
bundler = browserify('src/entry.js', {
Then, You could move JavaScript from the template (.ejs) to this entry point.
var ReactDOM = require('react-dom');
var QuestionBox = require('./components/questionbox.js').QuestionBox;
ReactDOM.render(QuestionBox({}), document.getElementById('mount-node'));
As long as browserify bundle is only used by the client, You don't have to change anything on the server.
Related
I'm using Browserify to create a bundle which contains an exported function that I want to call within a <script> tag. Everything works fine until I require Bootstrap, at which point the function is no longer accessible and I get the error:
TypeError: mainBundle.greeting is not a function
Here's the code:
JavaScript (main.js):
window.jQuery = require('jquery');
window.$ = global.jQuery;
module.exports = {
greeting
};
function greeting (name) {
return `Hello ${name}!`;
}
HTML
<script src="js/bundle.js"></script>
<script>
// Update greeting
$('#greeting').text(mainBundle.greeting('Foo'));
</script>
Gulpfile:
Taken pretty much from the Gulp Browserify recipe. You can see I've added the standalone option to customOpts to generate a standalone module as well as require to add Bootstrap. The issue occurs when the require line is commented in.
const gulp = require('gulp');
const sourcemaps = require('gulp-sourcemaps');
const concat = require('gulp-concat');
const watchify = require('watchify');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const log = require('gulplog');
// add custom browserify options here
const customOpts = {
entries: ['./src/js/main.js'],
// require: ['bootstrap', 'jquery'], // UNCOMMENT CAUSES ISSUE
standalone: 'mainBundle',
debug: true
};
const opts = {...watchify.args, ...customOpts};
const b = watchify(browserify(opts));
console.log('Browserify options: ', opts);
// add transformations here
// i.e. b.transform(coffeeify);
exports.js = bundle; // so you can run `gulp js` to build the file
b.on('update', bundle); // on any dep update, runs the bundler
b.on('log', log.info); // output build logs to terminal
function bundle() {
return b.bundle()
// log errors if they happen
.on('error', log.error.bind(log, 'Browserify Error'))
.pipe(source('bundle.js'))
// optional, remove if you don't need to buffer file contents
.pipe(buffer())
// optional, remove if you dont want sourcemaps
.pipe(sourcemaps.init({loadMaps: true})) // loads map from browserify file
// Add transformation tasks to the pipeline here.
.pipe(sourcemaps.write('./')) // writes .map file
.pipe(gulp.dest('./dist/js'));
}
Hmm, putting the require in main.js resolves the issue:
const bootstrap = require('bootstrap');
window.jQuery = require('jquery');
window.$ = global.jQuery;
module.exports = {
greeting
};
function greeting (name) {
return `Hello ${name}!`;
}
If anyone has a better answer that would allow me to use the Browserify require option, I will happily accept your answer. I would prefer to use the config option to avoid importing things that are not explicitly required in my scripts.
I'm trying to render each file in my gulp source files with it's own json file, but I can't figure out how to access the current filename in the pipe function.
var gulp = require('gulp');
var handlebars = require('handlebars');
var gulpHandlebars = require('gulp-compile-handlebars');
gulp.task('compile-with-sample-data', function () {
var options = {}
return gulp.src('./src/**/*.html')
.pipe(gulpHandlebars({ data: require('./data/' + filename +'.json') }, options))
.pipe(gulp.dest('./build/'));
});
Where I can get it to work with the same file each time by just using require('./data/orders-complete.json'):
var gulp = require('gulp');
var handlebars = require('handlebars');
var gulpHandlebars = require('gulp-compile-handlebars');
gulp.task('compile-with-sample-data', function () {
var options = {}
return gulp.src('./src/**/*.html')
.pipe(gulpHandlebars({ data: require('./data/orders-complete.json') }, options))
.pipe(gulp.dest('./build/'));
});
It's not clear how I would do this.
Use gulp-tap, it enables you to get the file name and even change it for downstream pipes.
So I have my Gulp file as below.
The 'watch' block seems to work absolutely fine, and do what is expected. Nodemon works in the way that it detects file changes and refreshes the server, that works too.
But I can't for the life of me get Nodemon to call the 'watch' method when a file changes.
On line 81, the .on('start' is called succesfully from nodemon, but the code inside the 'watch' method is never executed.
var gulp = require('gulp');
var gutil = require('gulp-util'); // For logging stats and warnings
var jshint = require('gulp-jshint'); // For checking JavaScript for warnings
var concat = require('gulp-concat'); // For joining together multiple files
var uglify = require('gulp-uglify'); // For minimising files
var coffee = require('gulp-coffee'); // For compiling coffee script into js
var cofLint = require('gulp-coffeelint');// For checking coffee script for errors
var less = require('gulp-less'); // For compiling Less into CSS
var cssLint = require('gulp-csslint'); // For checking the awesomeness of css
var minCss = require('gulp-minify-css');// For minifying css
var uncss = require('gulp-uncss'); // For deleting unused CSS rules
var footer = require('gulp-footer'); // For adding footer text into files
var nodemon = require('gulp-nodemon'); // For the super cool instant refreshing server
var bSync = require('browser-sync'); // Syncs the place between multiple browsers for dev
var es = require('event-stream'); // For working with streams rather than temp dirs
var sourcePath = "sources";
var destPath = "public";
var jsFileName = "all.min.js";
var cssFileName = "all.min.css";
var footerText = "";
/* JavaScript Tasks */
gulp.task('scripts', function(){
var jsSrcPath = sourcePath + '/javascript/**/*';
var jsResPath = destPath + '/javascript';
var jsFromCs = gulp.src(jsSrcPath+'.coffee')// Get all coffee script
.pipe(cofLint()) // Check CS for errors or warnings
.pipe(cofLint.reporter()) // Output the error results
.pipe(coffee()); // Convert coffee to vanilla js
var jsFromPlain = gulp.src(jsSrcPath+'.js');// get all vanilla JavaScript
return es.merge(jsFromCs, jsFromPlain) // Both js from cs and vanilla js
.pipe(jshint()) // Check js errors or warnings
.pipe(jshint.reporter('jshint-stylish')) // Print js errors or warnings
.pipe(concat(jsFileName,{newLine: ';'})) // Concatenate all files together
.pipe(uglify()) // Minify JavaScript
.pipe(footer(footerText)) // Add footer to script
.pipe(gulp.dest(jsResPath)); // Save to destination
});
/* CSS Tasks */
gulp.task('styles', function(){
var cssSrcPath = sourcePath + '/styles/**/*';
var cssResPath = destPath + '/stylesheet';
var cssFromLess = gulp.src(cssSrcPath+'.less') // Get all Less code
.pipe(less()); // Convert Less to CSS
var cssFromVanilla = gulp.src(cssSrcPath+'.css');// Get all CSS
return es.merge(cssFromLess, cssFromVanilla) // Combine both CSS
.pipe(cssLint()) // Check CSS for errors or warnings
.pipe(cssLint.reporter()) // And output the results
.pipe(concat(cssFileName)) // Concatenate all files together
.pipe(minCss({compatibility: 'ie8'})) // Minify the CSS
.pipe(gulp.dest(cssResPath)); // Save to destination
});
/* Configure files to watch for changes */
gulp.task('watch', function() {
gulp.watch(sourcePath+'/**/*.{js,coffee}', ['scripts']);
gulp.watch(sourcePath+'/**/*.{css,less}', ['styles']);
});
/* Start Nodemon */
gulp.task('demon', function () {
nodemon({
script: './bin/www',
ext: 'js coffee css less html',
env: { 'NODE_ENV': 'development'}
})
.on('start', ['watch'])//TODO: ERROR: watch is never called, even though start is called
.on('change', ['watch'])
.on('restart', function () {
console.log('restarted!');
});
});
/* Default Task */
gulp.task('default', ['demon']);
Any suggestions why this may be happening?
Is there something wrong with my Gulp file?
(and yes, I know the code is a little over-commented, I work with apprentices and they like English better than code ;)
The problem is that Nodemon and browser-sync both need to be running. This code works:
gulp.task('nodemon', function (cb) {
var called = false;
return nodemon({
script: './bin/www',
watch: ['source/**/*']
})
.on('start', function onStart() {
if (!called) { cb(); }
called = true;
})
.on('restart', function onRestart() {
setTimeout(function reload() {
bSync.reload({
stream: false
});
}, 500);
});
});
gulp.task('browser-sync', ['nodemon', 'scripts', 'styles'], function () {
bSync.init({
files: ['sources/**/*.*'],
proxy: 'http://localhost:3000',
port: 4000,
browser: ['google chrome']
});
gulp.watch(sourcePath+'/**/*.{js,coffee}', ['scripts']);
gulp.watch(sourcePath+'/**/*.{css,less}', ['styles']);
gulp.watch("sources/**/*").on('change', bSync.reload);
gulp.watch("views/**/*.jade").on('change', bSync.reload);
});
/* Default Task */
gulp.task('default', ['clean', 'browser-sync']);
main.js
var React = require('react');
var App = require('./components/app.js');
React.render(
<App />,
document.getElementById('container')
);
app.js
var React = require('react');
var App = React.createClass({
render: function(){
return (
<p>Hello</p>
);
}
});
module.exports = App;
is there any problem in gulpfile.js? because it works fine if i write react component code inside main.js file.
You're forgetting to transform your JSX into Javascript.
Here's my gulp build task - It uses browserify and reactify to transform and bundle my components, then minifies them, and finishes by placing them into a directory that my application reads from.
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var source = require('vinyl-source-stream');
var browserify = require('browserify');
var reactify = require('reactify');
var streamify = require('gulp-streamify');
gulp.task('build', function () {
browserify({
entries : ['./js_modules/main.js'],
transform : [reactify],
})
.bundle()
.pipe(source('bundle.js'))
.pipe(streamify(uglify('bundle.js')))
.pipe(gulp.dest('src/main/webapp/js/bundle.js'));
});
I am trying to get any variation of hbsfy or browserify-handlebars to compile correctly using browserify. Compiling results in the handlebars.js(hbsfy) code outputting to my browser. I've tried just using the browserify command browserify -t hbsfy app.js > bundle.js but it doesn't change anything
I haven't the reputation to post images but basically this is the output:
var templater = require("handlebars/runtime").default.template;module.exports = templater(function (Handlebars,depth0,helpers,partials,data) { this.compilerInfo = [4,'>= 1.0.0']; helpers = this.merge(helpers, Handlebars.helpers); data = data || {}; var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression; buffer += "
Hello "; if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); } else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; } buffer += escapeExpression(stack1) + "
"; return buffer; });
My template (template.hbs) is simply <h1>Hello {{name}}</h1>
My gulpfile setup:
var gulp = require('gulp');
var livereload = require('gulp-livereload');
var browserify = require('gulp-browserify');
var hbsfy = require('browserify-handlebars');
//var hbsfy = require('hbsfy'); //this one shows up the same way
gulp.task('scripts', function() {
return gulp.src('./app/app.js')
.pipe(browserify({
transform: [hbsfy]
}))
.pipe(rename('bundle.js'))
.pipe(gulp.dest('./build/js'))
.pipe(connect.reload());
});
and my js file:
var Handlebars = require('hbsfy/runtime');
var $ = require('jquery'),
router = require('./router/routerDefault'),
template = require('./template.hbs');
$(document).ready(function(){
document.body.innerHTML = template({name: 'browserify'});
})
Does anyone have any experience on how to handle this? Any suggestions would be heplful!
The cause of this issue is redundant compiling. Listing a transform in both the packages.json and the gulpfile.js will perform it twice, I believe. In my packages.json, I now just use this 'node':
"browserify": {
"transform": [
"hbsfy"
]
},
This will compile your templates for you. Your gulpfile.js DOES NOT require this section:
.pipe(browserify({
transform: [hbsfy]
}))
You can use either one. My scripts gulp task now looks like this:
gulp.task('scripts', function() {
return browserify('./app/app.js')
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest('./build/js'))
.pipe(connect.reload());
});
I am experiencing something similar.
Just curious, what OS are you using? Seems to affect Mac but Windows seems OK.
I'm not entirely sure what's causing this but I stopped using gulp-browserify as it is now blacklisted.
I followed the suggestions from this blog post and it seems to solve the issue: http://viget.com/extend/gulp-browserify-starter-faq
The last bit is most relevant.
EDIT:
While using gulp-browserify, I would also check if you've listed your transforms in package.json. I think you may only need to specify transforms in one place (either in gulpfile as you have now or in package.json).