keystonejs / keystone-classic

Node.js CMS and web app framework
http://v4.keystonejs.com
MIT License
14.63k stars 2.21k forks source link

How does one setup livereloading of Sass files #1630

Closed josephdburdick closed 9 years ago

josephdburdick commented 9 years ago

Hi, I've done this with Gulp projects before but it seems like no matter what I try I cannot get live reloading to work with Keystone. If there is documentation on this somewhere you could direct me to, I'll gladly inspect.

So far the gulpfile.js in my project looks like this:

var gulp = require('gulp'),
        jshint = require('gulp-jshint'),
        jshintReporter = require('jshint-stylish'),
        watch = require('gulp-watch'),
        sass = require('gulp-sass'),
        livereload = require('gulp-livereload'),
        connect = require('gulp-connect');

/*
 * Create variables for our project paths so we can change in one place
 */
var paths = {
    'src':['./models/**/*.js','./routes/**/*.js', 'keystone.js', 'package.json'],
    'style': {
        all: './public/styles/**/*.scss',
        input: './public/styles/site.scss',
        output: './public/styles/'
    }
};

// gulp sass
gulp.task('sass', function () {
  gulp.src(paths.style.input)
    .pipe(sass().on('error', sass.logError))
    .pipe(gulp.dest(paths.style.output))
        .pipe(connect.reload())
});

gulp.task('watch:sass', function () {
    livereload({ start: true });
    livereload.listen();
  gulp.watch(paths.style.all, ['sass']);
});

gulp.task('watch', ['watch:sass', 'watch:lint']);

// gulp lint
gulp.task('lint', function(){
    gulp.src(paths.src)
        .pipe(jshint())
        .pipe(jshint.reporter(jshintReporter))
        .pipe(connect.reload());

});

// gulp watcher for lint
gulp.task('watch:lint', function () {
    gulp.src(paths.src)
        .pipe(watch())
        .pipe(jshint())
        .pipe(jshint.reporter(jshintReporter));
});

gulp.task('connect', function() {
  connect.server({
    root: 'build',
    livereload: true,
    open: false
  });
});

gulp.task('start', ['connect', 'watch']);

Running gulp start fires everything off.

josephdburdick commented 9 years ago

Does express need to be handled within the gulpfile? or does the live reloading of LESS or SASS need to be handled within keystone.init()?

morenoh149 commented 9 years ago

have you read https://github.com/vohof/gulp-livereload ?

josephdburdick commented 9 years ago

@morenoh149 Yes I have tried both liveReload and BrowserSync implementations inside of Keystone, listening to file changes with gulp watch. I have also tried to capture the express instance via Gulp but I believe now that I might have it backwards and need to trigger the live reloading from Keystone.init(). I feel that my lack of middleware knowledge is preventing me from seeing this completely through. If you have any pointers, anything that would enlighten me I would be very grateful.

TL,DR; Is there some kind of explanation on how Middleware would be the interface for auto-reloading instead of rigging the present gulpfile?

josephdburdick commented 9 years ago

The closest tutorial I've found is this: https://community.nitrous.io/tutorials/setting-up-gulp-with-livereload-sass-and-other-tasks Since it accesses express from Gulp. But my gut tells me that I shouldn't mess with express because Keystone handles that so my next instinct is to find out how to control this with middleware. Has anyone used middleware to capture the sass compilation event? This event could then trigger the injection/reload.

creynders commented 9 years ago

Have you tried something like this?

if(process.env.NODE_ENV==="development") {
  keystone.app.use(require('node-sass-middleware'));
}

Haven't got the time to spike it, but this is how I'd approach it.

morenoh149 commented 9 years ago

@josephdburdick I feel like doing livereload may be prohibitively difficult because keystone is running its own webserver. I did however get sass file watching working and regenerated locally so you can just refresh the browser in development. I'll be submitting this to the https://github.com/keystonejs/generator-keystone project soon

josephdburdick commented 9 years ago

Ok, @morenoh149, thanks for the attempt. I was also able to have keystone regenerate sass on save and be able to see changes after refreshing. I guess I've been spoiled by front-end workflows the past couple years and also my development with Meteor.js. Keystone looks like it should be able to achieve the same kind of refresh but I just can't figure out how. I see other contributors focusing on React, maybe the potential reactivity will naturally produce more demand for a feature like this.

morenoh149 commented 9 years ago

Some thoughts.

If you don't want to pay for livereload you're directed to https://github.com/guard/guard-livereload which is a ruby based tool. I think it wouldn't be welcome to add such a dependency (encourage is the same thing) to a javascript project like keystone.

We'd sooner use something like the webpack dev server https://webpack.github.io/docs/webpack-dev-server.html or something custom working with browserify. Closing because I don't think livereload specifically is the correct tool. But you're welcome to make a new issue requesting browsersync/webpack/etc. This thread should probably also be moved to the generator repo as it's concerned with usage rather than an issue with keystone itself.

morenoh149 commented 9 years ago

oh wait I misread the issue title. I thought you were insisting on using livereload. We should explore browserSync in proxy mode.

josephdburdick commented 9 years ago

I'm on a rollercoaster of emotion. Yeah, I think Browserify should be able to handle it somehow.

dlwalsh commented 9 years ago

Jumping off from the suggestion of @creynders above, the following worked for me. I assume by live reload you just want to watch for source changes, not automatically reload the browser window.

I've placed this right before keystone.start(); in keystone.js.

if (keystone.get('env') === 'development') {
    keystone.app.use(sassMiddleware({
        src: path.join(__dirname, 'public'),
        dest: path.join(__dirname, 'public'),
        debug: true,
        force: true
    }));
}

UPDATE

This is more succinct. (And avoids the need to import the sass-middleware and path modules.)

if (keystone.get('env') === 'development') {
    keystone.set('sass options', {
        debug: true,
        force: true
    });
}
josephdburdick commented 9 years ago

Just to clarify, when I say live reloading I am mean injecting style changes when CSS files are generated. This means upon generating a new css file from sass, less, stylus, etc the style would change in the page without refreshing or changing state. @dlwalsh I'll give your method a shot when I return to my machine.

morenoh149 commented 9 years ago

@josephdburdick the following gulpfile does what you want. Adding this task the gulpfile in generator-keystone will wait until I get my previous PR merged.

var gulp = require('gulp');
var jshint = require('gulp-jshint');
var jshintReporter = require('jshint-stylish');
var watch = require('gulp-watch');
var sass = require('gulp-sass');
var shell = require('gulp-shell')
var bs = require('browser-sync').create();

var paths = {
    'src':['./models/**/*.js','./routes/**/*.js', 'keystone.js', 'package.json'],
    'style': {
        all: './public/styles/**/*.scss',
        output: './public/styles/'
    }
};

// gulp lint
gulp.task('lint', function(){
    gulp.src(paths.src)
        .pipe(jshint())
        .pipe(jshint.reporter(jshintReporter));
});

// gulp watcher for lint
gulp.task('watch:lint', function () {
    gulp.src(paths.src)
        .pipe(watch())
        .pipe(jshint())
        .pipe(jshint.reporter(jshintReporter));
});

gulp.task('sass', function(){
    gulp.src(paths.style.all)
        .pipe(sass().on('error', sass.logError))
        .pipe(gulp.dest(paths.style.output))
    .pipe(bs.stream());
});

gulp.task('watch:sass', function () {
    gulp.watch(paths.style.all, ['sass']);
});

gulp.task('browser-sync', function(){
  bs.init({
    proxy: 'http://localhost:3000',
    port: '4000'
  });
});

gulp.task('runKeystone', shell.task('node keystone.js'));

gulp.task('watch', ['watch:sass', 'watch:lint']);
gulp.task('default', ['watch', 'runKeystone', 'browser-sync']);

usage, run gulp

josephdburdick commented 9 years ago

Wow, I can't wait to try this. I'll report back the results today. Thanks! :metal:

josephdburdick commented 9 years ago

@morenoh149 I followed your directions and got the following error:

➜  keystone-adoptive  gulp
[15:41:40] Using gulpfile /Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/gulpfile.js
[15:41:40] Starting 'watch:sass'...
[15:41:40] Finished 'watch:sass' after 78 ms
[15:41:40] Starting 'watch:lint'...
[15:41:40] Finished 'watch:lint' after 25 ms
[15:41:40] Starting 'watch'...
[15:41:40] Finished 'watch' after 5.9 μs
[15:41:40] Starting 'runKeystone'...
[15:41:40] Starting 'browser-sync'...
[15:41:40] Finished 'browser-sync' after 12 ms

/Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/models/Enquiry.js
  line 24  col 27  Unexpected 'default'.
  line 24  col 27  Expected an identifier and instead saw 'default' (a reserved word).

✖ 2 problems

[BS] [info] Proxying: http://localhost:3000
[BS] Access URLs:
 -----------------------------------
       Local: http://localhost:4000
    External: http://10.0.1.202:4000
 -----------------------------------
          UI: http://localhost:3001
 UI External: http://10.0.1.202:3001
 -----------------------------------

/Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/models/Gallery.js
  line 15  col 31  Unexpected 'default'.
  line 15  col 31  Expected an identifier and instead saw 'default' (a reserved word).

✖ 2 problems

/Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/models/Post.js
  line 16  col 70  Unexpected 'default'.
  line 16  col 70  Expected an identifier and instead saw 'default' (a reserved word).

✖ 2 problems

/Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/routes/views/blog.js
  line 33  col 61  Expected an identifier and instead saw 'in' (a reserved word).
  line 73  col 26  Expected an identifier and instead saw 'in' (a reserved word).

✖ 2 problems

/Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/routes/emails.js
  line 38  col 0  Identifier 'enquiry_url' is not in camel case.

✖ 1 problem

/Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/keystone.js
  line 43  col 10  Expected an identifier and instead saw 'import' (a reserved word).
  line 65  col 0   Identifier 'logo_src' is not in camel case.
  line 66  col 0   Identifier 'logo_width' is not in camel case.
  line 67  col 0   Identifier 'logo_height' is not in camel case.
  line 69  col 0   Identifier 'email_bg' is not in camel case.
  line 70  col 0   Identifier 'link_color' is not in camel case.
  line 73  col 0   Identifier 'background_color' is not in camel case.
  line 74  col 0   Identifier 'border_color' is not in camel case.
  line 87  col 34  Expected '===' and instead saw '=='.
  line 90  col 34  Expected '===' and instead saw '=='.

✖ 10 problems

[15:41:42] ../../15 files was added from pipe
dyld: lazy symbol binding failed: Symbol not found: _node_module_register
  Referenced from: /Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/node_modules/keystone/node_modules/watchify/node_modules/chokidar/node_modules/fsevents/build/Release/fse.node
  Expected in: dynamic lookup

dyld: Symbol not found: _node_module_register
  Referenced from: /Applications/MAMP/htdocs/Code/Github/josephdburdick/js/node/apps/keystone-adoptive/node_modules/keystone/node_modules/watchify/node_modules/chokidar/node_modules/fsevents/build/Release/fse.node
  Expected in: dynamic lookup

[15:41:44] 'runKeystone' errored after 3.42 s
[15:41:44] Error in plugin 'gulp-shell'
Message:
    Command `node keystone.js` failed with exit code 

Any thoughts?

josephdburdick commented 9 years ago

Solution found, as referenced here: https://github.com/BrowserSync/browser-sync/issues/443

josephdburdick commented 9 years ago

I can confirm that sass reloading now works as long as viewing on port 4000 despite the console stating the site is ready on port 3000.

morenoh149 commented 9 years ago

@josephdburdick right, because the keystone webserver is still at 3000 and browsersync is simply proxying it to 4000. We could fix that if many users want to use this setup.

josephdburdick commented 9 years ago

Cool, good to know. So I guess the only thing I didn't have right was the proxy part. Thanks for inspecting this, I'm looking forward to your PR being merged in the generator!

niallobrien commented 8 years ago

Has there been any movement on this? Thanks.

morenoh149 commented 8 years ago

@niallobrien you get it when you use the generator https://github.com/keystonejs/generator-keystone/blob/master/app/templates/_gulpfile.js#L65

niallobrien commented 8 years ago

@morenoh149 Thanks for clarifying.

unremarkablegarden commented 8 years ago

@morenoh149 I'm trying to use your gulp file with keystone 4.0 beta. I've installed the requirements. Can't figure out how to fix this...

% gulp  
[14:17:01] Using gulpfile ~/Sites/keystone/gulpfile.js  
[14:17:01] Starting 'watch:sass'...  
[14:17:01] Finished 'watch:sass' after 69 ms  
[14:17:01] Starting 'watch:lint'...  
[14:17:01] 'watch:lint' errored after 14 ms  
[14:17:01] Error in plugin 'gulp-watch'  
Message:  
    glob argument required
Outridair commented 8 years ago

Using gulp 3.9.1 and node 5.9.0

var gulp = require('gulp');
var server = require('gulp-express');
var browserSync = require('browser-sync').create();

gulp.task('serve', function() {
    server.run(['keystone.js']);
    setTimeout(function() {
        browserSync.init({
            proxy: 'http://localhost:3000',
            port: '4000'
        });
    }, 3000); //Gotta let the Keystone Server actually begin ( you may need to increase this value depending on computer power)
});

Obviously anything using setTimeout is just... bad, but this'll work. Depending on your system you may want to increase the timeout. run with gulp serve

niallobrien commented 8 years ago

Just run it in a separate terminal session after you've started Keystone and remove the setTimeout().

mylastore commented 6 years ago

I have LiveReload working for both styles and templates here is my gulpfile.js just run gulp and yes you must install LiveReload plugin for chrome.

var gulp = require('gulp');
var jslint = require('gulp-jslint');
var watch = require('gulp-watch');
var sass = require('gulp-sass');
var process = require('process');
var livereload = require('gulp-livereload');
var nodemon = require('nodemon');
var gulphb = require('gulp-handlebars');
var paths = {
    'src':['./models/**/*.js','./routes/**/*.js', 'keystone.js', 'package.json'],
    'templates':['./templates/**/*.hbs'],
    'style': {
        all: './public/styles/**/*.scss',
        output: './public/styles/'
    }
};
gulp.task('lint', function(){
    gulp.src(paths.src)
         .pipe(jslint())
});
gulp.task('watch:lint', function () {
    gulp.src(paths.src)
        .pipe(watch())
        .pipe(jslint())
});
gulp.task('sass', function(){
    gulp.src(paths.style.all)
        .pipe(sass().on('error', sass.logError))
        .pipe(gulp.dest(paths.style.output))
        .pipe(livereload());
});
gulp.task('templates', function(){
    gulp.src('./templates/**/*.hbs')
        .pipe(gulphb())
        .pipe(livereload());
});
gulp.task('watch', function () {
    livereload.listen();
    gulp.watch(paths.style.all, ['sass']);
    gulp.watch(paths.templates, ['templates']);
    gulp.watch([paths.src], ['lint']);

    nodemon({
        script: 'keystone.js',
        stdout: false
    }).on('readable', function() {
        this.stdout.on('data', function(chunk) {
            if (/^listening/.test(chunk)) {
                livereload.reload()
            }
            process.stdout.write(chunk)
        })
    })

});
gulp.task('default', ['lint', 'watch']);
jelordreygulle commented 5 years ago

@morenoh149

AssertionError [ERR_ASSERTION]: Task function must be specified , on running gulp