Webpack doesn't place images in dist folder - image

I have a pug mixin. Mixin is using to create block with whatever image name passed as argument.
mixin img({imageSrc: ""} = {})
.img(src="./img/" + imageSrc + ".jpg")
As a result I want webpack either place this image in dist/img/ or processed and replaced this path with it's base64 format.
Due to my need to save relative paths in sass and pug I use url-loader. So my current configuration for pug and image looks like this:
module: {
rules: [{
test: /\.pug$/,
loader: 'pug-loader
}, {
test: /\.(jp(e*)g|png|svg)$/,
use: [{
loader: "url-loader",
options: {
outputPath: "images/"
}
}]
}]
}
Appreciate your help because I'm running out of ideas :c

I don't think url-loader has an outputPath option, it just outputs to your Webpack config's output.path. Assuming that your output.path is dist (default) you can have files go to dist/img by specifying the name option like:
options: {
name: 'img/[name].[ext]?[hash]'
}

Related

How I can pass environment variables to scss/ sass file using laravel mix and webpack?

I have an environment variable CDN_URL and I want to send this variable to the SCSS file.
I am also tried prependData of sass-loader.
I have to use Laravel 5.7, Laravel Mix 4.1.2 and webpack 4.27.1
error: Invalid CSS after "...load the styles": expected 1 selector or at-rule, was "var content = requi"
Below is my 'webpack.mix.js' file code.
mix.webpackConfig({
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
'vue-style-loader',
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
indentedSyntax: true,
prependData: '$cdn-s3-static-url: ' + process.env.CDN_S3_STATIC_URL + ';',
},
},
],
},
],
},
});
Below is my '_functions.scss' file code:
#function asset($type, $file) {
#return url('#{$cdn-s3-static-url}#{$asset-base-path}#{$type}/#{$file}');
}
In my case I was running a gatsby site. Before each build, it runs gatsby-config.js, which has access to environment variables.
So at the top of the .js file that builds, before module.exports, I put this:
if(process.env.NODE_ENV === 'development') {
fs.writeFileSync('./src/styles/build-style.scss','$root: "/development-path/";');
} else {
fs.writeFileSync('./src/styles/build-style.scss','$root: "/production-path/";');
}
This results in a file which looks like:
$root: "/development-path/";
Then in the SCSS files where I needed ENV-dependent behaviour, I have:
#import './build-style.scss';
#font-face {
font-family: "MyFontFamily";
src: url($root + "font/MyFontFamily.woff") format('woff');
}
And now my asset (font in this example) loads from different spots depending on my dev/production environment variable.
It feels like a big hack and I'm sure there's a more correct way somewhere, but this got me moving again after an hour stoppage and it is working so far. I will probably extend it in the future to have build-style-dev.scss, build-style-prod.scss, and just copy them into build-style.scss at compile time. Or research the correct way.
You can prepend data to SASS using sass-loader
For example to pass the CDN_URL from .env
Extend webpack.mix.js
module.exports = {
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
prependData: '$env: ' + process.env.CDN_URL + ';',
},
},
],
},
],
},
};
You may inject environment variables into Laravel Webpack Mix by prefixing a key in your .env file with MIX_. After the variable has been defined in your .env file, you may access via the process.env object.
So in your example, you should create a new variable in .env file like MIX_CDN_URL and inside webpack.mix.js you can access it using
process.env.MIX_CDN_URL
You can sass-loader that will achieve the results you desire.

Is there any way to set a node-sass configurations file?

I've been searching and found some issues and implementations that seems to be deprecated, though I'm not sure (as I tried to implement them and failed).
If it still not clear, what I want to achieve is a command line like so:
Straight in command line or npm scripts, we'd use something like this:
node-sass --config sassconfig.json
In sassconfig.json, we'd have a code like the one below:
{
"outputStyle": "compressed",
"sourceMap": true
}
This setup would make it more elegant and straightforward, just like tsconfig.json.
Normally you dont render Sass manually with a npm command. Integrate Sass in your webserver and render it when the server starts and your watcher programm recognize changes in the code (nodemon, Webpack hot module, etc.)
Here is an example for an expressJs server:
https://stackoverflow.com/a/26277894/6564085
Let me quote swervos' answer from the link to save the snippet in this answer:
var connect = require('connect');
var sass = require('node-sass');
var srcPath = __dirname + '/sass';
var destPath = __dirname + '/public/styles';
var server = connect.createServer(
sass.middleware({
src: srcPath,
dest: destPath,
debug: true,
outputStyle: 'expanded',
prefix: '/styles'
}),
connect.static(__dirname + '/public')
);
If you want to use Webpack heres a webpack version:
https://stackoverflow.com/a/46059484/6564085
let me quote Arnelle Balane's for it.
{
test: /.scss$/,
use: ExtractTextPlugin.extract({
fallbackLoader: "style-loader",
use: [{
loader: 'css-loader'
}, {
loader: 'sass-loader',
options: {
outputStyle: 'expanded'
}
}]
})
}
More information here: https://www.npmjs.com/package/sass-loader

How to create a Fusebox project with multiple html pages?

I'm new to bundlers and am currently learning about Fusebox. I really like it so far except that I can't figure out how to use it for a multi-page project. So far I've only been able to find a tutorial on how to do this using webpack, not for fusebox.
Input files in src folder:
index.html
index2.html
index.ts
Desired output in dist folder:
app.js
vendor.js
index.html
index2.html
Actual output in dist folder:
app.js
vendor.js
index.html
Here is my config in the fuse.js file:
Sparky.task("config", () => {
fuse = FuseBox.init({
homeDir: "src",
output: "dist/$name.js",
hash: isProduction,
sourceMaps: !isProduction,
plugins: [
[SassPlugin(), CSSPlugin()],
CSSPlugin(),
WebIndexPlugin({
title: "Welcome to FuseBox index",
template: "src/index.html"
},
WebIndexPlugin({
title: "Welcome to FuseBox index2",
template: "src/index2.html"
},
isProduction && UglifyJSPlugin()
]
});
// vendor should come first
vendor = fuse.bundle("vendor")
.instructions("~ index.ts");
// out main bundle
app = fuse.bundle("app")
.instructions(`!> [index.ts]`);
if (!isProduction) {
fuse.dev();
}
});
Setting WebIndexPlugin twice within plugins doesn't work. What is the correct way to set up a multi-html page project with fusebox?
The WebIndexPlugin can not be configured, to output more than one html file.
But if you don't use a hash for the generated bundles (e.g.: output: "dist/$name.$hash.js"), you don't need the WebIndexPlugin -- you can remove it completly from the plugins option. Because you already know the names of the generated bundles (vendor.js and app.js) you can just include the following lines
<script src="vendor.js"></script>
<script src="app.js"></script>
instead of the placeholder $bundles.
If you want, that both html files are copied from your src directory into your dist directory, you can add the following lines to your fuse.js script:
const fs = require('fs-extra');
fs.copySync('src/index.html', 'dist/index.html');
fs.copySync('src/index2.html', 'dist/index2.html');
Note: Don't forget to add fs-extra:^5.0.0 to your package.json
Might not been the case when the question was asked, but WebIndexPlugin now can be specified multiple times and also takes optional bundles parameter where list of bundles to be included in html can be specified (all bundles are included by default).
For example 2 html files (app1.html, app2.html) where each includes a common library (vendor.js), and different entry points (app1.js and app2.js)
app1.html
vendor.js
app1.js
app2.html
vendor.js
app2.js
Config would look like this:
const fuse = FuseBox.init({
homeDir : "src",
target : 'browser#es6',
output : "dist/$name.js",
plugins: [
WebIndexPlugin({
target: 'app1.html',
bundles:['vendor', 'app1']
}),
WebIndexPlugin({
target: 'app2.html',
bundles:['vendor', 'app2']
})
]
})
// vendor bundle, extracts dependencies from index1 and index2:
fuse.bundle("vendor").instructions("~[index1.ts,index2.ts]")
// app1 and app2, bundled separately without dependencies:
fuse.bundle("app1").instructions("!>index1.ts")
fuse.bundle("app2").instructions("!>index2.ts")

Is it possible to inject NODE_ENV param into sass file using Webpack & sass loader?

I'm trying to inject NODE_ENV into a sass file, so I can have different output for dev/prod env, and have a sass function that has a condition like that inside it:
#if (NODE_ENV == 'prod') {}
my webpack.config looks like this:
module: {
loaders: [{
test: /\.scss$/,
loader: "style-loader!raw-loader!sass-loader?includePaths[]=" + path.resolve(__dirname, "./node_modules/compass-mixins/lib")
}]
}
I tried passing a data parameter to the loader, but nothing that I tried worked.
will appreciate any help!
this is the code directly from sass-loader repo.
webpack.config.js
...
sassLoader: {
data: "$env: " + process.env.NODE_ENV + ";"
}
...
You can try the prependData option for sass-loader
{
loader: 'sass-loader',
options: {
prependData: '$env: ' + process.env.NODE_ENV + ';',
}
}
prependData also accepts a function which receives the loaderContext with which you can make it more dynamic. Once you have implemented this; you can :
#if ($env == 'prod') {}
More information here : https://webpack.js.org/loaders/sass-loader/#prependdata
Good Luck...
Took a look at the sass-loader source and it doesn't seem to support any options that would allow you to insert a variable -- so it won't be possible this way.
I don't think you'll be able to 'insert' an env/build-time variable into your Sass like this, but you could get the same behavior by creating multiple Sass files and requiring the one you want (from your js source) based on a condition. Note that webpack has limited ability to parse the logic in your code, however -- if you want to require something within a conditional, the conditional has to check a boolean value for it to work. So if you do if (NODE_ENV === 'production') {...} else {...} webpack will process all the requires in all those conditions. If you did something like if (IS_PROD_ENV) {...} else {...}, where the conditional value is a boolean, webpack will follow the conditional logic and only process the correct require. The UglifyJS plugin will remove the unneeded conditional branches for you.

Grunt - pass filename variable from command line

I am struggling to understand how I can pass a partial filename from the grunt command line, in order to run a task (from an installed grunt module) on a particular file.
What I want to be able to do is configure a series of tasks to take filename parameter from the command line.
I've tried reworking the final example on this page http://chrisawren.com/posts/Advanced-Grunt-tooling but I'm kind of stabbing in the dark a bit. Thought someone would have a quick answer.
Here is my Gruntfile:
module.exports = function (grunt) {
grunt.initConfig({
globalConfig: globalConfig,
uglify: {
js: {
options: {
mangle: true
},
files: {
'js/<%= globalConfig.file %>.min.js': ['js/<%= globalConfig.file %>.js']
}
}
},
});
// Load tasks so we can use them
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('go', 'Runs a task on a specified file', function (fileName){
globalConfig.file = fileName;
grunt.task.run('uglify:js');
});
};
I attempt to run it from the command line like this:
grunt go:app
to target js/app.js
I get this error:
Aborted due to warnings.
roberts-mbp:150212 - Grunt Tasks robthwaites$ grunt go:app
Loading "Gruntfile.js" tasks...ERROR
>> ReferenceError: globalConfig is not defined
Warning: Task "go:app" not found. Use --force to continue.
Thanks
you can use grunt.option.
your grunt register task will look like this.
> grunt.option('fileName'); grunt.registerTask('go', 'Runs a task on a
> specified file', function (){
> grunt.task.run('uglify:js');
> });
your grunt configuration will be
module.exports = function (grunt) {
var fileName=grunt.option('fileName');
grunt.initConfig({
uglify: {
js: {
options: {
mangle: true
},
files: {
'js/fileName.min.js': ['js/fileName.js']
}
}
},
});
command to run the task from terminal:
$ grunt go --fileName='xyzfile'
I the end I was able to accomplish what I wanted like this, but not sure if this is a standard way.
What I was failing to do was declare the globalConfig variable globally first, so that I could redefine it from the Terminal as I ran my grunt task.
Here is an example. When working with HTML emails I need to:
Process my sass files to css (grunt-contrib-sass)
Run an autoprefixer on the resulting css (grunt-autoprefixer)
Minify my CSS and remove CSS comments (grunt-contrib-cssmin)
Include my full CSS in a tag the of my html file (using grunt-include-replace)
Finally, run premailer on the file to inline all styles (grunt-premailer)
The point is, if I am working on several different HTMl emails in the same project, I need to be able to run all these tasks on html files one-by-one, as needed. The Gruntfile below allows me to do this.
What this does:
If you enter into terminal grunt It will simply run the sass task, which processes all sass files - no file parameter needed from Terminal.
However, if I wish to run a series of processes on a single html file, I enter grunt process:fileName with fileName being the name of the html file without the .html extension.
You will notice that the only tasks that require the fileName are actually include-replace and premailer. However, I still want to run al the other CSS cleanup tasks prior to targetting my chosen file.
The key is:
Declaring the global variable
Load the globalConfig variables into the grunt.initConfig
Use the grunt variable declaration where needed in your tasks
register your custom task, with the fileName variable being used as a paramater.
Hope that helps someone.
module.exports = function (grunt) {
var globalConfig = {
file: 'index' // this is the default value, for a single project.
}
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
// load the globalConfig variables
globalConfig: globalConfig,
sass: {
dev: {
files: [{
expand: true,
cwd: 'scss',
src: ['*.scss'],
dest: 'css',
ext: '.css'
}]
}
},
cssmin: {
options: {
keepSpecialComments: 0,
keepBreaks: true,
advanced: false
},
target: {
files: [{
expand: true,
cwd: 'css',
src: '*.css',
dest: 'css',
ext: '.css'
}]
}
},
autoprefixer: {
css: {
src: "css/*.css"
}
},
includereplace: {
your_target: {
options: {
prefix: '\\/\\* ',
suffix: ' \\*\\/',
},
files: {
'inline/<%= globalConfig.file %>-inline.html': ['<%= globalConfig.file %>.html']
}
}
},
premailer: {
main: {
options: {
verbose: true,
preserveStyles: true,
},
src: 'inline/<%= globalConfig.file %>-inline.html',
dest: 'inline/<%= globalConfig.file %>-inline.html'
}
},
});
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.loadNpmTasks('grunt-include-replace');
grunt.loadNpmTasks('grunt-premailer');
grunt.registerTask('default', 'sass');
grunt.registerTask('process', 'Runs all processing tasks on a specific file to produce inlined file', function (fileName) {
globalConfig.file = fileName;
grunt.task.run('sass', 'autoprefixer', 'cssmin', 'includereplace', 'premailer');
});
}
EDIT: Obviously at the moment this accepts only one parameter I beleive. In other use cases the grunt.option version above could give more functionality, being able to submit several parameters in one command. I will continue to experiment with grunt.option if I find the need to do this.

Resources