I'm trying to understand how to load fonts via fontawsome when loading them through a scss file:
this is my webpack config:
const path = require('path');
const webpack = require('webpack');
const UrlLoader = require('url-loader');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
publicFolder = path.resolve(__dirname, 'public');
// appFolder = path.resolve(__dirname, 'app');
module.exports = {
entry: {
// Selects main js file
main: './public/es6/index.js'
},
output: {
// Main path for the js folder
path: path.resolve(__dirname, 'public/js/'),
// Select teh name the main js file (after compression)
filename: 'bundle.js',
// Public path
// publicPath: 'http://localhost:8080',
publicPath: '/public/js/',
// Name the chunkFile (in case of external scripts)
chunkFilename: '[name].[contenthash].js'
},
module: {
rules: [ // Vue Files
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader',
options: {
loader: {
scss: 'vue-style-loader!css-loader!sass-loader',
css: 'vue-style-loader!css-loader'
}
}
},
// JS files
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
// CSS / SASS files
{
test: /\.(sa|sc|c)ss$/,
// test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
url: false,
minimize: true,
sourceMap: true
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
minimize: true
}
}
]
},
// Forgot why I need this...
{
test: /\.(png|gif|jpe|jpg|woff|woff2|eot|ttf|svg)(\?.*$|$)/,
use: [{
loader: 'url-loader',
options: {
limit: 100000
}
}]
}
]
},
plugins: [
// Load jQuery globally
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}),
// Hot module
// new webpack.HotModuleReplacementPlugin(),
// BrowserSync: Load page automatically on change
new BrowserSyncPlugin({
proxy: 'https://potato.mywebsite.com/',
port: 3000,
files: [
'**/*.php'
],
ghostMode: {
clicks: false,
location: false,
forms: false,
scroll: false
},
minify: false,
injectChanges: true,
logFileChanges: true,
logLevel: 'debug',
logPrefix: 'webpack',
notify: true,
reloadDelay: 0
}),
// Provides a way to customize how progress is reported during a compilation
new webpack.ProgressPlugin(),
// Loads Vue
new VueLoaderPlugin(),
// For webpack-dev-server (currently not in use)
// new webpack.HotModuleReplacementPlugin(),
// Handle css in different files (scss file in login.js for example to a hashed login.css file)
new MiniCssExtractPlugin({ filename: '../css/[name].css' }),
// CSS assets during the Webpack build and will optimize \ minimize the CSS
new OptimizeCSSAssetsPlugin({}),
// Not sure if needed yet
new webpack.NamedModulesPlugin()
],
devServer: {
// https: true,
headers: { 'Access-Controll-Allow-Origin': '*' },
compress: true,
quiet: true,
hot: true,
inline: true
}
};
And this is my SCSS file where I load FontAwesome (and others)..
#import 'variable';
// Colors
#import 'colors/default';
#import 'colors/green';
#import 'colors/megna';
#import 'colors/purple';
#import 'colors/red';
#import 'colors/blue';
#import 'colors/blue-dark';
#import 'colors/default-dark';
#import 'colors/green-dark';
#import 'colors/red-dark';
#import 'colors/megna-dark';
#import 'colors/purple-dark';
// Import Bootstrap source files
#import "../../node_modules/bootstrap/scss/bootstrap";
// This is for the icons
#import '../assets/icons/font-awesome/css/fontawesome-all.css';
#import '../assets/icons/simple-line-icons/css/simple-line-icons.css';
#import '../assets/icons/weather-icons/css/weather-icons.min.css';
#import '../assets/icons/themify-icons/themify-icons.css';
#import '../assets/icons/flag-icon-css/flag-icon.min.css';
#import "../assets/icons/material-design-iconic-font/css/material-design-iconic-font.min.css";
// This is the core files
#import 'core/core';
#import 'widgets/widgets';
#import 'responsive';
// In This scss you can write your scss
#import 'custom';
When running npm run dev (or others) i don't get any error mentioning fonts.
When loading my website I get these URLS refrences in the "network" pannel:
https://mywebsite.potato.com/public/webfonts/fa-regular-400.woff
https://mywebsite.potato.com/public/fonts/Simple-Line-Icons.ttf?-i3a2kk
pointing on font files that don't even exist in my directory (or at least do't get created..)
How do I load fonts properly?
EDIT:
Adding this for #FabioCosta
{
test: /\.(png|gif|jpe|jpg|woff|woff2|eot|ttf|svg)(\?.*$|$)/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
// name: '[path][name].[ext]',
outputPath: '/public/fonts/',
publicPath: '/public/fonts/'
}
}
]
}
Adding the full module part:
module: {
rules: [ // Vue Files
{
test: /\.vue$/,
exclude: /node_modules/,
loader: 'vue-loader',
options: {
loader: {
scss: 'vue-style-loader!css-loader!sass-loader',
css: 'vue-style-loader!css-loader'
}
}
},
// JS files
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
// CSS / SASS files
{
test: /\.(sa|sc|c)ss$/,
// test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
url: false,
minimize: true,
sourceMap: true
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
minimize: true
}
}
]
},
{
test: /\.(png|gif|jpe|jpg|woff|woff2|eot|ttf|svg)(\?.*$|$)/,
use: [
{
loader: 'file-loader',
options: {
// name: '[path][name].[ext]',
name: '[name].[ext]',
outputPath: '/public/fonts/',
publicPath: '/public/fonts/'
}
}
]
}
Tried following: https://chriscourses.com/blog/loading-fonts-webpack
and doesn't seem to work.
Adding CSS screentshot
UPDATE
With base on the github files, you are targeting the unchanged css on the php file. That will not be parsed by webpack, remove it.
<link rel="stylesheet" type="text/css" href="css/main.css">
If you run npm run build your entry point is the JS file, this will be the parsed JS file though webpack that will generate all your files and will need to be included.
Then you are using mini css extract plugin to copy your css to somewhere, you need to load that file.
By your current configuration it is saving one level up on a css folder:
new MiniCssExtractPlugin({ filename: '../css/[name].css' }),
Whatever this file outputs is what you should be loading not the original main.css, so by your current folder structure this file would be on one level UP path. Not in the public/css that you are probably expecting, if I am not mistaken if you use ./css/[name].css it should output to where you are expecting.
As a side note here, it seems you are using the same folder for the source and output. Try to move to separated folders just so you don't overwrite anything unwillingly.
Finally the fonts:
The test of the loader needs to match your font
src: url("../webfonts/fa-brands-400.eot");
does not match the test:
test: /\.(png|gif|jpe|jpg|woff|woff2|eot|ttf|svg)(\?.*$|$)/,
You probably want to make that last part optional
test: /\.(png|gif|jpe|jpg|woff|woff2|eot|ttf|svg)(\?.*$|$)?/,
also your css-loader has url=false so the font resolver would never be invoked. Remove the url: false from your loader. Then is just a case of playing with the options of the file-loader, you can change the public-path to go to whatever you store your files and they would be replaced on the generated css and output path to copy them to the desired location.
So to summarise:
Check if you are importing the right css file, rename the file and see where it lands if you need assurance
If you want the url and the loader to be replaced , remove the url:false from css-loader and ensure the fonts files regex is matching them.
To avoid confusion store all the output on separated folder and check what lays where.
First answer:
If you are already using font-awesome and webpack I would suggest you use the font-awesome-loader.
That would be the easiest way to load them but a deeper explanation is that basically for every file extension webpack requires a loader to handle it. It will handle the file appropriately and put its contents somewhere.
So the steps to make webpack load the fontawesome fonts are:
Install the font awesome package in your project (or have the assets on some fixed place).
Load the font files using some loader like below
module.exports = {
module: {
loaders: [
// the file-loader will copy the file and fix the appropriate url
{
test: /\.(ttf|eot|svg|woff(2)?)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "fonts/",
publicPath: "../fonts/"
}
}
]
}
};
Load the appropriate CSS/SCSS/LESS from font-awesome on your css.
So analysing your code on this part:
// Forgot why I need this...
{
test: /\.(png|gif|jpe|jpg|woff|woff2|eot|ttf|svg)(\?.*$|$)/,
use: [{
loader: 'url-loader',
options: {
limit: 100000
}
}]
}
You decided to load all these extensions through url-loader so they would become base64 URIs.
You loaded all font awesome css here
#import '../assets/icons/font-awesome/css/fontawesome-all.css';
If you check the CSS it is referencing the files by a given path and you choose the url-loader so the path will not match. If you change to the file-loader and make the options match the appropriate path it will copy the files there and you should be all set to go.
I've just posted a detailed answer on another similar question like this. That could help you and also includes another possible solution with the new way of using FontAwesome5 with SVG+JS. With that there's no need for font files, Webpack loaders, etc... Just a few extra lines in your JavaScript code.
(I hope posting an answer like this is not against the rules. That another is a long writing, I don't want to copy-paste it. Should I? I don't think the duplicate flag could be used here...)
Related
I am trying to get Webpack to render a static html and export relative css, starting from a pug template and css modules using HtmlWebpackPlugin.
My setup is as follow
//webpack.config.js
...
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "src/templates/index.pug",
excludeChunks: ["landing", "runtime"],
excludeAssets: [/index.*.js/]
})
new HtmlWebpackExcludeAssetsPlugin(),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
chunkFilename: "[name].[contenthash].css"
})
]
module: {
rules: [
/**
* pug
*/
{
test: /\.pug$/,
exclude: /node_modules/,
use: ["pug-loader"]
},
/**
* scss
*/
{
test: /\.scss$/,
exclude: /node_modules|_.+.scss/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === "development"
}
},
{
loader: "css-loader",
options: {
modules: true,
localsConvention: "camelCase",
sourceMap: true
}
},
"postcss-loader",
"sass-loader"
]
}
]
}
...
Then in my index.pug file I would like to do something like this:
- var styles = require("../styles/style.module.scss")
div(class=styles.someClass)
The problem is that if I leave the configuration as is, I get
ERROR in ./src/styles/style.module.scss
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
TypeError: this[MODULE_TYPE] is not a function
I managed to correctly get the transformed class names in the pug template by doing
/**
* scss
*/
{
test: /\.scss$/,
exclude: /node_modules|_.+.scss/,
use: [
//{
// loader: MiniCssExtractPlugin.loader,
// options: {
// hmr: process.env.NODE_ENV === "development"
// }
//},
{
loader: "css-loader",
options: {
modules: true,
localsConvention: "camelCase",
sourceMap: true,
onlyLocals: true <=====
}
},
"postcss-loader",
"sass-loader"
]
}
But by removing MiniCssExtractLoader from the chain I obviously don't get the exported css file.
By setting onlyLocals: true, classes in pug work as expected because css-loader exports the mappings.
If I remove - var styles = require("../styles/style.module.scss") from pug template and leave MiniCssExtractPlugin in the loaders' chain I get the opposite: css file, but no mappings.
Any idea? Is there a way to export the css file and get mappings back directly in the pug template?
P.S.
I have also come across this example from pug-loader's GitHub page
var template = require("pug-loader!./file.pug");
// => returns file.pug content as template function
// or, if you've bound .pug to pug-loader
var template = require("./file.pug");
var locals = { /* ... */ };
var html = template(locals);
// => the rendered HTML
Thought template(locals) would inject the locals into the template but I might have misinterpreted, since the html returned have unchanged classes' names, thus problem persists.
Just found out, all it takes is inlining the loaders applied in the pug template
- var styles = require("!css-loader?modules&onlyLocals&localsConvention=camelCase!postcss-loader!sass-loader!../styles/style.module.scss")
and having the Webpack configuration to export the css file
entry: {
index: ["./src/styles/style.module.scss"]
},
module: {
rules: [
/**
* scss
*/
{
test: /\.scss$/,
exclude: /node_modules|_.+.scss/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV === "development"
}
},
{
loader: "css-loader",
options: {
modules: true
}
},
"postcss-loader",
"sass-loader"
]
}
]
},
If anyone knows a better solution to make use of source maps please post it.
I have extended default web pack config in Ionic v3 for forcing cache busting.
I am able to fingerprint generated JavaScript artifacts, but I am unable to fingerprint images and JSON files under the assets folder. I took Help from Bundled files and cache-busting.
An excerpt of webpack config.js
module.exports = {
// ...
output: {
filename: '[name].[chunkhash].js',
chunkFilename: '[name].[chunkhash].js',
},
plugins: [
new WebpackChunkHash({algorithm: 'md5'}) // 'md5' is default value
]
}
The above is the approach for fingerprinting JavaScript bundles, and it's working fine. I want to add hashes/fingerprint images and JSON files inside the assets folder. I used the same approach for images also, but it did not work.
I have extended webpack config.js and added a new rule for images. By default webpack directly copies the images and assets to the output folder.
Copy Config.js
module.exports = {
copyAssets: {
src: ['{{SRC}}/assets/**/*'],
dest: '{{WWW}}/assets'
},
copyIndexContent: {
src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],
dest: '{{WWW}}'
},
copyFonts: {
src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],
dest: '{{WWW}}/assets/fonts'
},
Here images and other assets are directly copied.
I have added a new rule in extended webpack.config.js, but the build process is ignoring it. How do I fix this issue?
Excerpt of webpack config.js
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]',//adding hash for cache busting
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
entire Webpack.config.js
/*
* The webpack config exports an object that has a valid webpack configuration
* For each environment name. By default, there are two Ionic environments:
* "dev" and "prod". As such, the webpack.config.js exports a dictionary object
* with "keys" for "dev" and "prod", where the value is a valid webpack configuration
* For details on configuring webpack, see their documentation here
* https://webpack.js.org/configuration/
*/
var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);
var ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
var PurifyPlugin = require('#angular-devkit/build-optimizer').PurifyPlugin;
var optimizedProdLoaders = [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.js$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '#angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
]
},
{
test: /\.ts$/,
loader: [
{
loader: process.env.IONIC_CACHE_LOADER
},
{
loader: '#angular-devkit/build-optimizer/webpack-loader',
options: {
sourceMap: true
}
},
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]',
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
},
{
loader: process.env.IONIC_WEBPACK_LOADER
}
]
}
];
function getProdLoaders() {
if (process.env.IONIC_OPTIMIZE_JS === 'true') {
return optimizedProdLoaders;
}
return devConfig.module.loaders;
}
var devConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: [
{
test: /\.json$/,
loader: 'json-loader'
},
{
test: /\.ts$/,
loader: process.env.IONIC_WEBPACK_LOADER
},
{
test: /\.(jpg|png)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath:'assets/imgs',
publicPath:'assets/imgs'
},
}},
]
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin()
],
// Some libraries import Node.js modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
var prodConfig = {
entry: process.env.IONIC_APP_ENTRY_POINT,
output: {
path: '{{BUILD}}',
publicPath: 'build/',
filename: '[name].js',
devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
},
devtool: process.env.IONIC_SOURCE_MAP_TYPE,
resolve: {
extensions: ['.ts', '.js', '.json'],
modules: [path.resolve('node_modules')]
},
module: {
loaders: getProdLoaders()
},
plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin()
],
// Some libraries import Node.js modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
module.exports = {
dev: devConfig,
prod: prodConfig
}
Using Webpack 4 you should not need any additional plugins or loaders.
It will give you the naming option [contenthash].
Also, it looks like you have this block nested under the test: .ts block.
{
test: /\.(png|jpg|gif)$/,
loader: 'file-loader',
options: {
name:'[name].[hash].[ext]', // Adding hash for cache busting
outputPath:'assets/imgs',
publicPath:'assets/imgs'
}
}
Ultimately, you can do something like this:
// Copy static assets over with file-loader
{
test: /\.(ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: '[name].[contenthash].[ext]'},
},
{
test: /\.(woff|woff2|eot|ttf|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: 'fonts/[name].[contenthash].[ext]'},
},
{
test: /\.(jpg|gif|png|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader', options: {name: 'images/[name].[contenthash].[ext]'},
}
]
Using [chunkhash] instead of content should still work, and if you're not using webpack4 do that, but otherwise for more information see this issue for an explanation.
For more help, read the long-term caching performance guide from Google and the latest caching documentation from Webpack.
the files copied via CopyPlugin will not pass to loaders.
So even you have correct loader setting with hashname for images, it doesn't work.
but you can see https://github.com/webpack-contrib/copy-webpack-plugin#template
the CopyPlugin provide you a way to specify output name which can be set with hash:
module.exports = {
plugins: [
new CopyPlugin([
{
from: 'src/',
to: 'dest/[name].[hash].[ext]',
toType: 'template',
},
]),
],
};
Eventually, I used gulp for fingerprinting static assets.
Drop Angular Output hashing and build the application.ng build --prod --aot --output-hashing none .
post-build execute a gulp script which would fingerprint all the assets and update the references.
npm i gulp gulp-rev gulp-rev-delete-original gulp-rev-collector
gulpfile.js
const gulp = require('gulp');
const rev = require('gulp-rev');
const revdel = require('gulp-rev-delete-original');
const collect = require('gulp-rev-collector');
// finger priniting static assets
gulp.task('revision:fingerprint', () => {
return gulp
.src([
'dist/welcome/**/*.css',
'dist/welcome/**/*.js',
'dist/welcome/**/*.{jpg,png,jpeg,gif,svg,json,xml,ico,eot,ttf,woff,woff2}'
])
.pipe(rev())
.pipe(revdel())
.pipe(gulp.dest('dist/welcome'))
.pipe(rev.manifest({ path: 'manifest-hash.json' }))
.pipe(gulp.dest('dist'));
});
gulp.task('revision:update-fingerprinted-references', () => {
return gulp
.src(['dist/manifest-hash.json', 'dist/**/*.{html,json,css,js}'])
.pipe(collect())
.pipe(gulp.dest('dist'));
});
gulp.task(
'revision',
gulp.series(
'revision:fingerprint',
'revision:update-fingerprinted-references'));
Add a new script in package.json
"gulp-revision": "gulp revision"
Execute npm run gulp-revision Post-build.
Solving Browser Cache Hell With Gulp-Rev
Using webpack-assets-manifest you can generate a map of asset names to fingerprinted names like so:
{
"images/logo.svg": "images/logo-b111da4f34cefce092b965ebc1078ee3.svg"
}
Using this manifest you can then rename the assets in destination folder, and use the "correct", hash-inclusive src or href in your project.
The fix isn't framework-specific.
So I'm facing this problem that in my project I want to use sass as well as Vue but it wont compile.
So far I'm using vue-cli and have to init a normal project(vue init webpack project) and followed the guide here for global https://vue-loader.vuejs.org/en/configurations/pre-processors.html
And I wand the SCSS file to compile to a CSS and place itself in a folder.
After I've done that nothing happens it runs but without CSS.
Basically I want this -> src/asset/css/style.scss
To compile to this location -> static/css/style.css <- and then be a CSS
file because then I can load it into main.html
This is the webpack.base.conf file
{
test: /\.scss$/,
use: [
'sass-loader',
'style-loader',
'css-loader',
'postcss-loader',
{
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, '../src/assets/css/layout.scss')
},
},
],
},
This is the utils file
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass').concat(
{
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, '../src/assets/css/layout.scss')
}
}
),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
How will I be able to fix this?
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/"
}
}
]
}
]
}
I am building a component library and I am using Webpack to bundle it. Some components only rely on html templates, css and JavaScript that I've written, but some components require external libraries.
What I'd like to achieve is a vendor.js that is optional to include if the component you want to use needs it.
For instance, If a user only needs a component without vendor dependencies, it would suffice that they use main.bundle.js which only contains my own code.
In my index.js, I have the following imports:
import { Header } from './components/header/header.component';
import { Logotype } from './components/logotype/logotype.component';
import { Card } from './components/card/card.component';
import { NavigationCard } from './components/navigation-card/navigation-card.component';
import { AbstractComponent } from './components/base/component.abstract';
import { Configuration } from './system.config';
import 'bootstrap-table';
import './scss/base.scss';
All of these imports are my own, expect for bootstrap-table.
I have configured Webpack like this:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractScss = new ExtractTextPlugin({
filename: "[name].bundle.css"
});
module.exports = {
entry: {
main: './src/index.ts'
},
output: {
path: path.resolve(__dirname, 'dist/release'),
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', // Specify the common bundle's name.
minChunks: function (module) {
// Here I would like to tell Webpack to give
// each bundle the ability to run independently
return module.context && module.context.indexOf('node_modules') >= 0;
}
}),
extractScss
],
devtool: "source-map",
resolve: {
// Add `.ts` as a resolvable extension.
extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.ejs']
},
module: {
rules: [
// All files with a '.ts' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.ts?$/, exclude: /node_modules/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Allows for templates in separate ejs files
{test: /\.ejs$/, loader: 'ejs-compiled-loader'},
{
test: /\.scss$/,
use: extractScss.extract({
use: [{
loader: 'css-loader', options: {
sourceMap: true
}
}, {
loader: 'sass-loader', options: {
soureMap: true
}
}]
})}
]
}
}
This results in two .js files and one .css. However, webpacks common module loading functionality resides in vendor.js, and that renders my main unusable if I don't include vendor first, and it isn't always needed.
To sum it up, if a user only needs the footer (no external dependencies), this would suffice:
<script src="main.bundle.js"></script>
If the user wants to use the table, which has an external dependency, they would need to include both:
<script src="vendor.js"></script>
<script src="main.bundle.js"></script>
Right now, including only main.bundle.js gives me this error:
Uncaught ReferenceError: webpackJsonp is not defined.
I am aware that I can extract all common functionality by adding this after my vendor chunk is created in the Webpack config:
new webpack.optimize.CommonsChunkPlugin({
name: 'common'
})
But this approach still requires the user to include two .js files.
How can I go about achieving this? It seems that it only differs 2 kb when I don't extract the common modules like I do above, and that is fine with me.
Turns out this is very easy to do if you can stand some manual work and actually understand what Webpack does (which I didn't). I solved it like this:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractScss = new ExtractTextPlugin({
filename: "[name].bundle.css"
});
module.exports = {
entry: {
main: './src/index.ts',
vendor: './src/vendor/vendor.ts'
},
output: {
path: path.resolve(__dirname, 'dist/release'),
filename: "[name].bundle.js",
chunkFilename: "[name].bundle.js"
},
plugins: [
extractScss
],
devtool: "source-map",
resolve: {
// Add `.ts` as a resolvable extension.
extensions: ['.webpack.js', '.web.js', '.ts', '.js', '.ejs']
},
module: {
rules: [
// All files with a '.ts' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.ts?$/, exclude: /node_modules/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Allows for templates in separate ejs files
{test: /\.ejs$/, loader: 'ejs-compiled-loader'},
{
test: /\.scss$/,
use: extractScss.extract({
use: [{
loader: 'css-loader', options: {
sourceMap: true
}
}, {
loader: 'sass-loader', options: {
soureMap: true
}
}]
})}
]
}
}
In vendor.ts, I then simply import any vendor dependencies I have:
import 'jquery';
import 'bootstrap-table';
This results in two different files, both have Webpacks bootstrapping logic.
Hope this helps someone.