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));
});
Related
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());
});
I have my full gulp file below. It compiles my CSS, and then uses another function to take my CSS file, minify it, and then copy it over to another folder "assets/css".
The file I'm looking to exclude is mainStyle. If I don't exclude this, I get a perpetual loop in my watch task.
When I run the file, because I have the !mainStyle toward the bottom, I get the error "TypeError: pattern.indexOf is not a function".
var themename = 'themename';
var gulp = require('gulp'),
// Prepare and optimize code etc
autoprefixer = require('autoprefixer'),
browserSync = require('browser-sync').create(),
image = require('gulp-image'),
jshint = require('gulp-jshint'),
postcss = require('gulp-postcss'),
sass = require('gulp-sass'),
sourcemaps = require('gulp-sourcemaps'),
cleanCSS = require('gulp-clean-css'),
// Only work with new or updated files
newer = require('gulp-newer'),
// Name of working theme folder
root = '../' + themename + '/',
scss = root + 'sass/',
js = root + 'js/',
img = root + 'images/',
languages = root + 'languages/';
mainStyle = root + 'style.css';
// CSS via Sass and Autoprefixer
gulp.task('css', function() {
return gulp.src(scss + '{style.scss,rtl.scss}')
.pipe(sourcemaps.init())
.pipe(sass({
outputStyle: 'expanded',
indentType: 'tab',
indentWidth: '1'
}).on('error', sass.logError))
.pipe(postcss([
autoprefixer('last 2 versions', '> 1%')
]))
.pipe(sourcemaps.write(scss + 'maps'))
.pipe(gulp.dest(root));
});
gulp.task('minify-css', () => {
return gulp.src(mainStyle)
.pipe(cleanCSS({level: {1: {specialComments: 0}}}, (details) => {
console.log(`${details.name}: ${details.stats.originalSize}`);
console.log(`${details.name}: ${details.stats.minifiedSize}`);
}))
.pipe(gulp.dest(root + '/assets/css/'));
});
// Optimize images through gulp-image
gulp.task('images', function() {
return gulp.src(img + 'RAW/**/*.{jpg,JPG,png}')
.pipe(newer(img))
.pipe(image())
.pipe(gulp.dest(img));
});
// JavaScript
gulp.task('javascript', function() {
return gulp.src([js + '*.js'])
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(gulp.dest(js));
});
// Watch everything
gulp.task('watch', function() {
browserSync.init({
open: 'external',
proxy: 'example.local/',
port: 8080
});
gulp.watch([ root + '**/*.css', root + '**/*.scss', !mainStyle ], ['css']);
gulp.watch(js + '**/*.js', ['javascript']);
gulp.watch(img + 'RAW/**/*.{jpg,JPG,png}', ['images']);
gulp.watch(root + '**/*').on('change', browserSync.reload);
gulp.watch(root + 'style.css', ['minify-css'])
});
// Default task (runs at initiation: gulp --verbose)
gulp.task('default', ['watch']);
The way you have your rule written is actually returning false. Change it to a string so it's properly interpreted as a minimatch rule.
gulp.watch([ root + '**/*.css', root + '**/*.scss', `!${mainStyle}` ], ['css']);
Below is the js minify task:
gulp.task('jsBuildDev', function () {
return gulp.src(['js/build/*.js'])
.pipe(concat('build.js'))
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(hash())
.pipe(uglify())
.pipe(rename(function (path) { path.basename += "-min" }))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./js/'))
});
Below is the sass minify task:
gulp.task('sass', function () {
return gulp
.src('./scss/style.scss')
.pipe(hash())
.pipe(sourcemaps.init())
.pipe(sass(sassOptions).on('error', sass.logError))
.pipe(sourcemaps.write('./'))
.pipe(autoprefixer(autoprefixerOptions))
.pipe(rename(function (path) { path.basename += "-min" }))
.pipe(gulp.dest('./css/'));
});
Here is a task which replaces out the html that's needed:
gulp.task('htmlreplace', function () {
gulp.src('pre-built-index.html')
.pipe(htmlreplace({
'css': 'styles.min.css',
'js': 'js/build.min.js'
}))
.pipe(rename('index.html'))
.pipe(gulp.dest('./'));
});
All three tasks work fine but, how do I merge the htmlreplace into the jsBuildDev task; so that the 'js': 'js/build.min.js' is equal to the hashed name found in the js minify task? So it looks like'js': 'js/build-86fb603a0875705077f6491fee6b5e07-min' instead of 'js': 'js/build.min.js'.
The same applies for the sass task but I assume both with be resolved with the same solution.
I'm sure there are more elegant ways to do this but the following works:
var gulp = require('gulp');
var hash = require('gulp-hash');
var rename = require('gulp-rename');
var htmlreplace = require('gulp-html-replace');
var hashedJS;
var hashedCSS;
gulp.task('jsBuildDev', function () {
return gulp.src('./app.js')
.pipe(hash())
.pipe(rename(function (path) {
path.basename += "-min";
hashedJS = path.basename + '.js';
console.log("hashedJS = " + hashedJS);
}))
.pipe(gulp.dest('./js'));
});
gulp.task('sass', function () {
return gulp.src('./app.scss')
.pipe(hash())
// .pipe(sourcemaps.init())
// .pipe(sass(sassOptions).on('error', sass.logError))
// .pipe(sourcemaps.write('./'))
// .pipe(autoprefixer(autoprefixerOptions))
.pipe(rename(function (path) {
path.basename += "-min";
hashedCSS = path.basename + '.css';
console.log("hashedCSS = " + hashedCSS);
}))
.pipe(gulp.dest('./css'));
});
gulp.task('htmlreplace', ['jsBuildDev', 'sass'], function () {
return gulp.src('app.html')
.pipe(htmlreplace({
'css': hashedCSS,
'js': hashedJS
}))
.pipe(rename('index.html'))
.pipe(gulp.dest('./'));
});
It creates a couple of variables that are assigned in the dependent tasks that handle js and scss processing. You run the whole thing with "gulp htmlreplace". All three tasks must be run together for the variables to work.
Otherwise, you could set the replacement hashed filenames by retrieving their filenames after they are created. By looking in their folders at the most recently created file for example. But what I have done seems easier though not especially elegant.
Let me know if this works for you.
I am concatenating and minifying js files using gulp. But files are not concatenating in the order it should be, like this is the order I want :
var scriptFiles = [
'assets/lib/js/angular.min.js',
'assets/lib/js/angular-sanitize.min.js',
'assets/lib/js/angular-route.min.js',
'assets/lib/js/angular-ui-router.min.js',
'assets/lib/js/angular-css.min.js',
'assets/lib/js/angular-select.min.js',
'assets/lib/js/duration.js',
'assets/lib/js/ui-codemirror.min.js',
];
The problem is, after concatenation ui-codemirror.min.js file goes to top of the compiled file, which breaks the javascript since angular.js is not initialized/loaded.
To solve this problem I tried to use "gulp-order" package and wrote this gulp task:
gulp.task('lib-js', function() {
return gulp.src(scriptFiles)
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(order(scriptFiles, { base: './' }))
.pipe(concat('vendor.js'))
.pipe(gulp.dest('assets/compiled/js'))
.pipe(rename({ suffix: '.min' }))
.pipe(uglify())
.pipe(gulp.dest('assets/compiled/js'))
});
I passed the same scriptFiles array to try to preserve the order. But it did not work.
Then I tried the streamqueue plugin, and change the task:
gulp.task('lib-js', function() {
return streamqueue({ objectMode: true },
gulp.src('assets/lib/js/angular.min.js'),
gulp.src('assets/lib/js/angular-sanitize.min.js'),
gulp.src('assets/lib/js/angular-route.min.js?v=20161018-1'),
gulp.src('assets/lib/js/angular-ui-router.min.js'),
gulp.src('assets/lib/js/angular-css.min.js'),
gulp.src('assets/lib/js/angular-select.min.js'),
gulp.src('assets/lib/js/jquery-2.1.1.min.js'),
gulp.src('assets/lib/js/material.min.js'),
gulp.src('assets/lib/js/ocLazyLoad.min.js'),
gulp.src('assets/lib/js/duration.js'),
gulp.src('assets/lib/js/ui-codemirror.min.js')
)
.pipe(concat('vendor.js'))
.pipe(gulp.dest('assets/compiled/js'))
.pipe(rename({ suffix: '.min' }))
.pipe(uglify())
.pipe(gulp.dest('assets/compiled/js'))
});
This did not work either. I am unable to debug why this is happening. "order" and "streamqueue" plugin had not effect on the output. Any idea/solution?
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('./'));
});