Gulp rebuild parent js files when partials are modified - javascript

I have the following gulp task for my js files:
gulp.task("js", () => {
return gulp
.src([
src_js_folder + "*.js",
src_js_folder + "**/*.js" // Include partial js files, to trigger the build when there is a change in them
], { since: gulp.lastRun("js"), read: false }) // no need of reading file because browserify does.
.pipe(dependents())
.pipe(filter(['**'].concat( // filter partial js files (inside subdirectories)
readdirSync(src_js_folder, { withFileTypes: true }) // list all js files inside subdirectories
.filter(dirent => dirent.isDirectory())
.map(dirent => `!${src_js_folder.substring(2) + dirent.name}/**/*.js`)
)))
.pipe(plumber({errorHandler: reportError}))
.pipe(tap(function (file) {
logger.info('bundling ' + file.path);
// replace file contents with browserify's bundle stream
file.contents = browserify(file.path, {debug: true})
.bundle();
}))
.pipe(buffer()) // transform streaming contents into buffer contents (because gulp-sourcemaps does not support streaming contents)
.pipe(beautify.js({ indent_size: 2 }))
.pipe(gulp.dest(dist_folder + "js"))
.pipe(browserSync.stream());
});
And this watcher for browserSync:
gulp.watch([src_js_folder + '**/*.js'], gulp.series("dev")).on("change", browserSync.reload);
So what I did in my task is I included the partial js files in the src as well, and then filter them to not build them.
The problem I'm having is that when I update a parent js file that includes these partials gulp is rebuilding them, but when I change something in the partials, gulp doesn't build the parent js files that include these partials.
for example if I change the following file: src_js_folder + 'somefile.js', gulp successfully rebuild the file:
[16:27:34] Starting 'js'...
[16:27:34] bundling ...\www-src\assets\scripts\global.V3-1.js
[Browsersync] 1 file changed (global.V3-1.js)
[16:27:34] Finished 'js' after 597 ms
But when I change something in the partial js file, for example: src_js_folder + '/subdir/_somePartialFile.js', gulp does nothing:
[16:29:21] Starting 'js'...
[16:29:21] Finished 'js' after 10 ms
The logic I followed in my task is the same as my sass task:
gulp.task("sass", () => {
return (
gulp.src([
src_sass_folder + "*.scss",
src_sass_folder + "**/*.scss"
], {
since: gulp.lastRun("sass"),
})
.pipe(dependents())
.pipe(filter(['**'].concat( // filter partial SASS files (inside subdirectories) this is used to not build the partial SASS files
readdirSync(src_sass_folder, { withFileTypes: true }) // selector for all partial SASS files
.filter(dirent => dirent.isDirectory())
.map(dirent => `!${src_sass_folder.substring(2) + dirent.name}/**/*.scss`)
)))
.pipe(debug({title: "sass-debug:", showCount: false}))
.pipe(sourcemaps.init())
.pipe(plumber({errorHandler: reportError}))
.pipe(sass({ fiber: Fiber })) // call asynchronous importers from the synchronous code path (for using Dart Sass)
.pipe(autoprefixer())
.pipe(minifyCss({debug: true}, (details) => {
console.log(`Original size: ${details.stats.originalSize}, Minified size: ${details.stats.minifiedSize}, Efficiency: ${details.stats.efficiency}`);
}))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest(dist_folder + "Content"), {overwrite: true})
.pipe(browserSync.stream({match: '**/*.css'}))
);
});
gulp.watch([src_sass_folder + '**/*.scss'], gulp.series("sass"));
This is successfully working when I change something in the partial sass files, because when a partial sass file changes, gulp only builds the sass files that include them.
How can I make my js task work properly?
Edit:
My parent js files are looking like this:
bundle = require('./subdir/_partial1.js');
bundle = require('./subdir/_partial2.js');
bundle = require('./subdir/_partial3.js');

I was able to use gulp-dependents for setting up JS(ES6) incremental build.
For that I had to write the regex for extracting the file paths from the import statements and fortunately I found a GitHub Gist which had the required regex. Here is the gulp-dependents config for parsing JS import statements:
const jsDependentsConfig = {
".js":{
postfixes: ['.js'],
parserSteps: [
/import(?:["'\s]*(?:[\w*${}\n\r\t, ]+)from\s*)?["'\s]*(.*[#\w_-]+)["'\s].*;?$/gm,
function(path){
// Remove file extension, if any
path = path.replace(/\.js$/,'');
// Local packages
paths = [`${path}/index.js`,`${path}.js`];
return paths;
},
]
},
};
Once this was implemented, using gulp-dependents was fairly straightforward.
Final code:
const { src, dest, lastRun } = require('gulp');
const dependents = require('gulp-dependents');
const filter = require('gulp-filter');
const jsDependentsConfig = {
".js":{
postfixes: ['.js'],
parserSteps: [
/import(?:["'\s]*(?:[\w*${}\n\r\t, ]+)from\s*)?["'\s]*(.*[#\w_-]+)["'\s].*;?$/gm,
function(path){
// Remove file extension, if any
path = path.replace(/\.js$/,'');
// Local packages
paths = [`${path}/index.js`,`${path}.js`];
return paths;
},
]
},
};
function scripts(){
// Ref: https://stackoverflow.com/a/35413106
const filterPartials = filter([
`**/src/js/**/!(_)*.js`, // select all js files
`!**/src/js/**/_*/`, // exclude all folder starting with _
`!**/src/js/**/_*/**/*` //exclude files/subfolders in folders starting with '_'
]);
return src('src/js/**/*.js',{ since: lastRun(scripts) })
.pipe(gulpIf(!Config.production,
dependents(jsDependentsConfig)
))
.pipe(filterPartials)
// other pipes
.pipe(dest('assets/js'))
;
}

Is dependents gulp-dependents?
By default, gulp-dependents do not support .js files.
When 'partial sass file' is changed, gulp-dependents add 'parent sass file' by dependencyMap cached before. Then filter remove the 'partial sass file'. The 'parent sass file' is passed to gulp-sass.
When 'partial js file' is changed, gulp-dependents do not add any file. Then filter remove the 'partial js file'. No file last.
Cache dependencies by browserify pipeline.
function DependentsJS () {
const fileDepMap = new Map()
return {
browserify (filePath, opts) {
for (let deps of fileDepMap) {
deps[1].delete(filePath)
}
const b = browserify(filePath, opts)
b.pipeline.on('file', function (file) {
if (!fileDepMap.has(file)) {
fileDepMap.set(file, new Set())
}
fileDepMap.get(file).add(filePath)
})
return b
},
plugin: () => through.obj(function (file, encoding, callback) {
this.push(file)
if (fileDepMap.has(file.path)) {
fileDepMap.get(file.path).forEach(pFile => {
this.push({
cwd: file.cwd,
path: pFile
})
});
}
callback();
}, function (callback) {
callback()
})
}
}
USE:
const dependentsJS = DependentsJS()
gulp.task("js", () => {
return gulp
.src([
src_js_folder + "*.js",
src_js_folder + "**/*.js"
], { since: gulp.lastRun("js"), read: false })
// .pipe(dependents())
.pipe(dependentsJS.plugin()) // <<< USE `dependentsJS.plugin()`
.pipe(filter(['**'].concat(
readdirSync(src_js_folder, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => `!${src_js_folder.substring(2) + dirent.name}/**/*.js`)
)))
.pipe(plumber({errorHandler: reportError}))
.pipe(tap(function (file) {
logger.info('bundling ' + file.path);
// file.contents = browserify(file.path, {debug: true})
file.contents = dependentsJS.browserify(file.path, {debug: true}) // <<< USE `dependentsJS.browserify`
.bundle();
}))
.pipe(buffer())
.pipe(beautify.js({ indent_size: 2 }))
.pipe(gulp.dest(dist_folder + "js"))
.pipe(browserSync.stream());
});

Related

gulp merge multiple JS into one

I have a task gulp dev that should merge a array of JS files into one in dist/js however I cant seem to get the gulp command to create the file dist/js, Can anyone see where I have gone wrong been at this 9 hrs now.
Thanks
Gulp file
gulp.task("dev", function () {
// set the dev config (cache in utils.js)
utils.setConfig({
env: "dev",
watch: true,
notify: true,
tasks: ["js", "css", "copy", "bower", "svg-sprite"]
});
// build with this config
utils.build();
});
then ...
task/js
var gulp = require("gulp"),
utils = require("./utils"),
config = utils.loadConfig(),
gulpif = require("gulp-if"),
fs = require("fs"),
uglify = require("gulp-uglify"),
sourcemaps = require("gulp-sourcemaps"),
browserify = require("browserify"),
shim = require("browserify-shim"),
through2 = require("through2"),
babelify = require("babelify"),
minify = require('gulp-minify'),
replaceName = require('gulp-replace-name');
// dev/default settings
utils.setTaskConfig("js", {
default: {
// Pass array instead of single file!
src: [
config.root + "/js/index.js",
config.root + "/js/owlCarousel.js",
config.root + "/js/search/search.js",
// Angular 1.x doesn't play well with CommonJS modules :(
config.root + "/js/search/angular-1.5.1.min.js",
config.root + "/js/search/angular-animate-1.5.1.min.js",
config.root + "/js/search/angular-sanitize-1.5.1.min.js"
],
dest: config.dest + "/js",
// js uglify options, to skip, set value to false or omit entirely
// otherwise, pass options object (can be empty {})
uglify: false,
// browserify options
browserify: {
debug: true // enable sourcemaps
}
},
prod: {
browserify: {},
// uglify javascript for production
uglify: {}
}
});
// register the watch
utils.registerWatcher("js", [
config.root + "/js/**/*.js",
config.root + "/js/**/*.jsx"
]);
/* compile application javascript */
gulp.task("js", function(){
var js = utils.loadTaskConfig("js");
// for browserify usage, see https://medium.com/#sogko/gulp-browserify-the-gulp-y-way-bb359b3f9623
// ^^ we can't use vinyl-transform anymore because it breaks when trying to use b.transform()
// https://github.com/sogko/gulp-recipes/tree/master/browserify-vanilla
var browserifyIt = through2.obj(function (file, enc, callback){
// https://github.com/substack/node-browserify/issues/1044#issuecomment-72384131
var b = browserify(js.browserify || {}) // pass options
.add(file.path) // this file
.transform(babelify)
.transform(shim);
b.bundle(function(err, res){
if (err){
callback(err, null); // emit error so drano can do it's thang
}
else {
file.contents = res; // assumes file.contents is a Buffer
callback(null, file); // pass file along
}
});
});
return gulp.src(js.src)
.pipe(utils.drano())
.pipe(browserifyIt)
.pipe(sourcemaps.init({ loadMaps: true })) // loads map from browserify file
.pipe(gulpif((js.uglify), uglify(js.uglify)))
.pipe(minify(({
ignoreFiles: ['*.min.js', 'search.js']
})))
.pipe(sourcemaps.write("./"))
.pipe(gulp.dest(js.dest));
});
gulp.src([
'./dist/js/*.min.js',
'./dist/js/*-min.js',
'./dist/js/amcharts.js',
'./dist/js/amstock.js',
'./dist/js/table-childssorter.js',
'./dist/js/serial.js',
'./dist/js/vendor.js',
'./dist/js/jquery-3.1.1.js',
'./dist/js/jquery.tablesorter.js',
'./dist/js/search.js'
])
.pipe(replaceName(/\-min.js/g, '.js'))
.pipe(gulp.dest('./dist/minified/js'));

Gulp multiple src

I am trying to inject a concatenated css file into my index.html using 'gulp-inject' and then version my files. I can get this working separately but what I am trying to do is compile everything in a single task so that I can run assetVersion() against certain files and the index.html. The problem I have is that my 'styles.min.css' is not written to disk and I'm not sure how to combine the streams to achieve my goal. Can anyone help?
gulp.task('compile',['clean'],function() {
// CSS - minify, concat to one file and copy
gulp.src(paths.css + '*', { base: paths.base })
.pipe(cleanCSS())
.pipe(concat('styles.min.css'))
.pipe(gulp.dest(paths.dist + paths.css));
// Images - minify and copy
gulp.src(paths.images + '*', { base: paths.base })
.pipe(imagemin())
.pipe(gulp.dest(paths.dist));
// Index.html - inject the concatenated css, asset version and copy to dist
gulp.src(paths.index)
.pipe(inject(gulp.src(paths.dist + paths.css + 'styles.min.css', {read: false})))
.pipe(debug())
.pipe(assetVersion())
.pipe(gulp.dest(paths.dist));
});
You can use run-sequence to force run each task synchronously.
var runSequence = require('run-sequence')
gulp.task('compile', ['clean'], function() {
return runSequence(['compile:css', 'compile:images'], 'compile:version');
});
gulp.task('compile:css', function() {
// CSS - minify, concat to one file and copy
return gulp.src(paths.css + '*', { base: paths.base })
.pipe(cleanCSS())
.pipe(concat('styles.min.css'))
.pipe(gulp.dest(paths.dist + paths.css));
});
gulp.task('compile:images', function() {
// Images - minify and copy
return gulp.src(paths.images + '*', { base: paths.base })
.pipe(imagemin())
.pipe(gulp.dest(paths.dist));
});
gulp.task('compile:version', function() {
// Index.html - inject the concatenated css, asset version and copy to dist
return gulp.src(paths.index)
.pipe(inject(gulp.src(paths.dist + paths.css + 'styles.min.css', { read: false })))
.pipe(debug())
.pipe(assetVersion())
.pipe(gulp.dest(paths.dist));
});

Local changes to file in node_modules (Angular 2)

I know that this can be a very stupid question, but I can't find matches with other posts on stackoverflow...
So: Can I modify a file of an external module , just save the file and do something that my app can listen?
At the moment, i'm trying ti change some scss style at the ng2-datepicker module (inside node_modules folder), but if I save and the launch ng serve, changes will not affect my project.
I know it's a simple problem, but i don't know the background architecture of an Angular2 project.
Thanks in advance.
(ps I've seen that i can fork the git and then do something like npm install.
Very interesting, but i also want to know how to have the same result in local)
If you are using gulp file you can tell the changed lib file path to copy to build folder check gulp.task('copy-libs') in code below git repo for angular2-tour-of-heroes using gulp
const gulp = require('gulp');
const del = require('del');
const typescript = require('gulp-typescript');
const tscConfig = require('./tsconfig.json');
const sourcemaps = require('gulp-sourcemaps');
const tslint = require('gulp-tslint');
const browserSync = require('browser-sync');
const reload = browserSync.reload;
const tsconfig = require('tsconfig-glob');
// clean the contents of the distribution directory
gulp.task('clean', function () {
return del('dist/**/*');
});
// copy static assets - i.e. non TypeScript compiled source
gulp.task('copy:assets', ['clean'], function() {
return gulp.src(['app/**/*', 'index.html', 'styles.css', '!app/**/*.ts'], { base : './' })
.pipe(gulp.dest('dist'))
});
// copy dependencies
gulp.task('copy:libs', ['clean'], function() {
return gulp.src([
'node_modules/angular2/bundles/angular2-polyfills.js',
'node_modules/systemjs/dist/system.src.js',
'node_modules/rxjs/bundles/Rx.js',
'node_modules/angular2/bundles/angular2.dev.js',
'node_modules/angular2/bundles/router.dev.js',
'node_modules/node-uuid/uuid.js',
'node_modules/immutable/dist/immutable.js'
'yourpath/changedFileName.js'
])
.pipe(gulp.dest('dist/lib'))
});
// linting
gulp.task('tslint', function() {
return gulp.src('app/**/*.ts')
.pipe(tslint())
.pipe(tslint.report('verbose'));
});
// TypeScript compile
gulp.task('compile', ['clean'], function () {
return gulp
.src(tscConfig.files)
.pipe(sourcemaps.init())
.pipe(typescript(tscConfig.compilerOptions))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('dist/app'));
});
// update the tsconfig files based on the glob pattern
gulp.task('tsconfig-glob', function () {
return tsconfig({
configPath: '.',
indent: 2
});
});
// Run browsersync for development
gulp.task('serve', ['build'], function() {
browserSync({
server: {
baseDir: 'dist'
}
});
gulp.watch(['app/**/*', 'index.html', 'styles.css'], ['buildAndReload']);
});
gulp.task('build', ['tslint', 'compile', 'copy:libs', 'copy:assets']);
gulp.task('buildAndReload', ['build'], reload);
gulp.task('default', ['build']);

How to fix, ENOENT error: no such file or directory when the "fonts" task mentioned below in the gulpfile.js starts

Following is the gulpfile.js which i have. Am getting ENOENT error: no such file or directory, when the "fonts" task starts, while running "gulp build-qa" command. Howeve, The build is successful every alternate time but not every time consistently. How to fix this issue ? I have made sure that every task has return, even though it is not working.
var gulp = require('gulp'),
del = require('del'),
pump = require('pump'),
flatten = require('gulp-flatten'),
usemin = require('gulp-usemin'),
htmlmin = require('gulp-htmlmin'),
cssmin = require('gulp-clean-css'),
uglifyjs = require('gulp-uglify'),
stripdebug = require('gulp-strip-debug'),
ngannotate = require('gulp-ng-annotate'),
rev = require('gulp-rev'),
concat = require('gulp-concat'),
sourcemaps = require('gulp-sourcemaps'),
connect = require('gulp-connect');
var ENV = process.env.NODE_ENV || 'dev',
APP_DIR = 'app',
BUILD_DIR = 'build',
SRC_DIR = 'source',
ZIP_DIR = 'packed';
gulp.task('clean', function () {
return del(
[BUILD_DIR + '/**', ZIP_DIR + '/**'],
{ dryRun: false })
.then(
// paths => { console.log('Files and folders that would be deleted:\n', paths.join('\n'));}
);
});
gulp.task('build-qa',['clean','fonts','images','partials','usemin-qa'], function(){});
/* Html processing, minifying and copying to build folder */
gulp.task('partials', function () {
return gulp.src(['**/*.html','!index.html'], {cwd: APP_DIR})
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(gulp.dest(BUILD_DIR));
});
/* Images copying to build folder */
gulp.task('images', function () {
return gulp.src('**/*.{jpg,png,gif,svg}')
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/images'));
});
/* Find all font type files in 'fonts' folders and move to fonts build folder with flattened structure */
gulp.task('fonts', function () {
return gulp.src('**/fonts/*.{ttf,woff,eot,svg}')
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/fonts'));
});
gulp.task('usemin-qa', function () {
return gulp.src('app/index.html')
.pipe(usemin({
// pipelines are named in the HTML, like js_lib and js_app below; html is the src file
html: [htmlmin({ collapseWhitespace: true, quotes: true, empty: true, spare: true, loose: true })],
css_lib: [
cssmin(),
'concat',
rev()
],
css: [
cssmin(),
'concat',
rev()
],
js_lib: [
ngannotate({remove: false, add: true}),
uglifyjs(),
rev()
],
js_app: [
sourcemaps.init(),
'concat',
ngannotate({remove: true, add: true}),
uglifyjs(),
rev()
]
}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(BUILD_DIR))
});
ENOENT errors in gulp are almost always caused by some kind of race condition.
The first problem is that all your tasks are sourcing files not just from your source folders. For example '**/fonts/*.{ttf,woff,eot,svg}' selects font files from anywhere in your project including your BUILD_DIR. You need to exclude BUILD_DIR in all your tasks:
gulp.task('fonts', function () {
return gulp.src(['**/fonts/*.{ttf,woff,eot,svg}',
'!'+BUILD_DIR+'/**'])
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/fonts'));
});
The next problem is this line:
gulp.task('build-qa',['clean','fonts','images','partials','usemin-qa'], function(){});
This doesn't do what you think it does. All those tasks aren't executed in order, they're all executed at the same time. That means your clean task is deleting files in BUILD_DIR while your other tasks are busy copying files into that same directory.
You have two options:
(1) Place a dependency hint on all the other tasks. For example your fonts task would have to look like this:
gulp.task('fonts', ['clean'], function () {
return gulp.src(['**/fonts/*.{ttf,woff,eot,svg}',
'!' + BUILD_DIR + '/**'])
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/fonts'));
});
This makes sure that all the other tasks run only after the clean task has finished.
(2) Use run-sequence in your build-qa task:
var runSequence = require('run-sequence');
gulp.task('build-qa', function(cb) {
runSequence('clean',['fonts','images','partials','usemin-qa'], cb);
});
This runs the clean task first and then all the other tasks in parallel.

Is there a way I can see which files are processed by a gulp watch?

I have this gulp task:
var abc = {
src: [
'app/**/abc.html',
'app/**/def.html'
],
};
gulp.task('watchHTMLs', function () {
gulp.watch(abc.src, function (file) {
return gulp.src(file.path, { base: process.cwd() })
.pipe(rename({ basename: 'base' }))
.pipe(gulp.dest('./'));
});
});
Is there some way that I can see an output from where I run the gulp script that would show which file is changed?
Something like this:
> cmd.exe /c gulp -b "C:\H\user\user" --color --gulpfile "C:\H\user\user\Gulpfile.js" watchHTMLs
[11:37:29] Using gulpfile C:\H\user\user\Gulpfile.js
[11:37:29] Starting 'watchHTMLs'...
[11:37:29] Finished 'watchHTMLs' after 60 ms
Found file /app/xxx/abc.html
Found file /app/yyy/abc.html
Found file /app/xxx/def.html
Take a look at gulp-print:
gulp.watch(abc.src, function (file) {
return gulp.src(file.path, { base: process.cwd() })
.pipe(print(function (file) {
return "Found file "+file;
})
.pipe(rename({ basename: 'base' }))
.pipe(gulp.dest('./'));
});
gulp.watch returns an EventEmitter so you can simply listen to changes:
var watcher = gulp.watch(abc.src, 'task');
watcher.on('change', event => {
console.log(event.path);
});
Alternatively, if you want to follow your approach I recommend using gulp-debug:
var debug = require('gulp-debug');
...
gulp.watch(abc.src, function (file) {
return gulp.src(file.path, { base: process.cwd() })
.pipe(debug())
.pipe(rename({ basename: 'base' }))
.pipe(gulp.dest('./'));
});

Categories

Resources