Gulp - want to inject SCSS variable into another file - gulp

UPDATE -- See additional info below, added on 22 March...
I want to inject an SCSS variable into a code file with my Gulp process.
I think this is two steps:
Gulp the SCSS _variables.scss file so I can access the variable
Just do a simple gulp.replace to do a string-replace and drop the
variable value into my file (like the answer here)
I'm stuck on step 1. Here's what I have... as best I can tell, the not-heavily-documented gulp-sass-variables plugin is supposed to pull the variables from the SCSS file into the argv object. So I followed the example on that plugin page, and tried this:
gulp.task('test', function () {
gulp.src('sass/*')
.pipe(plugins.print())
.pipe(plugins.sassVariables({
$foundIt: argv.testSassVar
}))
console.log(argv);
});
The print() call is just to confirm that it is reading my SCSS files (and it is). But it can't find $testSassVar in my SCSS code, as you can see from this error message:
[12:53:04] Using gulpfile ~/dev/project/gulpfile.js
[12:53:04] Starting 'test'...
[12:53:04] 'test' errored after 15 ms
[12:53:04] ReferenceError: testSassVar is not defined
at Gulp.<anonymous> (...)
[12:53:04] sass/_mixins.scss <- this line and all below are from print()
[12:53:04] sass/_normalize.scss
[12:53:04] sass/_variables.scss
[12:53:04] sass/footer
[12:53:04] sass/header
[12:53:04] sass/landing
[12:53:04] sass/landing-style.scss
[12:53:04] sass/site
[12:53:04] sass/style.scss
I tried removing that $foundIt assignment, and just trying to fill the argv object, but that ends up outputting an empty array:
[13:00:57] Using gulpfile ~/dev/project/gulpfile.js
[13:00:57] Starting 'test'...
{ _: [ 'test' ], '$0': 'gulp' }
[13:00:57] Finished 'test' after 41 ms
Any ideas how I can accomplish what I'm trying to do?!
EDIT 22 March
Looks like I can partially accomplish this by going a different route and using the gulp-sass-json plugin:
gulp.task('sass-json', function () {
return gulp
.src(scss_base + '*')
.pipe(plugins.sassJson())
.pipe(gulp.dest(tmp + 'sass-vars.json'));
});
This writes a file to /tmp with my SCSS variables compiled into a JSON file. Then I thought, ok, I can load the JSON file in gulp and parse it from there. Unfortunately, this plugin doesn't resolve 2nd level variables (i.e. variables that reference variables), so you get something like this:
{
# variables parsed from SCSS to JSON by gulp-sass-json
# these are ok
"font__main": "'Open Sans', sans-serif",
"font__heading": "'Open Sans', sans-serif",
"font__line-height-body": "1.5",
"testSassVar": "123",
"color__nbar": "#ffffff",
"color__nbar_bg": "#50B107",
"size_border-radius": "3px"
# these variables reference other variables, not parsed, ouch
"color__background-body": "$color__nbar",
"color__background-screen": "$color__nbar",
# and this is even worse, the SCSS function isn't parsed, double ouch
"color_test": "lighten($color__nbar, 50%)",
}
As you can see, the variables that are simple references to other variables could probably be fixed with a bit of Javascript to just parse them, but once you get into the SASS functions, that idea falls apart.
Ideally, the SCSS compiler could parse and variables that aren't static values. Is that possible (or is that starting to really get into the weeds to solve this problem)?
The only other thought I had was to move all my variables to a JSON file, then use a gulp plugin that moved in the other direction -- JSON to SASS variables. But then I wouldn't get the advantage of having the SCSS compiler to run functions, parse variables, etc, and then give those values back to my gulp file.
Since my goal is to have the SASS values in the (parsed) variables available to my gulp file, so I can turn around and drop them into another (HTML or PHP) code file doing a simple string replace. If there's an easier way to accomplish my final goal, I'm all ears...

I recently had a similar issue, although I'm not using gulp.
I searched for npm packages that could accomplish this task (variable extraction from SCSS), but the couple I found were subpar.
So, I ended up writing my own lib (extract-scss-variables).
We use it in production, it's tested against foundation and a couple of other common uses.
I sincerely don't remember how to produce a gulp plugin anymore, but this library in itself shouldn't be hard to integrate.
In your specific case I'd do something like:
var extractScssVariables = require('extract-scss-variables');
var variables = extractScssVariables({
entryPoint: 'path/to/sass/style.scss',
files: [
'path/to/sass/_mixins.scss',
'path/to/sass/_normalize.scss',
'path/to/sass/_variables.scss',
],
sassOptions: {
includePaths: ['path/to/libraries/to/include.scss'],
},
});
where the entryPoint is the context where the variables are extracted (e.g. if you overwrite a variable declared in _mixins.scss, the entryPoint's one is used instead);
files is the array of files where to extract SCSS variables from;
and sassOptions are any command line sass options you are already using.

I've also authored a library for this kind of use case that uses native sass compiler feature to extract the variable values called sass-extract which also comes with a webpack loader and babel plugin depending on the use case.
The benefit is that is based on the native sass type system and thus supports all kinds of sass features like functions, mixins, lists, maps etc. It also handles imports, overrides, defaults etc as expected.
If you are open to using webpack you can simply import the sass file:
const style = require('sass-extract-loader!./style.scss');
to get the variables extracted, which would be the easiest option. Otherwise you can just use the core library directly as such:
const sassExtract = require('sass-extract');
sassExtract.render({
file: 'path/to/my/styles.scss'
})
.then(rendered => {
console.log(rendered.vars);
});
There is also a synchronous version available that you can use to integrate with your gulp pipelines.

Here is my solution - you need to have gulp-inject
main.scss file
...
/* inject:scss */
/* endinject */
...
gulpfile.js file
var gulpInject = require('gulp-inject');
...
gulp.src('...your/main/scss/file.scss')
.pipe(
gulpInject(gulp.src('...your/partial/scss/**/*.scss'), {
relative: true,
transform: function (filePath) {
var fileWithoutExtension = filePath.replace('.scss', '');
return '#import \'' + fileWithoutExtension + '\';';
}
})
)
.pipe(...)
...
The above example will inject partial SCSS files into the main SCSS file.
Hopefully my code would be helpful.

Related

Gulp src ignore part of glob path for recursive copy

I would like to copy some openui5 resource files into my output folder from bower packages. They have all a common prefix (openui5-). They all have a subfolder called "resources". I would like to copy that content from that subfolder to one common "resources" folder in my output.
I would like to use a glob to copy them with gulp. For now I have to explicitly give each path
['bower_components/openui5-sap.m/resources/**/*',
'bower_components/openui5-sap.ui.core/resources/**/*',
'bower_components/openui5-themelib_sap_belize/resources/**/*']
I would like to use a pattern like this:
'bower_components/openui5-*/resources/**/*'
But if I do this, I get the full module name copied, too, so my resource folder looks like this:
out/resources/
+ -- openui5-sap.m/resources/...
+ -- openui5-sap.ui.core/resources/...
+ -- openui5-themelib_sap_belize/resources/...
As I came to understand this is because per default gulp.src takes the first glob (which is in the module name) and makes the recursive structure from there.
Is there a way to ignore parts of the glob pattern for the output or trim the output paths using another glob?
I played around and searched for any solutions, but I cannot find anything.
gulp-rename seems to flatten the whole hierarchy:
gulp.task('copyui5resources', function() {
gulp.src('bower_components/openui5-*/**/*')
.pipe(rename({ dirname: '' }))
.pipe(gulp.dest('out/resources'));
});
And using the base option does not help either. It seems to copy just a part of it:
gulp.task('copyui5resources', function() {
gulp.src('bower_components/openui5-*/**/*', {base: 'bower_components/openui5-*'})
.pipe(gulp.dest('out/resources'));
});
Here is a screenshot with my input folder structure and my gulp task so far. Thank you for your help!
You're on the right track trying to use gulp-rename. However the dirname option isn't powerful enough for what you're trying to accomplish, since it can only replace the entire directory structure. You want to replace just a part of it.
For cases like these gulp-rename can be supplied with a function, which allows you to alter dirname using everything JavaScript has to offer including string.replace().
That means you can do this:
var gulp = require('gulp');
var rename = require('gulp-rename');
gulp.task('default', function() {
return gulp.src('bower_components/openui5-*/resources/**/*')
.pipe(rename(f => f.dirname = f.dirname.replace(/openui5-.*?\//, '')))
.pipe(gulp.dest('out/'));
});
Thank you for your help Sven,
I just stumbled on that hint this morning in another post before I read your post :( I removed the first two parts of the path and got the desired result. It has the advantage, that you can skip as many parts you want and the regex solution has the advantage catching even more similar path parts.
For completion: Here is the solution I came up with:
var gulp = require('gulp'),
rename = require('gulp-rename'),
path = require('path');
gulp.task('copyui5resources', function() {
gulp.src('bower_components/openui5-*/resources/**/*')
.pipe(rename(function(p) {
var nda = p.dirname.split(/[\\\/]/);
nda.splice(0, 2);
p.dirname = nda.length > 0 ? path.join.apply(null, nda) : "";
}))
.pipe(gulp.dest('out/resources'));
});
I used path to rejoin the paths indipendent from the OS. Splice removes the first two parts with the package name and the resources directory. You have to use path.join.apply, as it flattens the array, otherwise you get an error.

Gulp, css is saving to dist/css/less, should be dist/css

trying to setup my gulp file for a project, I want to compile the less, and then minify it and have it save this to a new folder.
My less file is saved in app/less/styles.less, the compiled css file should save to dist/css/styles.css but it's saving to dist/css/less/styles.css.
What am I doing wrong here?
var app = 'app/',
dist = 'dist/',
appStyles = app + '**/*.less';
gulp.task('compilecssremote', function(){
return gulp.src(appStyles)
.pipe(plumber({
errorHandler: onError
}))
.pipe(changed(dist)) //must be dist
.pipe(urladjuster({
prepend: '/' + project + '/dist/' //based on location of CSS files
}))
.pipe(less())
.pipe(minifycss({keepBreaks: false}))
.pipe(gulp.dest(dist + 'css'))
});
This is the expected behavior when you use a gulp.src like 'app/**/*.less (aka your appStyles) to match source files with paths like app/less/styles.less. There are two pieces to understand here:
By default everything before the first glob in your gulp.src path will be omitted from the output path. In this case that's everything before the **, which is to say the app/. That's why the your output css file isn't going to 'dest/app/…. (For a more detailed discussion of this, see this answer)
The path matched by the first glob and on is preserved in the output path. In this case, that's the less/ in the matched file 'app/less/styles.less' (that is, the part of 'app/**/*.less' represented by **). (It's beside the point in the case of your problem, but this feature is very useful for preserving relative file structures.)
Using #1 & #2, one quick fix would be to trim less/ from the output path by putting it before the first glob in the gulp.src. For example, change appStyles = app + 'less/**/*.less'.

How to use Webpack to combine JSON files from all subdirectories into one?

Say I have a directory structured like this:
1. folder A
1a. some_file.js
1b. data.json
2. folder B
2a. folder B1
2a1. other_file.json
2a2. data.json
2b. folder B2
2b1. data.json
3. output.json
Is there a webpack loader that can combine data.json in all subfolders, and output it to output.json?
I've found https://www.npmjs.com/package/json-files-merge-loader that seems to do something similar, but it seems to ask for each path to data.json, while I need something that goes through all folders and subfolders for data.json. All data.json keys are unique, and I want output.json to be one JSON object containing all key/value pair from all data.json.
webpack is not really suited for the use case you have. Many people think that webpack can be a replacement for a build system, but it's just a module bundler, it doesn't handle every task. Specifically:
webpack traverses require() and import statements, meaning it needs the modules to be statically defined. It's possible to get around this by writing a plugin or using a file generated from a template, but even if you did that...
webpack would have a hard time combining the JSON files in the way you want. webpack is good at bundling files into some sort of modules system (CommonJS or AMD). What you want is not a bundle containing modules, but a file containing arbitrary contents.
webpack might be able to do these things using some fancy plugins, but it's likely not worth it. In your case, you probably just want to write a Node script. If you want, you can use a plugin like this to run the code before build.
const fs = require('fs');
// https://github.com/isaacs/node-glob
const glob = require('glob');
const output = {};
glob('src/**/*.json', (error, files) => {
files.forEach((filename) => {
const contents = JSON.parse(fs.readFileSync(filename, 'utf8'));
Object.assign(output, contents);
});
fs.writeFileSync('output.json', JSON.stringify(output));
});
Is there a webpack loader that can combine data.json in all subfolders, and output it to output.json?
Use the merge-webpack-plugin.
This is differ from to run the code before build. Because deep webpack integration allow to follow file changes and update results immidiately while hot reloading:
MergePlugin = require("merge-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /\.(json)$/i,
use: [
MergePlugin.loader()
]
}
]
},
plugins: [
new MergePlugin({
search: './src/**/*.json',
})
]
}
if you need only one target file output.json for all folders in your project describe them all in search param, and that is all
if you need to join separated files for each top level folders (output_A.json, output_B.json ...) then:
if your do not need to lookup through subfolders try to play with group param with value [path] - read more about grouping
if you need to join all files through each folder and its subfolders you need to create multiple plugin instances for each top level folder; but you can group files in each plugin instance by name or by ext (see grouping)
and some more
#dee Please check this plugin
https://www.npmjs.com/package/merge-jsons-webpack-plugin
You can pass array of files/patterns as an input and it will emit single file as json.
My multi-json-loader might be helpful here. It accepts a glob and combines the files into a single JSON blob while also retaining relative paths in the output object.
Ie, dirA/a.json, dirB/b.json, and dirB/c.json get output as
{
"dirA": {
"a": /*parsed contents of a.json*/
},
"dirB": {
"b": /*parsed contents of b.json*/,
"c": /*parsed contents of b.json*/
}
}

ElectronJs: What does curly braces '{}' mean in package json

I was going through some electron package.json examples where I found some interpolations like given below:
"updater": {
"urls": {
"darwin": "{{& SQUIRREL_UPDATES_URL }}/update/%CHANNEL%/darwin?version=%CURRENT_VERSION%",
"win32": "{{& SQUIRREL_UPDATES_URL }}/update/%CHANNEL%/win32",
"linux": "{{& SQUIRREL_UPDATES_URL }}/update/%CHANNEL%/linux"
}
}
"piwik": {
"serverUrl": "{{& PIWIK_SERVER_URL }}"
},
"sentry": {
"dsn": "{{& SENTRY_DSN_PRIVATE }}"
}
I do not really know the following:
what does this {{}} mean in json
where does these variable exist
what does & mean in {{}} "{{& SENTRY_DSN_PRIVATE }}"
If anyone can explain then it would be really kind. Many thank in advance.
I guess you are talking about Whatsie and it's package.json.
If you take a look at one of the Gulp tasks located in the file tasks/compile.coffee, you'll be able to see the lines (in CoffeeScript):
# Move package.json
gulp.task 'compile:' + dist + ':package', ['clean:build:' + dist], ->
gulp.src './src/package.json'
.pipe mustache process.env
.pipe gulp.dest dir
Here the actual package.json is being passed to a mustache template engine - it receives a template as a first argument (package.json here acts like a template) and a data to be inserted in the template as a second argument - process.env.
As package.json acts like a template for mustache, you can use mustache syntax in it.
Curly braces {{}} are the part of it, they are used as placeholders which will be replaced by the actual data, when templates are being compiled. In the mustache docs you can also find a line:
You can also use & to unescape a variable: {{& name}}
So {{& name}} is to prevent values from being escaped. Otherwise, if you don't use & and values for output have some dangerous characters , they will be replaced by more secure ones (originally to prevent XSS in templates), as a result it will transform initial value, which is not always what you want. In this case author wants to preserve original value.
Going back to process.env - it is an object which gives access to environment variables in Node.JS. There is a file in repository .env-example with an example of env variables developer has to set in order to have the application work differently in different environments (for example on local machine or CI server). Names of some of the variables in this file are the ones that are used in a package.json as template placeholders - I guess author of the app uses all of this to simplify a build process for different environments.

Gulp globbing and negate

I'm having an issue where I'm unable to compile a set of LESS file using Globbing.
My structure is this:
+ dir3
- dir3.1
- dir3.1.1
- dir3.1.2
- dir3.2
- dir3.3
- responsive.less
- fixedwidths.less
Each dir has a set of less files and what I'm trying to do is to compile responsive.less and fixedwidths.less but i want to ignore all the remaining folders in dir3.
My Gulp Glob for compiling less files is:
var paths = {
root: 'Publication/',
source: 'Publication/LESS/',
styles: 'Publication/**/*.less'
}
// Compile less files
gulp.task('styles', function() {
gulp.src([
paths.source+'**/*.less', // compile it all
'!'+paths.source+'**/variables.less', // ignore all variables.less
paths.source+'responsive/*.less', // compile responsive & fixedwidths
'!'+paths.source+'responsive/**/*.less' // ignore all less files in subdir of dir3
])
.pipe(less())
.pipe(gulp.dest(paths.root+'build'))
});
I was expecting that paths.source+'responsive/*.less' would match responsive.less and fixedwidths.less and '!'+paths.source+'responsive/**/*.less' would ignore everything else but that's not what seems to be happening. The latter bit code seems to halt all processing of dir3.
Can anything help?
There is a bug in node-glob related to negated glob patterns. See this bower issue and this node-glob issue. Maybe your problem is related to this?
If so, this workaround should work './!('+paths.source+'responsive/**/*.less)'.