How to load web components in webpack project? - javascript

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
}),

Related

Webpack serve does not include exports on var

I'm working on a project using webpack, I'm trying to use the webpack-dev-serve package to preview my changes. However when I start the server and load the page the object created by webpack does not have the functions on it Uncaught TypeError: test.a is not a function, when I console log the object webpack creates I can see its an empty object Object { }. Using the exact same webpack config to build the package and including it on my page works fine.
Here is my webpack.config:
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
entry: "./src/index.js",
target: "web",
output: {
filename: "test.js",
library: {
name: "test",
type: "var"
}
},
module: {
rules: [{
exclude: /node_modules/
}]
},
resolve: {
symlinks: false
},
plugins: [
new HtmlWebpackPlugin({
title: "Development",
template: "./src/index.html",
scriptLoading: "blocking",
inject: "head"
})
]
};
My index.js is very simple:
export function a(){
console.log("A has been called");
}
My index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<script>
console.log(test);
test.a();
</script>
</body>
</html>
I found a fix from the problem here:
WebPack output.library.type var is undefined
After adding injectClient: false to devServer, and removing scriptloading and inject from the HtmlWebpackPlugin config, webpack serve now works as expects.

How to inject css styles from file to WebComponent

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

Phoenix 1.4 server is delivering default HTML instead of requested JS files

I am trying to render Vue.js directly in my Phoenix 1.4 application. But the following error is occurring after installing Vue-router:
Refused to execute script from 'http://localhost:4000/0.app.js'
because its MIME type ('text/html') is not executable, and strict MIME
type checking is enabled.
My Phoenix router.ex file looks as follows:
scope "/", Web do
pipe_through :browser
get "/*path", PageController, :index
end
My webpack.config.js:
const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = (env, options) => ({
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
optimization: {
minimizer: [
new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }),
new OptimizeCSSAssetsPlugin({})
]
},
entry: {
'./js/app.js': glob.sync('./vendor/**/*.js').concat(['./js/app.js'])
},
output: {
filename: 'app.js',
path: path.resolve(__dirname, '../priv/static/js')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.s(c|a)ss$/,
use: [
'css-loader',
{
loader: 'sass-loader',
// Requires sass-loader#^7.0.0
options: {
implementation: require('sass'),
fiber: require('fibers'),
indentedSyntax: true // optional
}
}
]
},
{ test: /\.(png|woff|woff2|eot|ttf|svg)$/, use: 'url-loader' }
]
},
plugins: [
new MiniCssExtractPlugin({ filename: '../css/app.css' }),
new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
new VueLoaderPlugin()
]
});
I am currently seeing an issue with (I think) the cache manifest whereby my site delivers HTML and CSS files properly but for JS files, the default HTML template is being delivered - which results in an erro.
I can see in the Chrome Dev Tools Network tab that the response from this request http://localhost:4000/0.app.js is the app.html.eex file.
<!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>Phoenix Framework</title>
</head>
<body>
<div id="vue-app"></div>
<script type="text/javascript" src="<%= Routes.static_path(#conn, "/js/app.js") %>"></script>
</body>
</html>
Any help greatly appreciated.
If you use a wildcard catch-all route to show your index page, and requests to resources are hitting that route, this means that Plug.Static is not set up correctly in your endpoint.ex.
Ours looks something like this:
plug Plug.Static,
at: "/", from: :my_app_name, gzip: true,
only: ~w(css images js favicon.ico robots.txt fonts)
put that anywhere before you do plug MyApp.Router.
Another reason could be that 0.app.js simply doesn't exist in priv/static/js. Check there to see if it isn't actually called app.js or has a hash appended to its name.

Text content did not match. Warning in React 16

I trying to build ReactJs application with server side rendering
My entry points for client and server:
client.jsx
const store = createStore(window.__INITIAL_STATE__);
hydrate(
<Provider store={store}>
<BrowserRouter>{renderRoutes(routes)}</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
server.jsx
const app = express();
if (isDev) {
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('../../webpack.config.js');
const compiler = webpack(config);
app.use(express.static('/public'));
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: 'errors-only',
})
);
}
app.get('*', (req, res) => {
const helmet = Helmet.renderStatic();
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
const context = {};
const data = {};
res.set('content-type', 'text/html');
res.send(
'<!DOCTYPE html>' +
renderToString(
<html {...htmlAttrs}>
<head>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
</head>
<body {...bodyAttrs}>
<div id="root">
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
</div>
<script
dangerouslySetInnerHTML={{
__html: `window.__INITIAL_STATE__ = ${JSON.stringify(data)}`,
}}
/>
<script src="/public/vendor.js" />
<script src="/public/app.js" />
</body>
</html>
)
);
});
And component:
home.jsx
import React, { Component } from 'react';
class Home extends Component {
render() {
return <div>home</div>;
}
}
export default Home;
When I change in my component Home and refresh browser page I get this error:
Warning: Text content did not match. Server: "home" Client: "home1"
Its ok because server render old version of my code. How to reload the code on the server so that the client and server versions are equal?
For those discovering this error because your client and server purposefully render different content (like the server rendering a dark theme which gets replaced with the user preference on load), use suppressHydrationWarning to suppress the error.
For example:
<div suppressHydrationWarning>Ignore this</div>
The problem here is that your server-side application does not reflect code changes. To do that you have to configure your express app as a webpack entry.
Briefly, you need 2 webpack configurations one for server and another for client code.
The server one will look something like this
module.exports = {
entry: {
server: './server.js',
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js'
},
target: 'node',
node: {
// Need this when working with express, otherwise the build fails
__dirname: false, // if you don't put this is, __dirname
__filename: false, // and __filename return blank or /
},
externals: [nodeExternals()], // Need this to avoid error when working with Express
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
// Loads the javacript into html template provided.
// Entry point is set below in HtmlWebPackPlugin in Plugins
test: /\.html$/,
use: [{loader: "html-loader"}]
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./index.html",
filename: "./index.html",
excludeChunks: [ 'server' ]
})
]
}
Here is a nice article explaining how to do that in details
No one said about but Cloudflare replaced double spaces with a space when you enable Auto Minify (HTML) in optimization.
And this error will happen.

Why do Vue single file components compile to such large files?

I am new to Vue and Grunt/Gulp/Webpack. I got a Vue app to work fine (Grunt: browserify -> babel -> uglify) with a setup like this:
// app.js
const LoginComponent = require('./login.js')
// login.js
const template = `<some html>`
module.exports = Vue.component('login-component', {
template: template,
// component stuff
})
Then, in order to make my components more readable, I switched to single file components (Webpack, Grunt: babel -> uglify) and go everything to work like this:
// app.js
import LoginComponent from './login.js'
// login.js
<template>
<some html>
</template>
<script>
export defalut {
// component stuff
}
</script>
The problem is that when using webpack the file size is doubled. The first setup resulted in an app.min.js of 3.3kb, and the second setup with webpack was 7.0kb.
Is this normal or did I do something wrong?
My webpack.config.js looks like this:
var path = require('path')
module.exports = {
entry: './resources/js/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'resources/js/temp')
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
]
}
}
I don't see anything wrong with your config, sometimes webpack needs to generate some runtime code that might explain the increase of your bundle size.
However you can reduce it by using the DefinePlugin to set the NODE_ENV variable to production and leverage the UglifyjsWebpackPlugin, it will result in optimized code that will most likely be smaller, so something like the following
module.exports = {
entry: './resources/js/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'resources/js/temp')
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue-loader',
},
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify('production') },
}),
new webpack.optimize.UglifyJsPlugin({ compressor: { warnings: false } }),
]
}
You can also specify the option dead_code to uglify so it trims out code that is never used, which might help in your case.
Also, you should get rid of Grunt entirely using the babel-loader and the appropriate presets.

Categories