Concat and minify CSS files with Webpack without requiring them from JS - javascript

what I'd like to achieve is the simplest way to concat and minify CSS with Webpack. I (successfully) tried ExtractTextPlugin and OptimizeCssAssetsPlugin, as follows
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
entry: {
app: "./assets/js/main.js"
},
output: {
path: 'dist',
filename: "main.bundle.js"
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin({
filename: "style.min.css",
allChunks: true
}),
new OptimizeCssAssetsPlugin()
]
}
But this only works requiring CSS files from within JS file, like that
require("../../css/normalize.css");
require("../../css/modal.css");
I would like JS files had nothing to do with CSS files.
My question is: how can I avoid requiring CSS file from JS entry point, and thus how can I specify in webpack.config.js what CSS file to concat and minify? So I want to write in webpack.config.js something like "i want to concat and minify all CSS files contained in /assets/css/ folder".
Thank you

Related

Images not displaying on production build, only in dev (webpack/sass-loader)

When running my app on webpack dev server, all my image files are working whether as img tags in my index.html or background-image: url()..
Running my project though in production build, I am getting file reference errors that they cannot be found.
GET file:///img/featured.png net::ERR_FILE_NOT_FOUND
GET file:///img/header-img1-1.jpg net::ERR_FILE_NOT_FOUND
I added the copy webpack plugin as I thought this would move all images from my src/img folder to my img folder inside dist.
Should I be using the contentBase option for webpack-dev-server? Or is the copy-webpack-plugin not getting the correct reference? Super confused
Project tree:
- webpack.config.js
- package.json
- .babelrc
- src
- js
- index.js
- ...
- img
- ALL IMAGES LOCATED HERE
- scss
- layout
- landing.scss
- brands.scss
- base
- index.html
inside landing.scss
i have used
background: url('~/img/img-title.png')
same in other files like brands.scss
background: url('~/img/img-title.png')
Which has all worked fine, and I think I've confused myself with how images are referenced with webpack/sass loader, and can't seem to work out how to get the image paths to work for both dev/production, i can only seem to get one working at a time.
production tree:
- dist
- css
- img
- js
- index.html
webpack.config.js:
const path = require('path');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractPlugin = new ExtractTextPlugin({
filename: 'css/main.css'
});
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtPlugin = require('script-ext-html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = (env) => {
const isProduction = env.production === true
return {
entry: './src/js/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
use: 'babel-loader'
},
{
test: /\.css$|\.scss$/,
include: path.resolve(__dirname, 'src'),
use: extractPlugin.extract({
fallback: "style-loader",
use: [
{ loader: 'css-loader', options: { importLoaders: 2, sourceMap: true }},
{ loader: 'postcss-loader', options: { sourceMap: true, plugins: () => [autoprefixer] }},
{ loader: 'sass-loader', options: { sourceMap: true }},
],
})
},
{
test: /\.html$/,
use: ['html-loader']
},
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000,
name: 'img/[name].[ext]',
}
}
]
}
]
},
plugins: [
extractPlugin,
new HtmlWebpackPlugin({
filename: 'index.html',
inject: true,
template: './src/index.html'
}),
new ScriptExtPlugin({
defaultAttribute: 'async'
}),
new CleanWebpackPlugin(['dist']),
new CopyWebpackPlugin([
{from:'src/img',to:'img'}
]),
]
}
}
I think you're using different folder structure on production than on local, i.e. on local, it's just http://localhost:PORT/app, but on prod, it must be similar to http://produrl/Some_Folder/app
Now coming to actual issue - It's your CSS loader.
By default css-loader, has url=true, causing all URLs to be mapped relative to root. Hence this works for you -
background: url('~/img/img-title.png')
but this doesn't
background: url('../../img/img-title.png')
Just set url=false, and you'll be able to provide relative URL and it'll load for all enviorments correctly.
While the accepted answer may works in your specific scenario, I think there is a better solution that do not involve disabling css-loader url() handling and will works better in most of situation.
Tilde ~ problem
When you use ~ to import something in css, css-loader will search for that file inside node_modules. Your images are inside src/img folder so you do not need tilde ~ to import your images.
url() problem
sass-loader doesn't not resolve url() correctly if they are not in the same directory of the entry-file.
In your specific example you import some urls inside src/scss/layout/landing.scss and src/scss/layout/brands.scss but I guess your main scss entry point is inside src/scss folder.
Note: for "main scss entry point" I mean the scss file that you import inside your javascript entry point src/js/index.js
So, in your example, every images imported within a scss file that is not inside src/scss folder will trow an error.
To solve this problem use resolve-url-loader which resolves relative paths in url() statements based on the original source file.
[
{ loader: 'css-loader'}, // set `importLoaders: 3` if needed but should works fine without it
{ loader: 'postcss-loader'},
{ loader: 'resolve-url-loader' }, // add this
{ loader: 'sass-loader',
// sourceMap is required for 'resolve-url-loader' to work
options: { sourceMap: true }
}
]
CopyWebpackPlugin is optional
Based on your configuration you do not need CopyWebpackPlugin because your images are already handle by url-loader.
Note: url-loader doesn't output your images but inline them, images are outputted by file-loader when the file is greater than the limit (in bytes).
file-loader will output your images in dist/img folder, because you set name: 'img/[name].[ext]', and you set output.path: path.resolve(__dirname, 'dist').
Hey I think your issue is just in how you're referencing the image in your scss.
Based on your folder structure it should be:
background: url('../../img/img-title.png')
You could modify the "publicPath" URL (which is relative to the server root) to match the file structure.
//webpack.config.js
...
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "assets/images/",
publicPath: "/other-dir/assets/images/"
}
}
]
}
]
}

CSS file won't rebuild after commenting out file when using extract-text-webpack-plugin

I am trying to extract CSS into its own file when imported into a JavaScript file. The initial build works fine, but when I am watching the files, and I comment out the line that imports the css (import './test.css';), webpack only rebuilds the JS file, and the CSS file (main.css) is not updated (it leaves the styles in the stylesheet).
When I comment out the css import, how can I get the css file to rebuild?
webpack.config.js
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'static'),
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new ExtractTextPlugin('main.css')
],
};
index.js
// If I comment out the following import while the watch is running,
// it will not remove the CSS from main.css
import './test.css';
document.body.innerText = 'Hello';
test.css
body {
background: lightcoral;
}

Automatically loading externals with Webpack

I've done some searching but was wondering if there's an elegant solution here. When building a Webpack app, it's common to have dependencies that don't need to be compiled/bundled, like jQuery, React, ReactDOM, Angular, or Bootstrap, to name a few. You can list these in your Webpack config file in an externals object, but externals just assumes that these libraries will be available as namespaced globals at runtime.
This means that for each entry in your externals hash, you also need to toss in a script tag in your HTML. This makes sense if you're referencing an external CDN, but I'm thinking this could be automated if all you want to do is copy some dist file from a library in node_modules.
I've been looking for examples of how to do this but I haven't seen any yet. I messed with external-loader but I haven't had any luck integrating it (the documentation doesn't seem to provide a complete example).
Essentially, this would need to happen:
Libraries that shouldn't be bundled should be added to resolve.alias, e.g. {"react": "react/dist/react.js"}
A loader copies the dist files to the public directory (maybe this could just be done with file-loader?)
An HTML loader or maybe plugin inserts the script tags before the bundle.js script tag
If something like this doesn't exist, I might look into trying to make one; I'm just posting this here to see if anyone might know of a pre-baked solution, as it seems like it'd be a common problem for building web apps and I figured I'm probably missing something.
var path = require("path");
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
entry: {
'index-ref': './app/index-ref.ts',
'vendor': './app/vendor.ts',
'app': './app/main.ts',
},
resolve: {
extensions: ['', '.ts', '.js']
},
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.css$/,
exclude: helpers.root('app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('app'),
loader: 'raw'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'index-ref']
}),
new HtmlWebpackPlugin({
filename: '../index.html',
template: 'template' + '/default.html',
lib: ['jQuery'],
chunks: ['entry-name']
}),
new HtmlWebpackExternalsPlugin([
// Using a CDN for a JS library
{
name: 'jquery',
var: 'jQuery',
url: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.js'
}
],
{
basedir: 'node_modules',
dest: 'lib'
}),
new WebpackNotifierPlugin()
]
};
Am I missing anything here?
I didn't find a pre-existing solution, so I wrote a plugin to supplement the HtmlWebpackPlugin. It takes an array of externals and appends script/link tags to the HTML file, generates the externals hash, and can use CDNs or local files.
https://github.com/mmiller42/html-webpack-externals-plugin
If you don't want to add extra package bloat then HtmlWebpackPlugin has templating features, so you could do something like this:
//template.html
<html>
<head>
<%= htmlWebpackPlugin.options.externals %>
</head>
...
</html>
and then something like this in your webpack config:
//webpack.config.js
const EXTERNALS = [
{
name: 'react',
globalVarName: 'React',
src: 'https://cdn.example.com/react#18',
},
...
]
module.exports = {
...,
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'template.html',
externals: EXTERNALS.reduce(
(scripts, external) => (
`${scripts}<script src="${external.src}"></script>`
), ''
)
})
],
externals: EXTERNALS.reduce(
(res, external) => ({
...res,
[external.name]: external.globalVarName,
}), {}
),
}
(You can obviously add any environment customisations/finer details/etc. you want in the webpack config file.)
EDIT:
For some extra panache you could also get the current package versions from your node_modules rather than hard-coding them into the webpack file:
const fs = require('fs')
function getPackageVersion(packageName) {
const pkgPath = `node_modules/${packageName}`
const pkg = JSON.parse(fs.readFileSync(`${pkgPath}/package.json`, 'utf8'))
return pkg['version']
}

webpack build less files output one css minify file

Is webpack the tool that I need to pass several less files into one minified CSS file?
if so, I'm not sure what I'm doing wrong in the code below?
Is there a way of outputting to different file paths, right now my js file outputs to './assets/javascripts/bundle/', I would like my css file to output to './assets/stylesheets/bundle/' , how would I do this?
Update
I did a test and I can build my less files to one css file but still can't find out how to set multiple paths for the output folder, now I have to comment out the js entry part and change output path...
webpack config
var path = require('path');
var webpack = require("webpack");
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: {
'MediaIndex':['./assets/javascripts/Media/Index/Base.js'],
// stylesheets
'MediaIndexStyleSheet':['./assets/stylesheets/Media/Index/Base.js']
},
output: {
path: './assets/javascripts/bundle/',
filename: '[name].js'
},
resolve: {
alias: {
jquery: path.join(__dirname, "assets/javascripts/lib/jquery-1.11.1.min.js"),
"jquery-ui": path.join(__dirname, "assets/javascripts/lib/jquery-ui.min.js"),
}
},
plugins: [
new webpack.ProvidePlugin({
jQuery: "jquery",
$: "jquery"
}),
new ExtractTextPlugin("[name].css")
],
module: {
loaders: [
{
test: /\.css$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
},
{
test: /\.less$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")
}
]
},
};
assets/stylesheets/Media/Index/Base.js
require('../../../Global/Config.less');
require('../../../Global/Fp.less');
require('../../../Global/Urb.less');
require('../../../Global/Ad1.less');
require('./Nav0.less');
require('./Index.less');
require('./Content.less');
Config.less
#color: 'red';
Index.less
body {
background-color: #{color};
}
You're almost there. Like you already did, you can get all your styling into one css file using ExtractTextPlugin. To put your js and css into a different directories, you can just define a path in ExtractTextPlugin or output.filename that is relative to your output.path. For example:
output: {
path: './assets',
filename: './javascripts/bundle/[name].js'
},
plugins: [
new ExtractTextPlugin("./stylesheets/bundle/[name].css")
]

How to include external file with webpack

Is it possible to include external file with webpack (outside the context) and make the file included in built output bundle.js?
consider this setup where "sub-app" is context for webpack:
/sub-app/entry.js
/bower-components/zepto/zepto.js
And webpack config with broccoli:
var webpackify = require('broccoli-webpack');
var path = require('path');
var webpack = require("webpack");
var bundler = webpackify(path.resolve('sub-app'), {
entry: './entry',
output: {filename: './bundle.js'},
devtool: 'eval',
module: {
loaders: [
{test: /\.js$/, loader: 'babel-loader'},
{test: /\.hbs$/, loader: "handlebars-loader"}
]
},
plugins: [
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin()
]
});
I would like to include zepto.js in output bundle.js. But I need to preserve bower_components outside the sub-app.
Ok found answer myself. No special adjustments are necessary. Only include external file in code with relative path:
In my case:
import zepto from './../bower_components/zepto/zepto.js';

Categories

Resources