How to inject css styles from file to WebComponent - javascript

I am wondering is it possible to inject .css styles imported from file to WebComponent (If yes then please tell me how can I achieve this). I used Webpack style-loader to load .css styles inside .js a file but then I do not know how (or is it possible) to inject those styles into WebComponent the template.
I know that I can export styles from .js file declared in template string but it is not a solution which I am looking for.
I created a repository with simple example which you can find here: inject-css-from-file. I also include those files here:
index.html :
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<kk-app></kk-app>
</body>
</html>
index.js :
import style from "./index.css";
const template = `
<style>${style}</style>
<div>
<p>Example component</p>
</div>
`;
export class App extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = template;
}
}
customElements.define('kk-app', App);
index.css :
p {
color: red;
}
webpack.config.js :
const HTMLWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'production',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
extensions: ['.js'],
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
},
plugins: [
new HTMLWebpackPlugin({
template: path.resolve(__dirname, 'index.html'),
filename: 'index.html',
}),
]
};

So the solution for that was to remove style-loader from webpack.config.js. Rule for .css file should look like that:
rules: [
{
test: /\.css$/,
use: ['css-loader'],
},
],
I do not know why but style-loader changed this .css styles into an object.
P.S. If you are using MiniCssExtractPlugin.loader it will cause same problem

Related

Webpack 5: How to inject CSS into head tag directly

I want to place CSS in the head tag of my HTML template directly instead of loading CSS into JS. But I can't find any reliable examples how to do this.
/* webpack.config.js */
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
mode: 'development',
entry: {
main: path.resolve(__dirname, 'src/script.js'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
],
},
module: {
rules: [
{test: /\.css$/, use: ['style-loader', 'css-loader']},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'My optimized file',
filename: 'index.html',
template: path.resolve(__dirname, 'src/temp.html'),
templateParameters: {
'style': '[name].[contenthash].css' // doesn't bind with CopyPlugin
},
}),
new CopyPlugin({
patterns: [
{ from: path.resolve(__dirname, 'src/style.css'), to: '[name].[contenthash].css' },
],
}),
],
};
And my HTML template src/temp.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="<%= style %>">
</head>
<body>
<h1>Hello there!</h1>
</body>
</html>
Now as the result in my compiled HTML file dist/index.html I get this:
<head>
...
<link rel="stylesheet" href="[name].[contenthash].css">
</head>
After webpack build href attribute has just a wapback parameter [name].[contenthash].css instead of compiled CSS filename style.347572c74109b5f9ef4e.css.
And my folder structure:
dist
├─ index.html
├─ main.3d522b68c880128437a8.js
└─ style.347572c74109b5f9ef4e.css
src
├─ script.js
├─ style.css
└─ temp.html
webpack.config.js
package.json
For now I found temporary solution to this problem. I've just tried mini-css-extract-plugin and looked at example from html-webpack-plugin.
/* webpack.config.js */
...
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'template.html'
}),
new MiniCssExtractPlugin({ filename: 'style.css' })
]
But to make it work you must require CSS in JS file:
/* script.js */
require('./style.css');
...
But I'm still looking for how to do it without hardcoding CSS in JS using only webpack.config.js file since style.css has nothing to do with script.js in my case.

How to use function declared in js file that built by webpack in html?

So, what I'm trying to do is, generate a html file called index.html based on template.html, that have styling based on style.css and a function handleClick that declared in index.js. The code below works for the styling, but the handleClick is not working. Is this possbile?
This is my webpack.config.js
const path = require('path')
const HTMLWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: {
'page': './src/index.js'
},
output: {
path: path.join(__dirname, '/dist'),
filename: "[name].js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
'babel-loader'
]
},
{
test: /\.css$/,
use: [
"style-loader",
"css-loader"
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
filename: 'index.html',
template: './src/template.html'
})
]
}
this is my index.js
require('./style.css');
function handleClick(){
alert("called from index")
}
this is my template.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Page's Title</title>
</head>
<body>
<div>This background should be blue</div>
<button onclick="handleClick()"> click me </button>
</body>
</html>
and this is my style.css
div {
background-color:blue;
}
The better way is to add addEventListener to that element using your js file. You can do it like this:
<button id="buttonWithFunction"> click me </button>
<script>
// pure js
document.getElementById('buttonWithFunction').addEventListener('click', handleClick);
//with jquery loaded
$('#buttonWithFunction').on('click',function() {
handleClick();
})
You may need to wrap the following in a document.onload for that to work.
Suggest to check namespace of index.js - as i expect, webpack will wrap it in a namespace.
Try to define function on a window namespace.
window.handleClick = () => console.log('Clicked');

How can I build a React app with Webpack and import an assets folder?

My main problem is that I'm trying to build a React Application but the assets folder is missing and I don't know how to import it and configure it in webpack.conf. Another problem is the relative route of index.html: I don't know if it will be affected in the application build.
Thanks in advance.
Application Tree
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>App</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="src/assets/favicon.ico"/>
</head>
<body>
<div id="app"></div>
</body>
</html>
Webpack Config:
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js'
},
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
},
module: {
rules: [
{
test: /\.tsx?$/,
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" },
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
},
plugins: [
new HTMLWebpackPlugin({
template: 'public/index.html'
}),
new MiniCssExtractPlugin("style.css")
],
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
devServer: {
historyApiFallback: true,
port: 3000
}
};
You can use webpack-copy-plugin to copy additional folders / files as part of the build process
new CopyPlugin([{
from: path.resolve(__dirname, 'src', 'assets'),
to: path.resolve(__dirname, 'build', 'assets')
}])
As #James mentioned: "You can use webpack-copy-plugin to copy additional folders / files as part of the build process"
However, there is a small error while using you example #James, it should be like this:
webpack.config.js
plugins: [
//...
new CopyPlugin({
patterns: [
{ from: path.resolve(__dirname, 'src', 'assets'), to: path.resolve(__dirname, 'build', 'assets') },
],
})
],
Hope it helps (upvote james for his help if it helps you too)

Index.html template isn't loading favicon for HtmlWebpackPlugin

I'm trying to load a favicon using the index.html that is the template for the HtmlWebpackPlugin but it's not loading.
That is how my Webpack config looks like:
'use strict'
const webpack = require('webpack')
const { join, resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'cheap-module-eval-source-map',
entry: join(__dirname, 'src', 'index'),
output: {
filename: 'bundle.js',
path: resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.s?css$/,
exclude: /node_modules/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
},
resolve: {
extensions: ['.js']
},
devServer: {
contentBase: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: join(__dirname, 'public', 'index.html')
})
]
}
And that is my index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" href="favicon.ico">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
HTMLWebpackPlugin will not parse the HTML to find your resources. You'll need to include it like this in your template:
index.html
<link rel="shortcut icon" href="${require('./favicon.ico')}">
You'll also need to include file-loader for your .ico file:
webpack.config.js
{
test: /\.ico$/,
loader: 'file-loader'
}
HtmlWebpackPlugin has an option called favicon which lets you inject an icon link into the header in development or production.
new HtmlWebpackPlugin({
title: "Title Here",
template: "./src/index.html",
favicon: "./src/favicon.ico",
inject: "body",
})
You should also have a rule to grab the icon, and also import it in your bundler file.
// # Target: favicon
{
test: /\.ico$/i,
type: "asset/resource",
// Use 'generator' to output unique name (based on webpack pattern e.g. [name], [ext], etc.)
generator: {
filename: "[name][ext][query]"
}
},
NB: I'm writing for Webpack 5
I'm not sure if Webpack 4 has the type: "asset/resource" feature, but I assume you can achieve the same thing with file-loader and its options.
{
test: /\.ico$/i,
use: {
loader: "file-loader",
options: {
name: "[name].[ext]"
}
}
},
*Not guaranteed for Webpack 4.

How to load web components in webpack project?

I have started experimenting with webpack and webcomponents. So far I managed only to run this basic web component example without webpack. If I simply load the web component in the index.html using a <script> tag than I get it's content rendered. However, when I try to run the same example using a basic webpack setup I don't see the rendered template.
The server is launched successfully and I can see the console init message. What is the missing step? I would expect to be able to concatenate them this way.
I have the following files:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="UTF-8">
<title>Web Components App</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<vs-app></vs-app>
</body>
</html>
main.ts
module.hot && module.hot.accept();
// Components
import { App } from './app';
// Styles
require('./shared/scss/main.scss');
console.log('Initialise Main');
app.ts
/**
* App
*/
export class App extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = this.template;
}
get template() {
return `
<div>This is a div</div>
`;
}
}
window.customElements.define('vs-app', App);
webpack-common.js
const webpack = require("webpack"),
path = require("path"),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
// Constants
const BUILD_DIR = path.join(__dirname, "../../build"),
PUBLIC_DIR = path.join(__dirname, "../../public"),
NODE_MODULES_DIR = path.join(__dirname, "../../node_modules");
// Shared between prod and dev
module.exports = {
entry: "./public/main.ts",
output: {
// Since webpack-dev-middleware handles the files in memory, path property is used only in production.
path: BUILD_DIR,
publicPath: "/",
filename: "bundle.js"
},
resolve: {
extensions: ["*", ".ts", ".js"]
},
module: {
loaders: [{
test: /\.ts?$/,
loader: "awesome-typescript-loader",
include: PUBLIC_DIR,
exclude: /node_modules/
},
{
test: /\.css$/,
exclude: /node_modules/,
loader: "style-loader!css-loader!autoprefixer-loader"
},
{
test: /\.scss$/,
loader: 'style-loader!css-loader!sass-loader'
},
]
},
// Base plugins
plugins: [
new CopyWebpackPlugin([
{
from: `public/shared/images`,
to: 'images'
},
]),
new HtmlWebpackPlugin({
template: 'public/index.html'
}),
// Load bundle.js after libs are loaded
new ScriptExtHtmlWebpackPlugin({
defaultAttribute: 'defer'
})
],
stats: {
colors: true,
modules: true,
reasons: true,
errorDetails: true
}
};
Edit
Eventually I got the component rendered moving the following line window.customElements.define('vs-app', App); from app.ts to main.ts. In the mean time I discovered that even that is not necessary.
// This is enough to trigger rendering
import { App } from './app';
App;
However, I still have an issue. Because of webpack hot reload, the component ends up registered twice, giving an error. Still looking for a fix.
Edit 2
After some research online I managed to find the problem: I did not have the inject: false property. This triggered webpack to load twice the same stuff. Looks like I can finally move forward with developing the app. However I would love to find out alternative ways to this setup, just to get confirmation that I'm on the right road.
new HtmlWebpackPlugin({
template: 'public/index.html',
inject: false
}),

Categories