I recently upgraded to Webpack 5 and my html-loader no longer loads svg files and inlines them.
Here's my svg rule in webpack
{
test: /\.svg$/,
use: [
{
loader: 'html-loader',
options: {
minimize: true,
},
},
],
},
No matter how I try to import it, it seems to just create a file and not give me a string of HTML.
import mySvg from "../path/to/my.svg"
let mySvg = require("../path/to/my.svg").default;
// output = "/build/path/my.svg"
// output I want = "<svg>...."
It used to not give me several build files instead it inlined them in my JS.
Help would be appreciated.
svg-inline-loader can achieve the same (confirmed to work).
I have listed other options for loading SVGs at https://survivejs.com/webpack/loading/images/#loading-svgs.
I use posthtml and posthtml-inline-svg plugin.
const postHtml = require('posthtml');
const postHtmlInlineSvg = require('posthtml-inline-svg');
{
test: /\.html$/,
use: {
loader: 'html-loader',
options: {
esModule: false,
preprocessor: async (content, loaderContext) => {
try {
return await postHtml(postHtmlInlineSvg({ cwd: loaderContext.context, tag: 'icon', attr: 'src' }),).process(content)).html;
} catch (error) {
loaderContext.emitError(error);
return content;
}
},
},
},
},
If you're using HtmlWebpackPlugin, the HtmlWebpackInlineSVGPlugin can be used to inline svgs.
Here are the relevant parts of the webpack config to achieve the same:
{
// ...
module: {
rules: [
{
test: /\.html/,
loader: "html-loader",
},
// ...
],
},
plugins: [
new HtmlWebpackPlugin(),
new HtmlWebpackInlineSVGPlugin({
inlineAll: true,
}),
// ...
]
}
Related
My current configuration is as follows:
output: {
filename: 'bundle.js',
path: OUT_DIR
},
However I need bundles.js to go to two directories?
Can I accomplish this by simply passing an array of directories to path?
Or do I need to do something more complex?
Currently I have a bash script cpll which I have to type in manually after each build and it is tedious.
Hopefully web pack has a configuration option to send the output to two or more locations.
Research
google search
This SO question is 4 years old and does not have what I am lookin for - so
The documentation does not mention a way to do it here - webpack.
If there is not a configuration option how can I have it run a bash command automatically?
I tried passing it an array of strings instead of a string and it crashed with the obvious error:
Invalid configuration object. Webpack has been initialised using a
configuration object that does not match the API schema.
- configuration.output.path should be a string.
Passing an array will not work. Hmmm.
Trying another approach starting with a google - search
Brings up a possible solution - so
per request - complete exportFunc
const exportFunc = ( env ) => {
console.log('webpack.config.js-exportFunc', OUT_DIR);
return {
entry: `${IN_DIR}/index.jsx`,
output: {
filename: 'bundle.js',
path: '/Users/c/_top/ll-front/dist'
},
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.jsx?/,
include: IN_DIR,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: ['#babel/plugin-proposal-object-rest-spread', '#babel/plugin-proposal-class-properties'],
}
}
}
]
}
};
};
module.exports = exportFunc;
You can use webpack's multi-compiler mode by exporting an array of configs.
As in the docs:
Instead of exporting a single configuration object/function, you may export multiple configurations (multiple functions are supported since webpack 3.1.0). When running webpack, all configurations are built.
For example:
const config = {
// your webpack config
}
const outputPaths = ["path/one", "path/two"]
module.exports = outputPaths.map(outputPath => {
return {
...config,
name: outputPath,
output: {
...config.output,
path: path.resolve(__dirname, outputPath)
}
}
})
As you're using a function config you can do something like this:
const outputPaths = ["path/one", "path/two"]
module.exports = outputPaths.map(outputPath => {
return env => {
return {
entry: `${IN_DIR}/index.jsx`,
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, outputPath)
},
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},
{
test: /\.jsx?/,
include: IN_DIR,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: ['#babel/plugin-proposal-object-rest-spread', '#babel/plugin-proposal-class-properties'],
}
}
}
]
}
};
}
})
You could use multiple configurations instead. Here's the webpack documentation link.
To avoid code duplicaion consider your current config as an object. Then duplicate that object and override the output section of the duplicated object. Finally put both objects into an array. Use that array as your new config.
var yourCurrentConfig = {...};
var secondaryDestinationConfig = Object.assign(yourCurrentConfig, {
output: {
path: {
filename: 'bundle.js',
path: SECONDARY_DIR
}
}
});
var newConfig = [
yourCurrentConfig, secondaryDestinationConfig
];
I am using Webpack 4 to build a simple website with an express backend. Now that I am implementing a custom element I am confronted with an issue regarding the shadow DOM.
The issue is as follows: all of my SCSS is being combined into one CSS output file. I need it to remain separate so that the custom element's style can be dynamically added to the shadow DOM, when the element is connected.
I am using extract-css-chunks-webpack-plugin (which I think the issue is with), css-loader, postcss-preset-env and sass-loader to handle all SCSS within the app/site.
All my searching thus far has simply lead me to the exact opposite of what I need (people trying to combine their SCSS).
I understand I could build the custom element separately and then just import it into the project after it has been built but that means managing two building environments and then having to version control across both -- seems like a lot of overhead.
The project's folder structure is as follows:
root
--src/
----assets/
------js/
--------main.js
------scss/
--------main.scss
------web-components/
--------contact-modal.js
--------scss/
----------modal.scss
My current webpack dev config is as follows:
**omitted**
module: {
rules: [{
test: /\.(scss)$/,
use: [{
loader: ExtractCssChunksPlugin.loader,
options: {
hot: true,
}
},
{
loader: 'css-loader',
options: {
sourceMap: true,
}
},
{
loader: 'postcss-loader',
options: {
indent: 'postcss',
plugins: () => postcssEnv(),
sourceMap: 'inline',
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
**omitted**
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: 'src/views/pages/index.ejs',
filename: 'index.html',
}),
new ExtractCssChunksPlugin({
filename: 'assets/css/[name].css',
chunkFilename: 'assets/css/[id].css',
})
]
If there is a better way of using custom elements which I have overlooked I'd appreciate any feedback.
Thank you in advance.
EDIT:
Figure it would be beneficial to state that I am including the modal.scss file with an import in the contact-modal.js file.
I'm assuming modal.scss is importing main.scss? If that's so, then your issue is with the Sass compiler itself, not webpack. Depending on what's in modal.scss, you may want to use #use instead
Edit 1:
If you're adding your compiled css in the shadow DOM in let's say a <style> tag, you can replace the ExtractCSSChunks loader with something like to-string-loader. In your js, import style from "modal.scss"; will have the compiled output in a string that you can use.
In order to solve my issue while still using SCSS (to allow me to use vendor prefixing from postcss-loader) I ended up prefixing my modal scss with a .modal flag so modal.scss became main.modal.scss.
I then edited my webpack config to have two rules for scss files: One which only affected .scss files and one which affected .modal.scss.
Then, in my modal I imported the scss with a normal import style from './main.modal.scss'; to then append it to the shadow DOM in a <style></style> element.
Code is as follows:
New SCSS rule
test: /(\.modal\.scss)$/,
use: [
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => [ postcssPresetEnv() ],
sourceMap: 'inline'
}
},
'sass-loader'
]
Modified old SCSS rule
test: /(?<!\.modal)\.scss$/,
use: [{
loader: ExtractCSSChunksPlugin.loader,
options: {
hot: true,
}
},
{
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 3
}
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [
postcssPresetEnv()
],
sourceMap: 'inline'
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
}
}
]
In my contact-modal.js file
import CSS from './scss/main.modal.scss';
// ... omitted ...
connectedCallback() {
// ... omitted ...
const style = document.createElement('style');
style.innerHTML = CSS.toString();
this.shadow.appendChild(style);
// ... omitted ...
}
What I'm trying to do:
Render an svg icon.
My results: Although svg and img property append to DOM, HTML does not render icon and I get no webpack or browser error.
My component file structure/ approach
I import SVG into sidebar.js
sidebar.js
import './sidebar.scss';
import photo from './usrPic.jpg';
import '../base/SVG/twitter.svg';
import '../base/sprite.svg';
export default class Sidebar {
render() {
const img = document.createElement('img');
img.alt = 'image';
img.width = 200;
img.src = photo;
img.classList = 'sidebar__user-photo';
// const svg = document.createElement('svg');
const use = document.createElement('use');
use.setAttribute('href', `../base/SVG/twitter.svg#icon-twitter.svg`);
const html = `<div class="sidebar-box">
<div class="sidebar-header">
<span class="sidebar-title__main">Something</span>
<span class="sidebar-title__sub">Sub</span>
<div class="Icon-box">
<svg class="twitter__icon">
</svg>
</div>
</div>
</div>`;
const contentBox = document.querySelector('.content');
contentBox.insertAdjacentHTML('beforeend', html);
const sidebarHeader = document.querySelector('.sidebar-header');
sidebarHeader.insertAdjacentElement("afterbegin", img);
const twitterIcon = document.querySelector('.twitter__icon');
twitterIcon.insertAdjacentElement("afterbegin", use);
}
}
webpack.dev.config
...
{
test: /\.svg$/,
use: 'svg-sprite-loader'
},
{
test: /\.(png|svg|jpg|gif)$/,
use: 'file-loader',
},
html
HTML code
What I have tried thus far:
swapping sprite.svg with direct icon.svg in <use href="..." />
I swapped <use href="..." /> . with <use xlink:href="..." />
I swapped the element with "img" property.
Applied a 40px to width & height on svg element
Given all this, I do not get an error yet the element is appended to
the DOM, but no icon.
Any assistance is greatly appreciated.
1st UPDATE:
sidebar.js
Was modified to this:
...
var SVG_NS = 'http://www.w3.org/2000/svg';
var XLink_NS = 'http://www.w3.org/1999/xlink';
const use = document.createElementNS(SVG_NS, 'image');
use.setAttributeNS(null, 'width', '100');
use.setAttributeNS(null, 'height', '100');
use.setAttributeNS(XLink_NS, 'xlink:href', '../base/sprite.svg#icon-twitter' );
const twitterIcon = document.querySelector('.twitter__icon');
twitterIcon.insertAdjacentElement("afterbegin", use);
This produced a Webpack error
ERROR in ./src/components/sidebar/sidebar.js
Module not found: Error: Can't resolve 'svg-sprite-loader' in '/Users/dev/Developer/projects/dcbio'
# ./src/components/sidebar/sidebar.js 10:0-28
# ./src/index.js
Commenting out ../sprite.svg file took care of the said error. Yet, the browser now produces a 404 error that's driving me crazy.
Browser Error
Elements Rendered
HLTM Elements
Recommendations is appreciated.
Thanks
2nd UPDATE
After reading through webpack's doc and its git, I made changes to my webpack.config to this:
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SpriteLoaderPlugin = require('svg-sprite-loader/plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
publicPath: '',
},
mode: 'development',
devServer: {
contentBase: path.resolve(__dirname, './dist'),
index: 'index.html',
port: 9000
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
]
},
{
test: /\.scss$/i,
use: [
'style-loader', 'css-loader', 'sass-loader'
],
},
{
test: /\.(png|jpg|gif)$/,
use: [ 'file-loader'],
},
{
test: /\.svg$/,
use: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
publicPath: '/src'
}
},
'svg-inline-loader',
'svgo-loader'
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [ '#babel/env' ],
plugins: [ 'transform-class-properties' ]
}
}
},
{
test: /\.hbs$/,
use: [
'handlebars-loader'
]
}
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: 'src/index.hbs',
title: 'Hello world',
description: 'Some description'
}),
new SpriteLoaderPlugin({
plainSprite: true,
spriteAttrs: {
id: 'icon-twitter'
}
})
],
};
I still get a browser error - see pic
Browser Error
DOM returns this:
enter image description here
FINAL Update and Answer:
After extensive research and edits, I realized within my base.js file the property inner.HTML element contributed to the <svg> icon not loading.
In addition, I modified the following files mentioned on my original post to this:
sidebar.js
...
import symbol from '../../images/icons_sprite.svg';
...
html = `<svg class="icon-fb">
<use href="#icons_sprite_icon-facebook"></use>
</svg>`;
const iconBox = document.querySelector('.Icon-box');
iconBox.insertAdjacentHTML("afterbegin", html);
Notice I'm only using href and NOT xlink:href. According to MDN documentation xlink:href is in the deprecation bucket, so I'm choosing to keep with their recommended approach - Just use href.
webpack.dev.config
...
{
test: /\.svg$/i,
include: /\.*_sprite\.svg/,
use:[
{
loader: 'svg-sprite-loader',
options: {
publicPath: './src',
}
},
{
loader: 'svgo-loader',
options: {
plugins: [
{ cleanupIDs: false },
]
}
},
..
Problem solved...
Here are the links that contributed to a resolution. I hope this solution provides help to someone in the future.
MDN
https://developer.mozilla.org/en-US/docs/Web/SVG
Webpack-svg
https://github.com/JetBrains/svg-sprite-loader
https://github.com/svg/svgo
https://github.com/vp-online-courses/webpack-tutorial/compare/svg-sprites-begin...svg-sprites-end
The only thing I have in my entry JS file is:
import $ from 'jquery';
The jQuery JS file has the size of 29.5kb from jsdelivr.
My entry, that only includes jQuery, and nothing else, has the size of 86kb.
webpack.config.js
const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
entry: './src/js/scripts.js',
output: {
publicPath: "./dist/",
path: path.join(__dirname, "dist/js/"),
filename: "bundle.js"
},
watch: true,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: [
['env', { loose:true, modules:false }],
'stage-2'
],
plugins: [
['transform-react-jsx', { pragma:'h' }]
]
}
},
{
test: /\.pug$/,
use: [
"file-loader?name=[name].html&outputPath=../dist",
"extract-loader",
"html-loader",
"pug-html-loader"
]
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: ['css-loader?url=false', 'sass-loader']
})
},
]
},
resolve: {
alias: {
"TweenMax": path.resolve('node_modules', 'gsap/src/uncompressed/TweenMax.js'),
"TimelineMax": path.resolve('node_modules', 'gsap/src/uncompressed/TimelineMax.js'),
"animation.gsap": path.resolve('node_modules', 'scrollmagic/scrollmagic/uncompressed/plugins/animation.gsap.js'),
}
},
plugins: [
new ExtractTextPlugin('../css/main.css'),
new UglifyJsPlugin({
test: /\.js($|\?)/i
})
],
stats: {
warnings: false
}
};
I should also mention, that going into the output bundle.js it still has the jQuery comments.
jQuery JavaScript Library v3.3.1
https://jquery.com/ ...
Even though I'm calling webpack with the -p argument and have the UglifyJS plugin, but the rest of the file is minified and mangled. Any ideas?
Thanks!
Try to copy and paste minified jquery from your link. It's has size of 86.9 kb.
This link also show that jquery v3 minified file size is also around 80kb.
So you already have correct setup. Maybe your 29.5kb file size is minified+gzipped file.
The 29.5kb file size is definitely the minified+gzipped version as per the link Niyoko posted.
I would also recommend checking out Fuse-Box It brought down our project size from over 1mb to under 200kb (Vendor and App bundles combined). Very easy to get going as well and it is TypeScript first :) It takes the best features from a number of the more popular bundlers and brings them together and builds on those features.
I created a multi part library similar to the example from webpack/webpack/.../multi-part-library. In my apps I want to be able to import parts of my library like this:
ìmport Button from 'myLib/atoms/button';
// or
import { Button } from 'myLib/atoms';
My webpack configuration for the apps looks like this and I get an error (Cannot resolve module 'myLib/atoms' or Cannot resolve module 'myLib/atoms/button'):
module.exports = {
'entry': {
'app': './client.js',
},
'output': {
'filename': 'bundle.js',
},
'externals': {
'react': true,
'react-dom': true,
'myLib': true,
},
'module': {
'loaders': [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel',
},
]
},
};
The webpack configuration for the library looks like this:
const files = glob.sync('**/*.js', {
cwd: path.join(__dirname, 'atomic-design'),
absolute: true,
});
let entries = {};
files.forEach((file) => {
const name = path.basename(path.dirname(file));
const newName = `atoms/${name}`;
entries[newName] = file;
});
module.exports = {
'entry': entries,
'output': {
'path': path.join(__dirname, 'lib'),
'filename': 'myLib.[name].js',
'library': ['myLib', '[name]'],
'libraryTarget': 'umd',
'umdNamedDefine': 'myLib',
},
'externals': {
'react': true,
'react-dom': true,
},
'module': {
'loaders': [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel'
},
]
}
};
The files are structured like this:
- app
- client.js
- webpack.config.js
- myLib
- webpack.config.js
- atomic-design
- button
- index.js
- text-field
- index.js
So far I could only find tutorials for creating libraries with webpack, where they only use small examples of libraries.
Thanks for your help in advance!
Best regards,
JBrieske
I believe you need to add the paths (and adjust the from 'path' accordingly) for the modules you're trying to import to resolve.modulesDirectories.
Relevant documentation: https://webpack.github.io/docs/configuration.html#resolve-modulesdirectories.
Bear in mind that this will change in Webpack2, which is feature complete and is just short of documentation for a release.