diff --git a/CHANGELOG.md b/CHANGELOG.md index 63cf6d4..1deeaaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # Changelog +#### 2.4.1 +* Added `debugIncludes` param + #### 2.4.0 * Lots of community fixes graceously assembled and merged by [KenEucker](https://github.com/KenEucker) +* Dependencies update after three years. +* The plugin now supports both gulp 3 and gulp version 4. +* Merged support for separated includes between files, by [PFight](https://github.com/PFight) #### 2.3.1 * Isolated include to solve some scoping issues that happens when running multiple includes in parallel. diff --git a/README.md b/README.md index 74d7aa5..a382e04 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,23 @@ -#gulp-include [![NPM version][npm-image]][npm-url] ![Travis build][travis-image] ->Makes inclusion of files a breeze. -Enables functionality similar to that of snockets / sprockets or other file insertion compilation tools. - -> Made for gulp 3 +# gulp-include [![NPM version][npm-image]][npm-url] ![Travis build][travis-image] + + + + + + + + + + + + + + + + +
Packagegulp-include
DescriptionMakes inclusion of files a breeze. Enables functionality similar to that of snockets / sprockets or other file insertion compilation tools.
Node Version>= 6.0.0
Gulp Version>= 3.0.0
+ +> Works with gulp 3 and gulp 4 ## Features * Concatenate files with full control @@ -35,13 +50,13 @@ gulp.task("default", ["scripts"]); ## Options - `extensions` (optional) - * Takes a `String` or an `Array` of extensions. + * Takes a `String` or an `Array` of extensions. eg: `"js"` or `["js", "coffee"]` - * If set, all directives that does not match the extension(s) will be ignored + * If set, all directives that does not match the extension(s) will be ignored - `includePaths` (optional) - * Takes a `String` or an `Array` of paths. + * Takes a `String` or an `Array` of paths. eg: `__dirname + "/node_modules"` or `[__dirname + "/assets/js", __dirname + "/bower_components"]` * If set, `gulp-include` will use these folders as base path when searching for files. @@ -58,6 +73,11 @@ gulp.task("default", ["scripts"]); So, if file required several times inside one file (or inside required by it files), then dublicates will be ignored. But when another file will begin processing, all information about required files from previuos file will be discarded. + +- `debugIncludes` (optional) + * Boolean, `false` by default + * Set this to `true` if you want `gulp-include` to output included filenames to the console. + #### Example options usage: ```js gulp.src("src/js/main.js") @@ -67,7 +87,8 @@ gulp.src("src/js/main.js") includePaths: [ __dirname + "/bower_components", __dirname + "/src/js" - ] + ], + debugIncludes: true })) .pipe(gulp.dest("dist/js")); ``` @@ -94,7 +115,7 @@ Example directives: The contents of the referenced file will replace the file. ### `require` vs. `include` -A file that is included with `require` will only be included if it has not been included before. Files included with `include` will _always_ be included. +A file that is included with `require` will only be included if it has not been included before. Files included with `include` will _always_ be included. For instance, let's say you want to include `jquery.js` only once, and before any of your other scripts in the same folder. ```javascript //=require vendor/jquery.js diff --git a/index.js b/index.js index 2e6ab60..7c28e4e 100644 --- a/index.js +++ b/index.js @@ -1,327 +1,342 @@ var fs = require('fs'), - path = require('path'), - es = require('event-stream'), - glob = require('glob'), - PluginError = require('plugin-error'), - colors = require('ansi-colors'), - applySourceMap = require('vinyl-sourcemaps-apply'), - stripBom = require('strip-bom'); + path = require('path'), + es = require('event-stream'), + glob = require('glob'), + PluginError = require('plugin-error'), + colors = require('ansi-colors'), + applySourceMap = require('vinyl-sourcemaps-apply'), + stripBom = require('strip-bom'); module.exports = function (params) { - params = params || {}; - - var SourceMapGenerator = require('source-map').SourceMapGenerator; - var SourceMapConsumer = require('source-map').SourceMapConsumer; - - var extensions = null, // The extension to be searched after - globalIncludedFiles = [], // For track of what files have been included over all files - includePaths = false, // The paths to be searched - hardFail = false, // Throw error when no match - separateInputs = false; // Process each input file separately when using `require` logic. - - // Check for includepaths in the params - if (params.includePaths) { - if (typeof params.includePaths == "string") { - // Arrayify the string - includePaths = [params.includePaths]; - } else if (Array.isArray(params.includePaths)) { - // Set this array to the includepaths - includePaths = params.includePaths; - } - } - - if (params.separateInputs) { - separateInputs = true; - } - - // Toggle error reporting - if (params.hardFail != undefined) { - hardFail = params.hardFail; - } - - if (params.extensions) { - extensions = typeof params.extensions === 'string' ? [params.extensions] : params.extensions; - } - - function include(file, callback) { - var includedFiles = separateInputs ? [] : globalIncludedFiles; - - if (file.isNull()) { - return callback(null, file); - } - - if (file.isStream()) { - throw new PluginError('gulp-include', 'stream not supported'); - } - - if (file.isBuffer()) { - var result = processInclude(String(file.contents), file.path, file.sourceMap, includedFiles); - file.contents = new Buffer(result.content); - - if (file.sourceMap && result.map) { - if (Object.prototype.toString.call(result.map) === '[object String]') { - result.map = JSON.parse(result.map); - } - - // relative-ize the paths in the map - result.map.file = path.relative(file.base, result.map.file); - result.map.sources.forEach(function (source, q) { - result.map.sources[q] = path.relative(file.base, result.map.sources[q]); - }); - - applySourceMap(file, result.map); - } - } - - callback(null, file); - } - - function processInclude(content, filePath, sourceMap, includedFiles) { - var matches = content.match(/^(\s+)?(\/\/|\/\*|\#|\<\!\-\-)(\s+)?=(\s+)?(include|require)(.+$)/mg); - var relativeBasePath = path.dirname(filePath); - - if (!matches) return { - content: content, - map: null - }; - - // Apply sourcemaps - var map = null, - mapSelf, lastMappedLine, currentPos, insertedLines; - if (sourceMap) { - map = new SourceMapGenerator({ - file: unixStylePath(filePath) - }); - lastMappedLine = 1; - currentPos = 0; - insertedLines = 0; - - mapSelf = function (currentLine) { // maps current file between matches and after all matches - var currentOrigLine = currentLine - insertedLines; - - for (var q = (currentLine - lastMappedLine); q > 0; q--) { - map.addMapping({ - generated: { - line: currentLine - q, - column: 0 - }, - original: { - line: currentOrigLine - q, - column: 0 - }, - source: filePath - }); - } - - lastMappedLine = currentLine; - }; - } - - for (var i = 0; i < matches.length; i++) { - var leadingWhitespaceMatch = matches[i].match(/^\s*/); - var leadingWhitespace = null; - if (leadingWhitespaceMatch) { - leadingWhitespace = leadingWhitespaceMatch[0].replace("\n", ""); - } - - // Remove beginnings, endings and trim. - var includeCommand = matches[i] - .replace(/\s+/g, " ") - .replace(/(\/\/|\/\*|\#|)$/g, "") - .replace(/['"]/g, "") - .trim(); - - var split = includeCommand.split(" "); - - var currentLine; - if (sourceMap) { - // get position of current match and get current line number - currentPos = content.indexOf(matches[i], currentPos); - currentLine = currentPos === -1 ? 0 : content.substr(0, currentPos).match(/^/mg).length; - - // sometimes the line matches the leading \n and sometimes it doesn't. wierd. - // in case it does, increment the current line counter - if (leadingWhitespaceMatch[0][0] == '\n') currentLine++; - - mapSelf(currentLine); - } - - // SEARCHING STARTS HERE - // Split the directive and the path - var includeType = split[0]; - - // Use glob for file searching - var fileMatches = []; - var includePath = ""; - - if (includePaths != false) { - // If includepaths are set, search in those folders - for (var y = 0; y < includePaths.length; y++) { - includePath = includePaths[y] + "/" + split[1]; - - var globResults = glob.sync(includePath, { - mark: true - }); - fileMatches = fileMatches.concat(globResults); - } - } else { - // Otherwise search relatively - includePath = relativeBasePath + "/" + split[1]; - var globResults = glob.sync(includePath, { - mark: true - }); - fileMatches = globResults; - } - - if (fileMatches.length < 1) fileNotFoundError(includePath); - - var replaceContent = ''; - for (var y = 0; y < fileMatches.length; y++) { - var globbedFilePath = fileMatches[y]; - - // If directive is of type "require" and file already included, skip to next. - if (includeType == "require" && includedFiles.indexOf(globbedFilePath) > -1) continue; - - // If not in extensions, skip this file - if (!inExtensions(globbedFilePath)) continue; - - // Get file contents and apply recursive include on result - // Unicode byte order marks are stripped from the start of included files - var fileContents = stripBom(fs.readFileSync(globbedFilePath)); - - var result = processInclude(fileContents.toString(), globbedFilePath, sourceMap, includedFiles); - var resultContent = result.content; - - if (sourceMap) { - var lines = resultContent.match(/^/mg).length; //count lines in result - - if (result.map) { // result had a map, merge mappings - if (Object.prototype.toString.call(result.map) === '[object String]') { - result.map = JSON.parse(result.map); - } - - if (result.map.mappings && result.map.mappings.length > 0) { - var resultMap = new SourceMapConsumer(result.map); - resultMap.eachMapping(function (mapping) { - if (!mapping.source) return; - - map.addMapping({ - generated: { - line: mapping.generatedLine + currentLine - 1, - column: mapping.generatedColumn + (leadingWhitespace ? leadingWhitespace.length : 0) - }, - original: { - line: mapping.originalLine, - column: mapping.originalColumn - }, - source: mapping.source, - name: mapping.name - }); - }); - - if (result.map.sourcesContent) { - result.map.sourcesContent.forEach(function (sourceContent, i) { - map.setSourceContent(result.map.sources[i], sourceContent); - }); - } - } - } else { // result was a simple file, map whole file to new location - for (var q = 0; q < lines; q++) { - map.addMapping({ - generated: { - line: currentLine + q, - column: leadingWhitespace ? leadingWhitespace.length : 0 - }, - original: { - line: q + 1, - column: 0 - }, - source: globbedFilePath - }); - } - - if (sourceMap.sourcesContent) { - map.setSourceContent(globbedFilePath, resultContent); - } - } - - // increment/set map line counters - insertedLines += lines; - currentLine += lines; - lastMappedLine = currentLine; - } - - if (includedFiles.indexOf(globbedFilePath) == -1) includedFiles.push(globbedFilePath); - - // If the last file did not have a line break, and it is not the last file in the matched glob, - // add a line break to the end - if (!resultContent.trim().match(/\n$/) && y != fileMatches.length - 1) { - resultContent += "\n"; - } - - if (leadingWhitespace) resultContent = addLeadingWhitespace(leadingWhitespace, resultContent); - - replaceContent += resultContent; - } - - // REPLACE - if (replaceContent.length) { - // sometimes the line matches the leading \n and sometimes it doesn't. wierd. - // in case it does, preserve that leading \n - if (leadingWhitespaceMatch[0][0] === '\n') { - replaceContent = '\n' + replaceContent; - } - - content = content.replace(matches[i], function () { - return replaceContent - }); - insertedLines--; // adjust because the original line with comment was removed - } - } - - if (sourceMap) { - currentLine = content.match(/^/mg).length + 1; - - mapSelf(currentLine); - } - - return { - content: content, - map: map ? map.toString() : null - }; - } - - function unixStylePath(filePath) { - return filePath.replace(/\\/g, '/'); - } - - function addLeadingWhitespace(whitespace, string) { - return string.split("\n").map(function (line) { - return whitespace + line; - }).join("\n"); - } - - function fileNotFoundError(includePath) { - if (hardFail) { - throw new PluginError('gulp-include', 'No files found matching ' + includePath); - } else { - console.warn( - colors.yellow('WARN: ') + - colors.cyan('gulp-include') + - ' - no files found matching ' + includePath - ); - } - } - - function inExtensions(filePath) { - if (!extensions) return true; - for (var i = 0; i < extensions.length; i++) { - var re = extensions[i] + "$"; - if (filePath.match(re)) return true; - } - return false; - } - - return es.map(include) + params = params || {}; + + var SourceMapGenerator = require('source-map').SourceMapGenerator; + var SourceMapConsumer = require('source-map').SourceMapConsumer; + + var extensions = null, // The extension to be searched after + globalIncludedFiles = [], // For track of what files have been included over all files + includePaths = false, // The paths to be searched + hardFail = false, // Throw error when no match + debugIncludes = false, // Output debugging information on each include being processed + separateInputs = false; // Process each input file separately when using `require` logic + + // Check for includepaths in the params + if (params.includePaths) { + if (typeof params.includePaths == "string") { + // Arrayify the string + includePaths = [params.includePaths]; + } else if (Array.isArray(params.includePaths)) { + // Set this array to the includepaths + includePaths = params.includePaths; + } + } + + // Toggle echoing of included filenames + if (params.debugIncludes != undefined) { + debugIncludes = params.debugIncludes; + } + + if (params.extensions) { + extensions = typeof params.extensions === 'string' ? [params.extensions] : params.extensions; + } + + if (params.separateInputs) { + separateInputs = true; + } + + // Toggle error reporting + if (params.hardFail != undefined) { + hardFail = params.hardFail; + } + + if (params.extensions) { + extensions = typeof params.extensions === 'string' ? [params.extensions] : params.extensions; + } + + function include(file, callback) { + var includedFiles = separateInputs ? [] : globalIncludedFiles; + + if (file.isNull()) { + return callback(null, file); + } + + if (file.isStream()) { + throw new PluginError('gulp-include', 'stream not supported'); + } + + if (file.isBuffer()) { + var result = processInclude(String(file.contents), file.path, file.sourceMap, includedFiles); + file.contents = new Buffer(result.content); + + if (file.sourceMap && result.map) { + if (Object.prototype.toString.call(result.map) === '[object String]') { + result.map = JSON.parse(result.map); + } + + // relative-ize the paths in the map + result.map.file = path.relative(file.base, result.map.file); + result.map.sources.forEach(function (source, q) { + result.map.sources[q] = path.relative(file.base, result.map.sources[q]); + }); + + applySourceMap(file, result.map); + } + } + + callback(null, file); + } + + function processInclude(content, filePath, sourceMap, includedFiles) { + var matches = content.match(/^(\s+)?(\/\/|\/\*|\#|\<\!\-\-)(\s+)?=(\s+)?(include|require)(.+$)/mg); + var relativeBasePath = path.dirname(filePath); + + if (!matches) return { + content: content, + map: null + }; + + // Apply sourcemaps + var map = null, + mapSelf, lastMappedLine, currentPos, insertedLines; + if (sourceMap) { + map = new SourceMapGenerator({ + file: unixStylePath(filePath) + }); + lastMappedLine = 1; + currentPos = 0; + insertedLines = 0; + + mapSelf = function (currentLine) { // maps current file between matches and after all matches + var currentOrigLine = currentLine - insertedLines; + + for (var q = (currentLine - lastMappedLine); q > 0; q--) { + map.addMapping({ + generated: { + line: currentLine - q, + column: 0 + }, + original: { + line: currentOrigLine - q, + column: 0 + }, + source: filePath + }); + } + + lastMappedLine = currentLine; + }; + } + + for (var i = 0; i < matches.length; i++) { + var leadingWhitespaceMatch = matches[i].match(/^\s*/); + var leadingWhitespace = null; + if (leadingWhitespaceMatch) { + leadingWhitespace = leadingWhitespaceMatch[0].replace("\n", ""); + } + + // Remove beginnings, endings and trim. + var includeCommand = matches[i] + .replace(/\s+/g, " ") + .replace(/(\/\/|\/\*|\#|)$/g, "") + .replace(/['"]/g, "") + .trim(); + + var split = includeCommand.split(" "); + + // Echo filenames of includes if debugIncludes is true + if (debugIncludes) { + console.log(gutil.colors.cyan('Including: ') + gutil.colors.blue.bold(split[1])); + } + + var currentLine; + if (sourceMap) { + // get position of current match and get current line number + currentPos = content.indexOf(matches[i], currentPos); + currentLine = currentPos === -1 ? 0 : content.substr(0, currentPos).match(/^/mg).length; + + // sometimes the line matches the leading \n and sometimes it doesn't. wierd. + // in case it does, increment the current line counter + if (leadingWhitespaceMatch[0][0] == '\n') currentLine++; + + mapSelf(currentLine); + } + + // SEARCHING STARTS HERE + // Split the directive and the path + var includeType = split[0]; + + // Use glob for file searching + var fileMatches = []; + var includePath = ""; + + if (includePaths != false) { + // If includepaths are set, search in those folders + for (var y = 0; y < includePaths.length; y++) { + includePath = includePaths[y] + "/" + split[1]; + + var globResults = glob.sync(includePath, { + mark: true + }); + fileMatches = fileMatches.concat(globResults); + } + } else { + // Otherwise search relatively + includePath = relativeBasePath + "/" + split[1]; + var globResults = glob.sync(includePath, { + mark: true + }); + fileMatches = globResults; + } + + if (fileMatches.length < 1) fileNotFoundError(includePath); + + var replaceContent = ''; + for (var y = 0; y < fileMatches.length; y++) { + var globbedFilePath = fileMatches[y]; + + // If directive is of type "require" and file already included, skip to next. + if (includeType == "require" && includedFiles.indexOf(globbedFilePath) > -1) continue; + + // If not in extensions, skip this file + if (!inExtensions(globbedFilePath)) continue; + + // Get file contents and apply recursive include on result + // Unicode byte order marks are stripped from the start of included files + var fileContents = stripBom(fs.readFileSync(globbedFilePath)); + + var result = processInclude(fileContents.toString(), globbedFilePath, sourceMap, includedFiles); + var resultContent = result.content; + + if (sourceMap) { + var lines = resultContent.match(/^/mg).length; //count lines in result + + if (result.map) { // result had a map, merge mappings + if (Object.prototype.toString.call(result.map) === '[object String]') { + result.map = JSON.parse(result.map); + } + + if (result.map.mappings && result.map.mappings.length > 0) { + var resultMap = new SourceMapConsumer(result.map); + resultMap.eachMapping(function (mapping) { + if (!mapping.source) return; + + map.addMapping({ + generated: { + line: mapping.generatedLine + currentLine - 1, + column: mapping.generatedColumn + (leadingWhitespace ? leadingWhitespace.length : 0) + }, + original: { + line: mapping.originalLine, + column: mapping.originalColumn + }, + source: mapping.source, + name: mapping.name + }); + }); + + if (result.map.sourcesContent) { + result.map.sourcesContent.forEach(function (sourceContent, i) { + map.setSourceContent(result.map.sources[i], sourceContent); + }); + } + } + } else { // result was a simple file, map whole file to new location + for (var q = 0; q < lines; q++) { + map.addMapping({ + generated: { + line: currentLine + q, + column: leadingWhitespace ? leadingWhitespace.length : 0 + }, + original: { + line: q + 1, + column: 0 + }, + source: globbedFilePath + }); + } + + if (sourceMap.sourcesContent) { + map.setSourceContent(globbedFilePath, resultContent); + } + } + + // increment/set map line counters + insertedLines += lines; + currentLine += lines; + lastMappedLine = currentLine; + } + + if (includedFiles.indexOf(globbedFilePath) == -1) includedFiles.push(globbedFilePath); + + // If the last file did not have a line break, and it is not the last file in the matched glob, + // add a line break to the end + if (!resultContent.trim().match(/\n$/) && y != fileMatches.length - 1) { + resultContent += "\n"; + } + + if (leadingWhitespace) resultContent = addLeadingWhitespace(leadingWhitespace, resultContent); + + replaceContent += resultContent; + } + + // REPLACE + if (replaceContent.length) { + // sometimes the line matches the leading \n and sometimes it doesn't. wierd. + // in case it does, preserve that leading \n + if (leadingWhitespaceMatch[0][0] === '\n') { + replaceContent = '\n' + replaceContent; + } + + content = content.replace(matches[i], function () { + return replaceContent + }); + insertedLines--; // adjust because the original line with comment was removed + } + } + + if (sourceMap) { + currentLine = content.match(/^/mg).length + 1; + + mapSelf(currentLine); + } + + return { + content: content, + map: map ? map.toString() : null + }; + } + + function unixStylePath(filePath) { + return filePath.replace(/\\/g, '/'); + } + + function addLeadingWhitespace(whitespace, string) { + return string.split("\n").map(function (line) { + return whitespace + line; + }).join("\n"); + } + + function fileNotFoundError(includePath) { + if (hardFail) { + throw new PluginError('gulp-include', 'No files found matching ' + includePath); + } else { + console.warn( + colors.yellow('WARN: ') + + colors.cyan('gulp-include') + + ' - no files found matching ' + includePath + ); + } + } + + function inExtensions(filePath) { + if (!extensions) return true; + for (var i = 0; i < extensions.length; i++) { + var re = extensions[i] + "$"; + if (filePath.match(re)) return true; + } + return false; + } + + return es.map(include) }; \ No newline at end of file diff --git a/package.json b/package.json index 6821451..b46e824 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,38 @@ { - "name": "gulp-include", - "version": "2.4.0", - "description": "Makes inclusion of files a breeze. Enables functionality similar to that of snockets / sprockets or other file insertion compilation tools.", - "homepage": "http://github.com/wiledal/gulp-include", - "repository": { - "type": "git", - "url": "git://github.com/wiledal/gulp-include.git" - }, - "main": "index.js", - "scripts": { - "test": "mocha" - }, - "keywords": [ - "gulpplugin" - ], - "author": { - "name": "Hugo Wiledal" - }, - "license": "MIT", - "devDependencies": { - "gulp": "^4.0.0", - "gulp-sourcemaps": "^2.6.5", - "mocha": "^6.0.2", - "should": "^13.2.3", - "stream-assert": "^2.0.3" - }, - "dependencies": { - "ansi-colors": "^3.2.4", - "event-stream": "^4.0.1", - "glob": "^7.1.3", - "plugin-error": "^1.0.1", - "source-map": "^0.7.3", - "strip-bom": "^2.0.0", - "vinyl": "^2.2.0", - "vinyl-sourcemaps-apply": "^0.2.1" - } + "name": "gulp-include", + "version": "2.4.1", + "description": "Makes inclusion of files a breeze. Enables functionality similar to that of snockets / sprockets or other file insertion compilation tools.", + "homepage": "http://github.com/wiledal/gulp-include", + "repository": { + "type": "git", + "url": "git://github.com/wiledal/gulp-include.git" + }, + "main": "index.js", + "scripts": { + "test": "mocha" + }, + "keywords": [ + "gulpplugin" + ], + "author": { + "name": "Hugo Wiledal" + }, + "license": "MIT", + "devDependencies": { + "gulp": "^4.0.0", + "gulp-sourcemaps": "^2.6.5", + "mocha": "^6.0.2", + "should": "^13.2.3", + "stream-assert": "^2.0.3" + }, + "dependencies": { + "ansi-colors": "^3.2.4", + "event-stream": "^4.0.1", + "glob": "^7.1.3", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "strip-bom": "^2.0.0", + "vinyl": "^2.2.0", + "vinyl-sourcemaps-apply": "^0.2.1" + } }