I am having an issue where after several restarts triggered by gulp watch, I get a warning that states I may have a memory leak. I assume it's because I am starting a new instance of gulp watch with every restart, triggering a new server and another new watch, over and over. I am not sure that's the case and not sure how to fix it if it is.
Here is my gulpfile.js.
const gulp = require('gulp');
const exec = require('child_process').exec;
const gutil = require('gulp-util');
const eslint = require('gulp-eslint');
gulp.task('dev', (() => {
gulp.start('test');
gulp.start('lint');
gulp.start('server');
gulp.start('watch');
return gutil.log('Gulp is running!');
}));
gulp.task('server', (cb) => {
exec('npm start', (err, stdout, stderr) => {
gutil.log(stdout);
gutil.log(stderr);
cb(err);
});
});
gulp.task('test', (cb) => {
exec('npm test', (err, stdout, stderr) => {
gutil.log(stdout);
gutil.log(stderr);
cb(err);
});
});
gulp.task('watch', () => {
gulp.watch(['./app.js', './views/*', './routes/*'], ['dev']);
});
gulp.task('lint', () => {
return gulp.src(['**/*.js', '!node_modules/**'])
.pipe(eslint())
.pipe(eslint.format());
});
And my error is:
(node:808) Warning: Possible EventEmitter memory leak detected. 11 change listeners added. Use emitter.setMaxListeners() to increase limit
Your gulp.watch calls the task dev which in turn creates another gulp.watch. My money is on that.
As another user noted, you're calling gulp dev in gulp watch basically creating an inifinity loop, which would cause that.
To add, I would encourage using a default task:
gulp.task('default', ['dev', 'test', 'lint', 'watch', 'server']);
Related
I like to use Javascript as a replacement for bash scripts.
Assuming a contrived script called start.js that is run using node start.js:
const shelljs = require("shelljs")
if (!shelljs.which("serve")) {
shelljs.echo("'serve' is missing, please run 'npm ci'")
process.exit(1)
}
shelljs.exec("serve -s build -l 3000")
How do I test that:
If serve isn't available then serve -s build -l 3000 is never called and program exits with code 1.
If serve is available then serve -s build -l 3000 is called.
I don't mind mocking "shelljs", process.exit or anything else.
My main issue is figuring out how to require or import such a functionless and moduleless file in a test suite and get it to run once on each separate test with the mocks present without actually turning this into a CommonJS/ES6 module.
Just mock shelljs module, and spy process.exit function.
describe("start.js", () => {
let shelljs
let exitSpy
beforeEach(() => {
jest.mock("shelljs", () => {
return {
exec: jest.fn(),
which: jest.fn(),
echo: jest.fn(),
}
})
shelljs = require("shelljs")
exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {})
});
afterEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
it("should execute process.exit with code is 1 when 'serve' is not existed", () => {
shelljs.which.mockReturnValue(false)
require("./start")
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).toHaveBeenCalledWith("'serve' is missing, please run 'npm ci'")
expect(exitSpy).toHaveBeenCalledWith(1)
// expect(shelljs.exec).toHaveBeenCalled() // can not check like that, exitSpy will not "break" your code, it will be work well if you use if/else syntax
});
it("should execute serve when 'serve' is existed", () => {
shelljs.which.mockReturnValue(true)
require("./start")
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).not.toHaveBeenCalled()
expect(exitSpy).not.toHaveBeenCalled()
expect(shelljs.exec).toHaveBeenCalledWith("serve -s build -l 3000")
});
})
Another way to make sure the production code with be break when process.exit is called. Mock exit function throw an Error, then expect shelljs.exec will not be called
describe("start.js", () => {
let shelljs
let exitSpy
beforeEach(() => {
jest.mock("shelljs", () => {
return {
exec: jest.fn(),
which: jest.fn(),
echo: jest.fn(),
}
})
shelljs = require("shelljs")
exitSpy = jest.spyOn(process, "exit").mockImplementation(() => {
throw new Error("Mock");
})
});
afterEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
it("should execute process.exit with code is 1 when 'serve' is not existed", () => {
shelljs.which.mockReturnValue(false)
expect.assertions(5)
try {
require("./start")
} catch (error) {
expect(error.message).toEqual("Mock")
}
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).toHaveBeenCalledWith("'serve' is missing, please run 'npm ci'")
expect(exitSpy).toHaveBeenCalledWith(1)
expect(shelljs.exec).not.toHaveBeenCalled()
});
it("should execute serve when 'serve' is existed", () => {
shelljs.which.mockReturnValue(true)
require("./start")
expect(shelljs.which).toHaveBeenCalledWith("serve");
expect(shelljs.echo).not.toHaveBeenCalled()
expect(exitSpy).not.toHaveBeenCalled()
expect(shelljs.exec).toHaveBeenCalledWith("serve -s build -l 3000")
});
})
I've looked at many different posts here on SO using Gulp
CLI version: 2.3.0
Local version: 4.0.2
and have tried multiple approaches to the gulp 4 default task declaration:
exports.default = gulp.series(clean, (done) => {
gulp.parallel(styles, scripts);
done();
});
gulp.task('default',
gulp.series(clean, (done) => {
gulp.parallel(scripts, styles);
done();
});
);
function production () {
return gulp.series(clean, function (done) {
gulp.parallel(styles, scripts);
done();
})
}
exports.default = production
Clean function is simple:
function clean(cb) {
console.log("clean");
del(["dist"]);
cb();
}
For testing I have created a test function:
function test() {
console.log("in test");
return (
gulp.src(paths.styles.src)
.pipe(notify("Styles rendered"))
);
}
exports.test = test;
And a test default function:
exports.default = gulp.series(clean, (done) => {
console.log("before styles and scripts");
gulp.parallel(test);
console.log("after styles and scripts");
done();
});
Printed to the terminal are:
"clean"
"before styles and scripts"
"after styles and scripts"
But no "in test".
What am I missing here?
Not sure if this is the way Gulp is designed, but based on this answer, the code that works for me is:
exports.default = gulp.series(clean, (callbackA) => {
return gulp.parallel(styles, scripts, (callbackB) =>
{
callbackA();
callbackB();
})();
});
And the three tasks i'm calling are:
function clean(cb) {
del(["dist"]);
cb();
}
// Define tasks after requiring dependencies
function styles() {
console.log("in styles");
return (
gulp.src(paths.styles.src)
.pipe(sass())
.on("error", sass.logError)
.pipe(sourcemaps.init())
.pipe(sass())
.on("error", sass.logError)
.pipe(postcss([autoprefixer(), cssnano()]))
.pipe(sourcemaps.write())
.pipe(gulp.dest(paths.styles.dest))
// Add browsersync stream pipe after compilation
.pipe(browserSync.stream())
);
}
// Expose the task by exporting it
// This allows you to run it from the commandline using
// $ gulp style
exports.styles = styles;
function scripts() {
// Start by calling browserify with our entry pointing to our main javascript file
return ( gulp.src(paths.scripts.src)
.pipe(babel())
.pipe(uglify())
// Then write the resulting files to a folder
.pipe(gulp.dest(paths.scripts.dest))
);
}
// Expose the task, this allows us to call this task
// by running `$ gulp build' in the terminal
exports.scripts = scripts;
Aside from Gulp docs and here, this was a nice source.
I'm playing with gulp and I stumbled on this issue that I can't really understand.
I have some tasks:
gulp.task('concat-js', (done) => {
gulp.src('src/app/**/*.js')
.pipe(concat('app-script.js'))
.pipe(gulp.dest('src/app/dist/'));
done();
});
gulp.task('minify-js', (done) => {
gulp.src('src/app/dist/*.js')
.pipe(minify({noSource: true, ignoreFiles: ['*-min.js']}))
.pipe(gulp.dest('src/app/dist/'));
done();
});
gulp.task('compress',
gulp.series('concat-js', 'minify-js', function(done) {
// default task code here
done();
})
);
gulp.task(
'run',
gulp.series('compress', () => {
...
}
));
My goal is that when run is executed, I get concatenated of my source files and then minifed version of that file. But I'm running in some kind of problem with executing those tasks in series.
When I execute run for the first time, application doesn't run and only app-script.js gets created. Running it for the second time creates app-script-min.js file.
As far as I understand minify-js doesn't wait for concat-js to finish creating file.
I think this might be because done() is called before gulp is completed. Try change
gulp.task('concat-js', (done) => {
gulp.src('src/app/**/*.js')
.pipe(concat('app-script.js'))
.pipe(gulp.dest('src/app/dist/'));
done();
});
to
gulp.task('concat-js', (done) => {
gulp.src('src/app/**/*.js')
.pipe(concat('app-script.js'))
.pipe(gulp.dest('src/app/dist/'))
.on('finish', function() {
done();
});
});
I also found this resource which might be interesting for you. http://www.ericcredeur.com/gulp/javascript/2015/07/15/cascading-gulp-tasks.html
I want the gulp calls below to run synchronously, one after the other. But they do not follow an order.
The run-sequence node module doesn't help here, as I'm not trying to run gulp tasks in series (i.e. it has syntax similar to gulp.task("mytask", ["foo", "bar", "baz"] etc.), but rather gulp "calls" in series, as you see below.
gulp.task("dostuff", function (callback) {
gulp
.src("...")
.pipe(gulp.dest("...");
gulp
.src("...")
.pipe(gulp.dest("...");
gulp
.src("...")
.pipe(gulp.dest("...");
callback();
});
How do I make them run one after the other?
You can use async as a control flow for your calls to get them in only one task, also avoiding you to get a "pyramid effect". So something like this should be good for your use-case:
var async = require('async');
gulp.task('yeah', function (cb) {
async.series([
function (next) {
gulp.src('...')
.pipe(gulp.dest('...')
.on('end', next);
},
function (next) {
gulp.src('...')
.pipe(gulp.dest('...')
.on('end', next);
},
function (next) {
gulp.src('...')
.pipe(gulp.dest('...')
.on('end', next);
}
], cb);
});
That will also allow you to have some error handling and target better where a problem occured.
Well, it's just streams so you could listen for the end event (Watch out for the pyramid of doom!)
gulp.task("dostuff", function (callback) {
gulp
.src("...")
.pipe(gulp.dest("..."))
.on('end', function () {
gulp
.src("...")
.pipe(gulp.dest("..."))
.on('end', function () {
gulp
.src("...")
.pipe(gulp.dest("..."))
.on('end', callback);
});
});
});
But it's probably a better pattern to split it up in multiple tasks each one with a dependency on the previous one.
run-sequence:
Runs a sequence of gulp tasks in the specified order. This function
is designed to solve the situation where you have defined run-order,
but choose not to or cannot use dependencies.
npm install --save-dev run-sequence
// runSequence will ensure this task will run the following tasks in the listed order
gulp.task('things-to-do', callback => runSequence(
'clean-up-workspace', // clean up before copying new files
'copy-new-files', // wait till copy-new-files done before linting
'lint', // wait till lint is done before running minify
'minify', // wait till minify is done before starting laundry and dinner
['do-laundry', // do laundry and cook dinner at the same time
'cook-dinner'],
'bath-cat', // don't bath the cat till both laundry and dinner are done
callback
));
https://www.npmjs.com/package/run-sequence
Use synchronous mode for glob
Then return the result of gulp.src:
gulp.task('render', function() {
var appJsFiles = _.map(glob.sync('src/**/*.js'), function(f) {
return f.slice(6);
});
// Render the file.
return gulp.src('src/template.html')
.pipe(template({
scripts: appJsFiles,
styles: ['style1.css', 'style2.css', 'style3.css']
}))
.pipe(gulp.dest(config.build_dir));
});
you can add something like this after the last pipe
.pipe(gulp.dest(FINAL_DEST))
.on('end', () => gulp.src(['build']) .pipe(clean()))
It should work like this ....
gulp.src("...")
.pipe(gulp.dest("..."))
.on('end', () => {
gulp.src("...")
.pipe(gulp.dest("..."))
})
.on('end', () => {
gulp.src("...")
.pipe(gulp.dest("..."))
})
.on('end', () => {
gulp.src("...")
.pipe(gulp.dest("..."))
})
but it doesn't ....
instead it wors like this ....
gulp.src("...")
.pipe(gulp.dest("..."))
.on('end', () => {
gulp.src("...")
.pipe(gulp.dest("..."))
.on('end', () => {
gulp.src("...")
.pipe(gulp.dest("..."))
.on('end', () => {
gulp.src("...")
.pipe(gulp.dest("..."))
})
})
})
I have added the following registerTask calls to this Gruntfile.js
grunt.task.registerTask('runDebug', 'Run the app with debug flag', function() {
var done = this.async();
grunt.util.spawn({
cmd: 'node',
args: ['--debug', './node_modules/nodemon/nodemon.js', 'index.js'],
opts: {
stdio: 'inherit'
}
}, function (error, result, code) {
if (error) {
grunt.log.write (result);
grunt.fail.fatal(error);
}
done();
});
grunt.log.writeln ('node started');
grunt.util.spawn({
cmd: 'node-inspector',
args: ['&'],
opts: {
//cwd: current working directory
}
},
function (error, result, code) {
if (error) {
grunt.log.write (result);
grunt.fail.fatal(error);
}
done();
});
grunt.log.writeln ('inspector started');
});
grunt.task.registerTask('debug', ['runDebug', 'compile', 'watch']);
The new debug task is similar to the existing server task. However, grunt server command runs compile, watch, and runNode tasks, whereas grunt debug command only runs runDebug task.
What am I missing here? Why aren't the compile and watch tasks run with grunt debug command.
Your code is calling the done() function returned by this.async() more than once. This could be confusing Grunt. I would suggest calling a function of your own which could named spawned() instead of calling done() directly in your callbacks. The function could be something like:
var expected_spawns = 2;
function spawned() {
if (!--expected_spawns)
done();
if (expected_spawns < 0)
throw new Error("too many spawns!") // Or some of Grunt's fail functions.
}
This way done() will be called once.