I am using Google Lighthouse to figure out ways to speed up my page loading time.
One of the red points was that I was missing a Service Worker.
So I get one working.
Before I used the Service Worker my page was fully loaded and ready in 7 seconds.
After I added the Service Worker my page fully loads in 29 seconds!
When I check on Fiddler was is going on, it looks like the Service Worker is loading all the files before letting the page render.
I am using the SWPrecacheWebpackPlugin plugin in my webpack.config to automatically generate the service-worker.js file because my libraries name are hashed automatically to optimize the caching.
This is my webpack.config:
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ChunkManifestPlugin = require('chunk-manifest-webpack-plugin');
const WebpackChunkHash = require('webpack-chunk-hash');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
// To handle the regeneratorRuntime exception
require('babel-polyfill');
require('file-loader');
require('css-loader');
require('style-loader');
require('html-webpack-template');
/* Shared Dev & Production */
const config = {
context: path.resolve(__dirname, 'src'),
entry: {
index: [
// To handle the regeneratorRuntime exception
'babel-polyfill',
'./index.js'
],
vendor: ['react', 'react-dom', 'react-router', 'react-redux', 'history', 'react-router-dom', 'redux', 'react-router-redux', 'redux-form', 'lodash'],
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(ico|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
exclude: /\/favicon.ico$/,
use: [
{
loader: 'file-loader',
query: {
name: '[path][name][hash].[ext]',
publicPath: '/'
}
}
]
},
{
test: /\.css$/,
include: path.join(__dirname, 'src/style'),
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(ico)(\?.*)?$/,
exclude: /node_modules/,
use: {
loader: 'file-loader',
options: { name: '[name].[ext]' },
},
},
{
test: /\.xml/,
use: {
loader: 'file-loader',
options: { name: '[name].[ext]' },
},
},
],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
publicPath: '/',
},
resolve: {
extensions: ['.js'],
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
},
plugins: [
// New moment.js optimization
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({
appMountId: 'app-root',
inlineManifestWebpackName: 'webpackManifest',
template: 'templateIndex.html',
title: 'Our Site',
}),
new SWPrecacheWebpackPlugin()
],
devServer: {
historyApiFallback: true,
},
};
//if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
config.plugins = [
...config.plugins,
new BundleAnalyzerPlugin({analyzerMode: 'static'}),
];
//}
//if (process.env.NODE_ENV === 'production') {
config.output.filename = '[name].[chunkhash].js';
config.plugins = [
...config.plugins,
new webpack.HashedModuleIdsPlugin(),
new WebpackChunkHash(),
/*new ChunkManifestPlugin({
filename: 'chunk-manifest.json',
manifestVariable: 'webpackManifest',
inlineManifest: true,
}),*/
];
//}
module.exports = config;
And my templateIndex.html (I am using HtmlWebpackPlugin for the same reason, hashed files name)
The service loader is loaded at the end of the body.
If I remove it, things are fast again. But then Lighthouse complains...
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#242424" />
<title itemprop="name">My Site</title>
<base href="/">
<link rel="apple-touch-icon" sizes="57x57" href="/images/favicon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/favicon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/favicon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/images/favicon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/images/favicon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/images/favicon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/images/favicon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/images/favicon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/images/favicon-180x180.png">
<link rel="icon" type="image/png" href="/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/images/favicon-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/images/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="/images/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/images/favicon-16x16.png" sizes="16x16">
<link type="image/png" href="/images/favicon.png" rel="icon">
<link rel="icon" href="/images/favicon.ico" />
<link rel="shortcut icon" href="/images/favicon.png" />
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
// ServiceWorker is a progressive technology. Ignore unsupported browsers
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
} else {
console.log('CLIENT: service worker is not supported.');
}
</script>
</body>
</html>
I have searched on Google and I could not find anything related to the issue I am experiencing.
Is that the expected behavior? Because if that is so, then I will not use a Service Loader.
Otherwise, does anyone have a clue about how to get my page to load in parallel to the Service Worker?
You should most likely register the SW after the load event of the page. Currently you're registering the SW right away when the browser executes the script in the HTML script tag. The registration is even happening before any critical app logic since Webpack most likely inserts your actual JS files just before the closing body tag.
More on the topic over here with good details and explanations!
https://developers.google.com/web/fundamentals/primers/service-workers/registration
Related
while using the ES6 module language in nodejs it gives me an error of
" IMPORTS CANNOT BE USED OUTSIDE THE MODULE" ERROR IN CHROME BROWSER.
I am trying to build my project using Node js express mongoose morgan express-handlebars and ES6
but while I run the project it gives me an error for the same
I tried using .babelrc and webpack.config.js but not able to resolve it.
can anyone help me to achieve it?
I am putting images of my project for your reference.
Thank You
enter image description here
enter image description here
Babelrc:
{
"presets": [
["#babel/env", {
"useBuiltIns": "usage",
"corejs": "3",
"targets": {
"browsers": [
"last 5 versions",
"ie >= 8"
]
}
}]
]
}
webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: ['./index.js'],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/bundle.js'
},
devServer: {
contentBase: './dist'
},
plugins: [
new HtmlWebpackPlugin({
title: 'Custom template using Handlebars',
filename: 'index.html',
template: 'main.hbs'
})
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}
],
loaders: [
{ test: /\.hbs$/, loader: "handlebars-loader" }
]
}
};
main.js:
enter code here
import Search from './models/search';
import Movie from './models/Movie'
import User from './models/user'
import * as searchView from './views/searchView'
import * as movieView from './views/movieView'
import { elements , renderLoader, clearLoader } from './views/base'
const state = {};
const controlSearch = async () => {
// const query = searchView.getInput();
const query = 'avengers';
if (query) {
searchView.clearInput();
searchView.clearResult();
state.search = new Search(query);
state.user = new User();
searchView.clearInput();
searchView.clearResult();
renderLoader(elements.searchRes);
await state.search.getResult();
await state.user.userSignUp();
clearLoader();
console.log(state.search.result);
searchView.renderResults(state.search.result);
}
};
elements.searchForm.addEventListener('submit', e => {
e.preventDefault();
controlSearch();
});
main.hbs
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Movie Recherer</title>
<link rel="stylesheet" href="/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/css/all.min.css"/>
<link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/home.css" />
</head>
<body>
<div class="container">
<div class="row">
{{{body}}}
</div>
</div>
<script src="/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script language="javascript" src="/js/main.js"></script>
<script language="javascript" src="/js/models/Movie.js"></script>
<script language="javascript" src="/js/models/search.js"></script>
<script language="javascript" src="/js/views/base.js"></script>
<script language="javascript" src="/js/views/movieView.js"></script>
<script language="javascript" src="/js/views/searchView.js"></script>
</body>
</html>
You need to include you import inside a module:
Try this:
<script type="module"> import btn from './helper' </script>
Add type="module" in every script tag of your main.hbs file
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.
I am using Reactjs, redux, webpack and webpack server to develop a website.
The website crashes when I refresh a certain pages. To be more specific when I visit http://localhost:8080/article/Id the page works like a charm, but when I hit refresh the page turns to white with errors.
GET http://localhost:8080/article/scripts/vendor.bundle.js?cfd99d78961c5e13afca 404 (Not Found)
Refused to execute script from 'http://localhost:8080/article/scripts/vendor.bundle.js?cfd99d78961c5e13afca' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
The error displayed by Chrome is this and this.
My webpack config page looks like this :
//webpack.config.js
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
var path = require('path')
var isProd = process.env.NODE_ENV === 'production'
var cssDev = ['style-loader', 'css-loader', 'sass-loader']
var cssProd = ExtractTextPlugin.extract({
use: [{
loader: 'css-loader',
options: {
minimize: 'true'
}
},
{
loader: 'sass-loader'
}
],
fallback: 'style-loader',
publicPath: '/docs'
})
var cssConfig = isProd ? cssProd : cssDev
module.exports = {
entry: {
'app': './src/app.js',
'vendor': './src/vendor.js'
},
output: {
path: path.resolve(__dirname, 'docs'),
filename: 'scripts/[name].bundle.js'
},
module: {
rules: [{
test: /\.jsx?/,
exclude: [/node_modules/, path.resolve(__dirname, './src/lib')],
use: 'babel-loader',
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
},
{
test: /\.s?css$/,
use: cssConfig
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
'file-loader?name=images/[name].[ext]',
'image-webpack-loader'
]
},
{
test: /\.(xml|json|ico)$/i,
use: [
'file-loader?name=images/[name].[ext]',
]
}
]
},
devServer: {
contentBase: path.join(__dirname, 'docs'),
compress: true,
hot: true,
stats: 'errors-only',
open: true,
disableHostCheck: true,
historyApiFallback: true, //when refreshing
inline: true //reloads the entire browser page
},
plugins: [
new ExtractTextPlugin({
filename: 'styles/[name].bundle.css',
disable: !isProd,
allChunks: true
}),
new HtmlWebpackPlugin({
title: 'React MovieDB - Powered by The Movie Database',
minify: {
collapseWhitespace: true
},
hash: true,
template: './src/index.ejs'
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new UglifyJsPlugin()
],
watch : true
}
And I run the project with : "dev": "webpack-dev-server",
And this how my index.ejs looks like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('./icons/apple-touch-icon.png') %>">
<link rel="icon" type="image/png" sizes="32x32" href="<%= require('./icons/taleed.png') %>">
<link rel="icon" type="image/png" sizes="16x16" href="<%= require('./icons/taleed.png') %>">
<link rel="manifest" href="<%= require('./icons/manifest.json') %>">
<link rel="mask-icon" href="<%= require('./icons/safari-pinned-tab.svg') %>" color="#01d277">
<script src="//cdnjs.cloudflare.com/ajax/libs/wavesurfer.js/2.0.5/wavesurfer.min.js"></script>
<meta name="theme-color" content="#ffffff">
<title>
Archive Search Engine
</title>
</head>
<body>
<div class="smoke-screen"></div>
<div id="root">
</div>
</body>
</html>
Honestly, I can't manage to figure out the main source of the problem because the project is based on a template and I couldn't get in touch with the template' maker.
You're trying to grab the bundle from the base path, which works just fine. But when you go to a different part of the app, you are requesting to get the bundle from /articles/app.bundle.... So you need to change the index file to pull from the absolute path from the server for the bundle instead of a relative path.
You need to do something like this to your server.js (if you use node);
app.get('/*', (req, res) => {
res.send(template({
css: bundle.css,
js: bundle.js,
}));
});
for example on using mustache as the template engine.
So I'm creating a react component which will be deployed with UMD format and can be consumed from unpkg. It is built using webpack.
Webpack config creating the UMD module with external modules:
module.exports = {
plugins: [
new StyleLintPlugin(),
new CleanWebpackPlugin(['../dist']),
new ExtractTextPlugin("index.css")
],
resolve:{
extensions: ["",".scss", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
preLoaders: [
{
test: /\.tsx$/,
loader: 'tslint-loader'
}
],
loaders: [
{ test: /\.scss$/, loader: ExtractTextPlugin.extract("css-loader!postcss-loader!sass-loader")},
{ test: /\.css$/, loader: ExtractTextPlugin.extract("css-loader!postcss-loader")},
{ test: /\.tsx?$/, loader: "awesome-typescript-loader",exclude: /node_modules/,query:{declaration:false} },
{
test: /\.(png|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader',
}
]
},
devtool: 'source-map',
output: {
filename: '[name].js',
path: path.resolve(__dirname,"..", 'dist'),
library: 'productupload',
libraryTarget: 'umd',
umdNamedDefine: true,
//publicPath: '/'
}
externals: {
'react': 'commonjs react',
'react-dom':'commonjs react-dom',
'mongodb':'commonjs mongodb',
'express':'commonjs express'
}
};
This then creates the package you can see here
And I try to get the react component with the HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Upload Component</title>
<meta name="description" content="It will upload generic product data to server.">
<meta name="author" content="Alexander Morton">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script src="https://unpkg.com/#mortonprod/product-upload#1.0.1/dist/inputBuilder.min.js"></script>
</head>
<body>
<div id="root"></div>
<section>
</section>
<footer>
Product Upload component create by Alexander Morton
</footer>
<script>window.__IDS__ = ${ids}</script>
<script src= ${options.unpkgUI}></script>
<script type="text/babel">
ReactDOM.render(
<InputBuilder/>,
document.getElementById('root')
);
</script>
</body>
</html>
This should include react, react-dom and my component and then render it to the screen.
The error I get is:
inputBuilder.min.js:1 Uncaught ReferenceError: require is not defined
at Object.r.extends.Object.setPrototypeOf.__proto (inputBuilder.min.js:1)
at e (inputBuilder.min.js:1)
at Object. (inputBuilder.min.js:1)
at e (inputBuilder.min.js:1)
at i.extends.Object.setPrototypeOf.__proto (inputBuilder.min.js:1)
at inputBuilder.min.js:1
at inputBuilder.min.js:1
Require should not be defined in the browser right? Why does webpack use it then?
When I run my npm run build or npm run build-dev
It creates the index.html and manage2.bundle.js and manage2.css files in the root. I need to move those files into the static directory.
So the generated index.html below will actually work, with the correct paths:
<!doctype html>
<html lang="en">
<head>
<title>Manage2</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="The TickerTags backend manage app">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:300|Source+Sans+Pro:200,600" rel="stylesheet">
<link rel="icon" type="image/x-icon" href="static/favicon.ico">
<link href="/static/manage2.css" rel="stylesheet"></head>
<body>
<div id="manage2"></div>
<script type="text/javascript" src="/static/manage2.bundle.js"></script></body>
</html>
How is this acomplished? webpack.config below
const fs = require('fs');
const webpack = require('webpack')
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require("path");
const dist = path.resolve(__dirname, "dist");
const src = path.resolve(__dirname, "src");
const environment = process.env.NODE_ENV;
const stream = fs.createWriteStream("src/services/environment.js");
stream.once('open', function(fd) {
stream.write('const env = "'+environment+'"\n');
stream.write('export default env');
stream.end();
});
module.exports = {
context: src,
entry: [
"./index.js"
],
output: {
path: dist,
filename: "manage2.bundle.js",
publicPath: '/static/',
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallbackLoader: "style-loader",
loader: ["css-loader", "sass-loader"],
publicPath: dist
})
}
]
},
devServer: {
hot: false,
quiet: true,
publicPath: "",
contentBase: path.join(__dirname, "dist"),
compress: true,
stats: "errors-only",
open: true
},
plugins: [
new HtmlWebpackPlugin({
template: "index.html"
}),
new ExtractTextPlugin({
filename: "manage2.css",
disable: false,
allChunks: true
}),
new CopyWebpackPlugin([{ from: "static", to: "static" }])
]
};
// new webpack.DefinePlugin({ env: JSON.stringify(environment) })
My npm scripts
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --history-api-fallback",
"prod": "NODE_ENV=production webpack-dev-server -p",
"build": "NODE_ENV=production webpack -p",
"build-dev": "NODE_ENV=production webpack -d",
This config is saving the *.js and *.css to the static folder.
output: {
// the output bundle
filename: '[name].[hash].js',
// saves the files into the dist/static folder
path: path.resolve(__dirname, 'dist/static'),
// set static as src="static/main.js as relative path
publicPath: 'static/'
},
With the HtmlWebpackPlugin you can generate a html file from a template. With this config in the Webpack plugins section the index.html is saved to the dist. folder with the correct path to the *.js and *.css.
plugins: [
// is only working with npm run build
new HtmlWebpackPlugin({
title: '',
// save index.html one director back from the output path
filename: '../index.html',
template: 'index.template.ejs',
hash: false
}),
],
index.template.ejs
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
</body>
</html>
Results in
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>
</title>
<link href="static/main.css" rel="stylesheet">
</head>
<body>
<script type="text/javascript" src="static/main.js"></script>
</body>
</html>
Your output.path is incorrect. It should be path.resolve(__dirname, 'dist', 'static').
And since now your output.path points to dist/static, set publicPath back to /.