PWA - Why has my caching strategy such a negative impact on the performance? - performance

So I am currently building my first pwa and I don't understand why the way I implemented caching, has such bad influence on the performance (LightHouse Performance Score drops from ~94 to 76 with a huge Increase (+4 seconds) in the LCP Score and I honestly don't get what I am doing wrong here.
The app is a blogging app and later on, I want to make whole articles available for offline use. For now, I did not want to do anything fancy, I just wanted to make the landingpage (which does not contain any mongo db queries, but many images and css) available for offline use.
So the Idea is the following:
Interrupt the fetch request
Check if the data is available in the cache
if yes: serve from the cache
if no: proceed with the fetch request
Implementation:
I have the following setup:
Node JS - blogging app (server-side rendered)
hooked up with a mongo db database
in combo with ejs templates
by entering: localhost:8000
the server responds with the ejs file that corresponds to landingpage with partials, that include the header and the footer.
app.get("/", resetVerifiedVariable, (req, res) => {
res.render("home.ejs");
});
This is the corresponding header , in which I make sure that the service worker is registered
<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" />
<link rel="icon" type="image/x-icon" href="/icons/favicon.ico" />
<link rel="preload" href="/img/upchart-mobile.webp" as="image" />
<link rel="preload" href="css/main.css" as="style" />
<link type="text/css" rel="stylesheet" href="css/main.css" />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#1045be" />
<!-- ios support for pwa -->
<link rel="apple-touch-icon" href="/icons/icon-96x96.png" />
<meta name="apple-mobile-web-status-bar" content="#1045be" />
<!-- REGISTER SERVICE WORKER -->
<script src="/scripts/initiateServiceWorker.js"></script>
<!-- REGISTER SERVICE WORKER -->
<!-- typeofOperator does not throw an error so we can check for the existence of an article-->
<% if (typeof article !== 'undefined') { %>
<title><%=article.title%>| title </title>
<!-- Meta Tags for Social Media sharing the articles -->
<meta
property="og:url"
content="https://example.com/blog/<%=article.slug%>"
/>
<meta property="og:type" content="article" />
<meta property="og:title" content="<%=article.title%>" />
<meta property="og:description" content="<%=article.metaDescription%>" />
<meta property="og:image" content="./img/logo-optimized.webp" />
<%} else { %>
<title>Title| Title </title>
<meta
name="description"
content="Description"
/>
<%}%>
</head>
Here is the corresponding Service Worker File that was hooked-up in the header:
// ###################################
// REGISTER SERVICE WORKER - PWA
// ###################################
// Check if the browser supports the serviceWorker
if ("serviceWorker" in navigator) {
//console.log(navigator.serviceWorker);
navigator.serviceWorker
.register("../serviceWorker.js")
.then((registrationObject) =>
console.log("ServiceWorker registered", registrationObject)
)
.catch((error) => console.log("ServiceWorker NOT registered", error));
}
With that out of the way, I have implemented the Cache
I want to cahce the following items:
-The html/ejs template for the landingpage
-The image from the Hero section
-All of the css
-and my main script
const staticCacheName = "staticServiceWorker-v1";
const staticAssets = ["/", "/img/upchart-mobile.webp", "/scripts/index.js", "css/main.css];
// ############################
// INSTALL
// ############################
self.addEventListener("install", (event) => {
const preCache = async () => {
const cache = await caches.open(staticCacheName);
// addAll returns a promise which must be passed on to waitUntil
return cache.addAll(staticAssets);
};
event.waitUntil(preCache());
});
//console.log("ServiceWorker installed");
//
// ############################
// ACTIVATE
// ############################
self.addEventListener("activate", (event) => {
//console.log("ServiceWorker activated");
});
// ############################
// FETCH
// ############################
self.addEventListener("fetch", (event) => {
console.log(event);
const checkCacheMatch = async () => {
const result = await caches.match(event.request);
return result || fetch(event.request);
};
event.respondWith(checkCacheMatch());
});
In the Fetch-Block I try to interrupt the fetch and serve the files from the cache instead
I assume, that this block right here is causing the performance decrease, but I dont know how to make this more performant.
So the main goal was to make the page much more performant because I make use of the cache and would there would be no need for a server request. What happens however is the exact opposite and I honestly can not figure out why that is the case
Any help would much appreciated
BR

Related

How to use Laravel Mix and WorkBox?

I'm trying to build a PWA for my app; and have spent almost 48 hours trying to figure out how to make use of Workbox with Laravel Mix. What's ironical is that Google says Workbox is meant to make things easy!
Buh!
Okay, so far I've figured out that -
I will need to use the InjectManifest Plugin because I want to integrate Push notifications service in my Service Worker
I have no clue how to specifiy the paths for swSrc and swDest.
What code should go into my webpack.mix.js and whether I should have a temporary service-worker inside my resources/js folder to create a new service worker inside public/ folder.
Can someone help?
PS: I've read almost every blog and help article; but none talks about reliably using Workbox with Laravel mix. Would really appreciate some help here.
I have done a lot of research into this recently and whilst this may not be a full answer to your question, it should give you, or anyone else visiting this page, enough guidance to get started...
I will add to this answer as I learn and research more.
For the purposes of this answer, I will assume your service worker is called service-worker.js, however, you can obviously call it whatever you like.
Step 1 - Laravel Mix
Assuming you are using Dynamic Importing in your project (if you aren't, you should be), you will need to downgrade Laravel Mix to version 3. There is an acknowledged bug in Laravel Mix 4 that prevents CSS from bundling correctly and this will not be fixed until Webpack 5 is released.
In addition, the steps outlined in this answer are specifically configured for Laravel Mix 3.
Step 2 - Import or ImportScripts
The second issue to solve is whether to utilise the workbox-webpack-plugin for injecting the workbox global using importScripts or whether you should disable this (using importWorkboxFrom: 'disabled') and just individually import the specific modules you need...
The documentation states:
When using a JavaScript bundler, you don't need (and actually shouldn't use) the workbox global or the workbox-sw module, as you can import the individual package files directly.
This implies that we should be using import instead of injecting the importScripts.
However, there are various issues here:
We do not want service-worker.js to be included in the build manifest as this will be injected into the precache manifest
We do not want service-worker.js to be versioned in production i.e. the name should always be service-worker.js, not service-worker.123abc.js.
InjectManifest will fail to inject the manifest because the service-worker.js file will not exist at the time that it runs.
Therefore, in order to utilise import instead of importScripts, we must have two separate webpack (mix) configurations (see conclusion for guidance on how to do this). I am not 100% certain this is correct, but I will update my answer once I have received an answer to either of the following (please support them to increase chance of receiving an answer):
How to use Workbox with a Bundler (Webpack etc.)
https://github.com/GoogleChrome/workbox/issues/2207
Step 3 - File Structure
Assuming you are using InjectManifest and not GenerateSW, you will need to write your own service worker which will have the JS manifest injected into it by the webpack plugin on each build. This, quite simply, means you need to create a file in your source directory that will be used as the service worker.
Mine is located at src/js/service-worker.js (this will be different if you are building in a full Laravel project, I am simply using Laravel Mix in a standalone app)
Step 4 - Registering the Service Worker
There are various ways to do this; some like to inject inline JS into the HTML template, but others, myself included, simply register the service worker at the top of their app.js. Either way, the code should look something along the lines of:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
Step 5 - Writing your Service Worker; workbox Global, or Module Importing
As mentioned in the previous quote from the documentation, it is encouraged to import the specifically required modules into your service worker, instead of utilising the workbox global or workbox-sw module.
For more information on how to use the individual modules, and how to actually write your service worker, see the following documentation:
https://developers.google.com/web/tools/workbox/guides/using-bundlers
Conclusion
Based on all of my research (which is still ongoing), I have taken the following approach outlined below.
Before reading, please bear in mind that this is configured for a standalone static PWA (i.e. not a full Laravel project).
/src/service-worker.js (the service worker)
When using a bundler such as webpack, it is advised to utlilise import to ensure you include only the necessary workbox modules. This is my service worker skeleton:
import config from '~/config'; // This is where I store project based configurations
import { setCacheNameDetails } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
import { registerNavigationRoute } from 'workbox-routing';
// Set the cache details
setCacheNameDetails({
prefix: config.app.name.replace(/\s+/g, '-').toLowerCase(),
suffix: config.app.version,
precache: 'precache',
runtime: 'runtime',
googleAnalytics: 'ga'
});
// Load the assets to be precached
precacheAndRoute(self.__precacheManifest);
// Ensure all requests are routed to index.html (SPA)
registerNavigationRoute('/index.html');
/package.json
Splitting the Mix configuration
"scripts": {
"development": "npm run dev-service-worker && npm run dev-core",
"dev": "npm run development",
"dev-service-worker": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=service-worker.mix",
"dev-core": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=core.mix",
"watch": "npm run dev-core -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"production": "npm run prod-service-worker && npm run prod-core",
"prod": "npm run production",
"prod-service-worker": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=service-worker.mix",
"prod-core": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js --env.mixfile=core.mix"
}
Command Explanation
All standard commands will work in the same way as usual (i.e. npm run dev etc.). See known issue about npm run watch
npm run <environment>-service-worker will build just the service worker in the specified environment
npm run <environment>-core will build just the core application in the specified environment
Known Issues
If you are using an html template that utilises the webpack manifest then you may have issues with npm run watch. I have been unable to get this to work correctly as of yet
Downgrading to Laravel Mix 3
"devDependencies": {
"laravel-mix": "^3.0.0"
}
This can also be achieved by running npm install laravel-mix#3.0.0
/static/index.ejs
This HTML template is used to generate the single page application index.html. This template is dependant on the webpack manifest being injected.
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" class="no-js">
<head>
<!-- General meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="<%= config.meta.description %>">
<meta name="rating" content="General">
<meta name="author" content="Sine Macula">
<meta name="robots" content="index, follow">
<meta name="format-detection" content="telephone=no">
<!-- Preconnect and prefetch urls -->
<link rel="preconnect" href="<%= config.api.url %>" crossorigin>
<link rel="dns-prefetch" href="<%= config.api.url %>">
<!-- Theme Colour -->
<meta name="theme-color" content="<%= config.meta.theme %>">
<!-- General link tags -->
<link rel="canonical" href="<%= config.app.url %>">
<!-- Manifest JSON -->
<link rel="manifest" href="<%= StaticAsset('/manifest.json') %>" crossorigin>
<!-- ----------------------------------------------------------------------
---- Icon Tags
---- ----------------------------------------------------------------------
----
---- The following will set up the favicons and the apple touch icons to be
---- used when adding the app to the homescreen of an iPhone, and to
---- display in the head of the browser.
----
---->
<!--[if IE]>
<link rel="shortcut icon" href="<%= StaticAsset('/favicon.ico') %>">
<![endif]-->
<link rel="apple-touch-icon" sizes="72x72" href="<%= StaticAsset('/apple-touch-icon-72x72.png') %>">
<link rel="apple-touch-icon" sizes="120x120" href="<%= StaticAsset('/apple-touch-icon-120x120.png') %>">
<link rel="apple-touch-icon" sizes="180x180" href="<%= StaticAsset('/apple-touch-icon-180x180.png') %>">
<link rel="icon" type="image/png" sizes="16x16" href="<%= StaticAsset('/favicon-16x16.png') %>">
<link rel="icon" type="image/png" sizes="32x32" href="<%= StaticAsset('/favicon-32x32.png') %>">
<link rel="icon" type="image/png" sizes="192x192" href="<%= StaticAsset('/android-chrome-192x192.png') %>">
<link rel="icon" type="image/png" sizes="194x194" href="<%= StaticAsset('/favicon-194x194.png') %>">
<link rel="mask-icon" href="<%= StaticAsset('/safari-pinned-tab.svg') %>" color="<%= config.meta.theme %>">
<meta name="msapplication-TileImage" content="<%= StaticAsset('/mstile-144x144.png') %>">
<meta name="msapplication-TileColor" content="<%= config.meta.theme %>">
<!-- ----------------------------------------------------------------------
---- Launch Images
---- ----------------------------------------------------------------------
----
---- Define the launch 'splash' screen images to be used on iOS.
----
---->
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-640x1136.png') %>" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-750x1294.png') %>" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1242x2148.png') %>" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1125x2436.png') %>" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1536x2048.png') %>" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-1668x2224.png') %>" media="(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="<%= StaticAsset('/assets/images/misc/splash-2048x2732.png') %>" media="(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<!-- ----------------------------------------------------------------------
---- Application Tags
---- ----------------------------------------------------------------------
----
---- Define the application specific tags.
----
---->
<meta name="application-name" content="<%= config.app.name %>">
<meta name="apple-mobile-web-app-title" content="<%= config.app.name %>">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="<%= config.app.status_bar %>">
<meta name="mobile-web-app-capable" content="yes">
<meta name="full-screen" content="yes">
<meta name="browsermode" content="application">
<!-- ----------------------------------------------------------------------
---- Social Media and Open Graph Tags
---- ----------------------------------------------------------------------
----
---- The following will create objects for social media sites to read when
---- scraping the site.
----
---->
<!-- Open Graph -->
<meta property="og:site_name" content="<%= config.app.name %>">
<meta property="og:url" content="<%= config.app.url %>">
<meta property="og:type" content="website">
<meta property="og:title" content="<%= config.meta.title %>">
<meta property="og:description" content="<%= config.meta.description %>">
<meta property="og:image" content="<%= StaticAsset('/assets/images/brand/social-1200x630.jpg') %>">
<!-- Twitter -->
<meta name="twitter:card" content="app">
<meta name="twitter:site" content="<%= config.app.name %>">
<meta name="twitter:title" content="<%= config.meta.title %>">
<meta name="twitter:description" content="<%= config.meta.description %>">
<meta name="twitter:image" content="<%= StaticAsset('/assets/images/brand/social-440x220.jpg') %>">
<!-- ----------------------------------------------------------------------
---- JSON Linked Data
---- ----------------------------------------------------------------------
----
---- This will link the website to its associated social media page. This
---- adds to the credibility of the website as it allows search engines to
---- determine the following of the company via social media
----
---->
<script type="application/ld+json">
{
"#context": "http://schema.org",
"#type": "Organization",
"name": "<%= config.company.name %>",
"url": "<%= config.app.url %>",
"sameAs": [<%= '"' + Object.values(config.company.social).map(x => x.url).join('","') + '"' %>]
}
</script>
<!-- Define the page title -->
<title><%= config.meta.title %></title>
<!-- Generate the prefetch/preload links -->
<% webpack.chunks.slice().reverse().forEach(chunk => { %>
<% chunk.files.forEach(file => { %>
<% if (file.match(/\.(js|css)$/)) { %>
<link rel="<%= chunk.initial ? 'preload' : 'prefetch' %>" href="<%= StaticAsset(file) %>" as="<%= file.match(/\.css$/) ? 'style' : 'script' %>">
<% } %>
<% }) %>
<% }) %>
<!-- Include the core styles -->
<% webpack.chunks.forEach(chunk => { %>
<% chunk.files.forEach(file => { %>
<% if (file.match(/\.(css)$/) && chunk.initial) { %>
<link rel="stylesheet" href="<%= StaticAsset(file) %>">
<% } %>
<% }) %>
<% }) %>
</head>
<body ontouchstart="">
<!-- No javascript error -->
<noscript>JavaScript turned off...</noscript>
<!-- The Vue JS app element -->
<div id="app"></div>
<!-- Include the core scripts -->
<% webpack.chunks.slice().reverse().forEach(chunk => { %>
<% chunk.files.forEach(file => { %>
<% if (file.match(/\.(js)$/) && chunk.initial) { %>
<script type="text/javascript" src="<%= StaticAsset(file) %>"></script>
<% } %>
<% }) %>
<% }) %>
</body>
</html>
/service-worker.mix.js (building the service worker)
This mix configuration will build your Service Worker (service-worker.js), and place it into the root of /dist.
Note: I like to clean my dist folder each time I build my project, and as this functionality must be run at this stage of the build process, I have included it in the below configuration.
const mix = require('laravel-mix');
const path = require('path');
// Set the public path
mix.setPublicPath('dist/');
// Define all the javascript files to be compiled
mix.js('src/js/service-worker.js', 'dist');
// Load any plugins required to compile the files
const Dotenv = require('dotenv-webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// Define the required plugins for webpack
const plugins = [
// Grant access to the environment variables
new Dotenv,
// Ensure the dist folder is cleaned for each build
new CleanWebpackPlugin
];
// Extend the default Laravel Mix webpack configuration
mix.webpackConfig({
plugins,
resolve: {
alias: {
'~': path.resolve('')
}
}
});
// Disable mix-manifest.json (remove this for Laravel projects)
Mix.manifest.refresh = () => void 0;
/core.mix.js (building the application)
This mix configuration will build your main application and place it in /dist/js.
There are various key parts of this mix configuration, each of which has been clearly outlined in the comments within. These are the top-level areas:
Code splitting to app.js, manifest.js, and vendor.js (and dynamic importing)
Laravel Mix versioning does not work as needed for the HTML template so laravel-mix-versionhash is utilised instead
html-webpack-plugin is utilised to generate index.html based on the index.ejs template (see above)
webpack-pwa-manifest is utilised to generate a manifest based
copy-webpack-plugin is utilised to copy the static files to the /dist directory, and to copy any necessary icons to the site root
imagemin-webpack-plugin is used to compress any static images in production
workbox-webpack-plugin is used to inject the webpack manifest into the precaching array used in the service worker. InjectManifest is used, not GenerateSW
Any necessary manifest transformations are applied once the build process is complete
There may be additions to the above but pretty much everything is described by the comments in the following code:
const config = require('./config'); // This is where I store project based configurations
const mix = require('laravel-mix');
const path = require('path');
const fs = require('fs');
// Include any laravel mix plugins
// NOTE: not needed in Laravel projects
require('laravel-mix-versionhash');
// Set the public path
mix.setPublicPath('dist/');
// Define all the SASS files to be compiled
mix.sass('src/sass/app.scss', 'dist/css');
// Define all the javascript files to be compiled
mix.js('src/js/app.js', 'dist/js');
// Split the js into bundles
mix.extract([
// Define the libraries to extract to `vendor`
// e.g. 'vue'
]);
// Ensure the files are versioned when running in production
// NOTE: This is not needed in Laravel projects, you simply need
// run `mix.version`
if (mix.inProduction()) {
mix.versionHash({
length: 8
});
}
// Set any necessary mix options
mix.options({
// This doesn't do anything yet, but once the new version
// of Laravel Mix is released, this 'should' extract the
// styles from the Vue components and place them in a
// css file, as opposed to placing them inline
//extractVueStyles: true,
// Ensure the urls are not processed
processCssUrls: false,
// Apply any postcss plugins
postCss: [
require('css-declaration-sorter'),
require('autoprefixer')
]
});
// Disable mix-manifest.json
// NOTE: not needed in Laravel projects
Mix.manifest.refresh = () => void 0;
// Load any plugins required to compile the files
const Dotenv = require('dotenv-webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackPwaManifest = require('webpack-pwa-manifest');
const { InjectManifest } = require('workbox-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ImageminPlugin = require('imagemin-webpack-plugin').default;
// Define the required plugins for webpack
const plugins = [
// Grant access to the environment variables
new Dotenv,
// Process and build the html template
// NOTE: not needed if using Laravel and blade
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'static', 'index.ejs'),
inject: false,
minify: !mix.inProduction() ? false : {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
useShortDoctype: true
},
templateParameters: compilation => ({
webpack: compilation.getStats().toJson(),
config,
StaticAsset: (file) => {
// This will ensure there are no double slashes (bug in Laravel Mix)
return (config.app.static_url + '/' + file).replace(/([^:]\/)\/+/g, "$1");
}
})
}),
// Generate the manifest file
new WebpackPwaManifest({
publicPath: '',
filename: 'manifest.json',
name: config.app.name,
description: config.meta.description,
theme_color: config.meta.theme,
background_color: config.meta.theme,
orientation: config.app.orientation,
display: "fullscreen",
start_url: '/',
inject: false,
fingerprints: false,
related_applications: [
{
platform: 'play',
url: config.app.stores.google.url,
id: config.app.stores.google.id
},
{
platform: 'itunes',
url: config.app.stores.apple.url,
id: config.app.stores.apple.id
}
],
// TODO: Update this once the application is live
screenshots: [
{
src: config.app.static_url + '/assets/images/misc/screenshot-1-720x1280.png',
sizes: '1280x720',
type: 'image/png'
}
],
icons: [
{
src: path.resolve(__dirname, 'static/assets/images/icons/android-chrome-512x512.png'),
sizes: [72, 96, 128, 144, 152, 192, 384, 512],
destination: path.join('assets', 'images', 'icons')
}
]
}),
// Copy any necessary directories/files
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, 'static'),
to: path.resolve(__dirname, 'dist'),
toType: 'dir',
ignore: ['*.ejs']
},
{
from: path.resolve(__dirname, 'static/assets/images/icons'),
to: path.resolve(__dirname, 'dist'),
toType: 'dir'
}
]),
// Ensure any images are optimised when copied
new ImageminPlugin({
disable: process.env.NODE_ENV !== 'production',
test: /\.(jpe?g|png|gif|svg)$/i
}),
new InjectManifest({
swSrc: path.resolve('dist/service-worker.js'),
importWorkboxFrom: 'disabled',
importsDirectory: 'js'
})
];
// Extend the default Laravel Mix webpack configuration
mix.webpackConfig({
plugins,
output: {
chunkFilename: 'js/[name].js',
}
}).then(() => {
// As the precached filename is hashed, we need to read the
// directory in order to find the filename. Assuming there
// are no other files called `precache-manifest`, we can assume
// it is the first value in the filtered array. There is no
// need to test if [0] has a value because if it doesn't
// this needs to throw an error
let filename = fs
.readdirSync(path.normalize(`${__dirname}/dist/js`))
.filter(filename => filename.startsWith('precache-manifest'))[0];
// In order to load the precache manifest file, we need to define
// self in the global as it is not available in node.
global['self'] = {};
require('./dist/js/' + filename);
let manifest = self.__precacheManifest;
// Loop through the precache manifest and apply any transformations
manifest.map(entry => {
// Remove any double slashes
entry.url = entry.url.replace(/(\/)\/+/g, "$1");
// If the filename is hashed then remove the revision
if (entry.url.match(/\.[0-9a-f]{8}\./)) {
delete entry.revision;
}
// Apply any other transformations or additions here...
});
// Filter out any entries that should not be in the manifest
manifest = manifest.filter(entry => {
return entry.url.match(/.*\.(css|js|html|json)$/)
|| entry.url.match(/^\/([^\/]+\.(png|ico|svg))$/)
|| entry.url.match(/\/images\/icons\/icon_([^\/]+\.(png))$/)
|| entry.url.match(/\/images\/misc\/splash-([^\/]+\.(png))$/);
});
// Concatenate the contents of the precache manifest and then save
// the file
const content = 'self.__precacheManifest = (self.__precacheManifest || []).concat(' + JSON.stringify(manifest) + ');';
fs.writeFileSync('./dist/js/' + filename, content, 'utf8', () => {});
});
/src/js/app.js (the main application)
This is where you register your service worker, and obviously define your application etc...
/**
* Register the service worker as soon as the page has finished loading.
*/
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
// TODO: Broadcast updates of the service worker here...
navigator.serviceWorker.register('/service-worker.js');
});
}
// Define the rest of your application here...
// e.g. window.Vue = require('vue');

SAPUI5 reuse Views and make new instance

I am new in SAP and trying to develop a SAPUI5 application, but faced some problems when reusing views/partial views. I want to have a partial view and pass custom data in a specefic format (data changes but model is same), and reuse these partial view many times in one page, every time passing different data. Somehow like Tiles, but very cuszomized.
what do you suggest me to use?
I have tryied to make new instance of a regular view but faced this error:
GET https://sapui5.hana.ondemand.com/resources/view/List.view.xml 404
(Not Found)
this is my code to make new instance:
var firstListView=sap.ui.xmlview("firstViw", "view.List");
thanks for your help
Update:
this is my index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8' />
<script src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.m, sap.ushell"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-theme="sap_belize"
data-sap-ui-resourceroots='{
"sap.ui.appName": "./"
}'>
</script>
<script src="../libs/jquery.cookie.js"></script>
<link href="css/site.css" rel="stylesheet" type="text/css" />
<link href="css/library.css" rel="stylesheet" type="text/css" />
<link href="css/theme/library.css" rel="stylesheet" type="text/css" />
<script>
sap.ui.getCore().attachInit(function () {
new sap.m.Shell({
appWidthLimited:false,
app: new sap.ui.core.ComponentContainer({
name: "sap.ui.appName"
})
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" role="application">
<div id="content"></div>
</body>
</html>
and this is the folder structered:
Please have a look at chapter “Reusing UI Parts: Fragments” of the UI5 SDK.
Here is an excerpt. Please have a look whether this helps.

DHTMLXGantt and Laravel - Data doesn't display in extended templates

Background
Following this tutorial from Jan 2016, ( https://dhtmlx.com/blog/using-dhtmlxgantt-with-laravel/ ) I began experimenting with DHTMLXGantt (v4.1) and Laravel (v5.4) and ran into some troubles getting the sample data from a mysql/mariadb database to display in the gantt chart. The initial troubles had to do with the DHTMLX connector not staying current with some Laravel changes. For the sake of others who may read this and are struggling with the same issues, the two basic problems I already solved were:
(1) I was referred to this [updated] connector which was compatible with Laravel's recent versions ( https://github.com/mperednya/connector-php/tree/modern ). And,
(2) I specified a date range that matched the dates of the sample data (from 2013), such as...
gantt.config.start_date = new Date(2013, 04, 01);
gantt.config.end_date = new Date(2013, 04, 30);
At this point I was able to successfully use Laravel as a backend server for the DHTMLXGantt chart (including read-write with the sample data).
Problem
The problem I am having now is trying to move from the simplistic sample, to something slightly more complex. Specifically, when I employ Laravel's extended templating I get the gantt chart painted on screen, but no project/task data displays in the chart.
To explain this a little more specifically, a simple Laravel view with the whole page contained within it works as expected. For example, this blade file (gantt.blade.php) works.
<!DOCTYPE html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<script src="codebase/dhtmlxgantt.js"></script>
<link rel="stylesheet" href="codebase/dhtmlxgantt.css">
<link rel="stylesheet" href="codebase/skins/dhtmlxgantt_skyblue.css" type="text/css" media="screen" title="no title" charset="utf-8">
</head>
<body>
<div class="container">
<h1>Placeholder Header</h1>
<div id="gantt_here" style='width:100%; height:500px;'></div>
<script type="text/javascript">
gantt.config.xml_date = "%Y-%m-%d %H:%i:%s";
gantt.config.step = 1;
gantt.config.scale_unit= "week";
gantt.config.autosize = "xy";
gantt.config.fit_tasks = true;
gantt.config.columns = [
{name:"text", label:"Task name", width:"*", tree:true },
{name:"start_date", label:"Start time", align: "center" },
{name:"duration", label:"Duration", align: "center" },
{name:"add", label:"", width:44 }
];
gantt.init("gantt_here");
gantt.load("./gantt_data", "xml");
var dp = new gantt.dataProcessor("./gantt_data");
dp.init(gantt);
</script>
<h1>Placeholder Footer</h1>
</div>
</body>
But if I try using an extended app layout with the intention of building out a standard look & feel across all pages, the Gantt chart appears and is formated as I expect, but no data appears within the gantt chart. Here is top-level layout file (app.blade.php)
<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<link rel="stylesheet" href="codebase/dhtmlxgantt.css">
<link rel="stylesheet" href="codebase/skins/dhtmlxgantt_skyblue.css" type="text/css" media="screen" title="no title" charset="utf-8">
<!-- Scripts -->
<script>
window.Laravel = {!! json_encode([
'csrfToken' => csrf_token(),
]) !!};
</script>
<script src="codebase/dhtmlxgantt.js"></script>
</head>
<body>
<div id="app">
<!-- menus and other bootstrap styling removed for brevity -->
#yield('content')
</div>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
And here's the "content" view (index.blade.php)...
#extends('layouts.app')
#section('content')
<div id="gantt_here" style='width:100%; height:500px;'></div>
<script type="text/javascript">
gantt.config.xml_date = "%Y-%m-%d %H:%i:%s";
gantt.config.step = 1;
gantt.config.scale_unit= "week";
gantt.config.autosize = "xy";
gantt.config.fit_tasks = true;
gantt.config.columns = [
{name:"text", label:"Task name", width:"*", tree:true },
{name:"start_date", label:"Start time", align: "center" },
{name:"duration", label:"Duration", align: "center" },
{name:"add", label:"", width:44 }
];
gantt.init("gantt_here");
gantt.load("./gantt_data", "xml");
var dp = new gantt.dataProcessor("./gantt_data");
dp.init(gantt);
</script>
</div>
#endsection
Other things I've tried:
Using Chrome developer tools, I can see that the xml data was properly delivered to the browser in both examples. This made me think maybe it is a timing problem of some sort. So I put a couple links on the page just to test clearing the chart and reloading it. But still no change.
<a onclick="gantt.clearAll()" href="javascript:void(0);">Clear</a>
<a onclick="gantt.load('./gantt_data', 'xml')" href="javascript:void(0);">Refresh</a>
I also tried moving the div block "gantt_here" to various other places above and below Laravel's template directives. It fails in all cases, except when this div block is outside (either above or below) the "app" div tag in "app.blade.php" which, of course, defeats the purpose I am trying to achieve.
My goal is to use this chart within Laravel's extended templating capabilities. But I can't figure out what's wrong. Any ideas?
After further troubleshooting, I discovered the problem. It turns out that in app.blade.php, I was loading the app.js at the bottom of the body tag. When I moved this up to the HTML header area, the data began to display properly.

Kendo data source not working

I am new with using the Kendo data source and so far it is not working. My page is extremely simple.
<head>
<meta name="viewport" content="width=device-width" />
<title>View Quote Lists</title>
<link href="/Content/bootstrap.min.css" rel="stylesheet" />
<link href="/styles/kendo.common.min.css" rel="stylesheet" />
<link href="/styles/kendo.default.min.css" rel="stylesheet" />
<link href="/styles/kendo.dataviz.min.css" rel="stylesheet" />
<link href="/styles/kendo.dataviz.default.min.css" rel="stylesheet" />
</head>
<body>
<div>
</div>
<script src="/Scripts/jquery-2.1.3.min.js"></script>
<script src="/Scripts/kendo.all.min.js"></script>
<script src="/Scripts/bootstrap.min.js"></script>
<script>
var query = "3";
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "/api/QuoteAPI?page=1",
dataType: "json"
},
requestEnd: function() {
console.log(JSON.stringify(dataSource.data()))
}
}
});
dataSource.read();
</script>
</body>
The server data set that is returned from the service is an object that looks like:
{
{"QuoteCount":13393,"CurrentPage":1,"Quotes":[{"QuoteID":"B0339420-52C1-4762-8F41-474BE601E872","QuoteNo":"00014857","QuoteDate":"2015.03.09","netgainID":"5BDF1655-CB35-4326-80E3-6DCA0CC00C8B","Qty":1.0,"SKU":"CC1512570","Product":"WELDMNT,ANTLER FRAME ","Price":186.66,"Customer":"Grady Health Systems","repID":"{CA7A9606-8EAB-447E-934E-C52B4D8E06C6}","Rep":"Kirkland, Travis"},{"QuoteID":"B0339420-52C1-4762-8F41-474BE601E872","QuoteNo":"00014857","QuoteDate":"2015.03.09","netgainID":"8DBF5603-DAED-4DF8-89FD-1A172CA3589E","Qty":4.0,"SKU":"CC0900265","Product":"FRONT FLOOR PLATE (PR) 175 KIT","Price":28.95,"Customer":"Grady Health Systems","repID":"{CA7A9606-8EAB-447E-934E-C52B4D8E06C6}","Rep":"Kirkland, Travis"}......]
When I load the page is calls the web service and returns the data which I can see using Google tools. However the line dataSource.read() gives an error
Uncaught TypeError: undefined is not a function kendo.all.min.js:11
The requestEnd function is never called so I can't see the data even though it has been called.
Can someone tell me why this is not working. I have tried every variation of the docs that seem appropriate but this refuses to work.
Thanks,
Terry
Can you move requestEnd out side of transport block and try? If it doesn't help create a Kendo Dojo to replicate the problem so that it's easier to fix it there.
Here is a dojo with similar code that is working, except that I am using jsonp.

Ionic App not able to submit get Request

I have been struggling to submit a simple get request to google places api
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=-33.8670522,151.1957362&radius=500&types=food&name=cruise&key=xxxx
I keep on getting an error:
XMLHttpRequest cannot load https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=-33.8…ius=500&types=food&name=cruise&key=xxxx. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '8100' is therefore not allowed access.
I looked this up online and it has something to do with CORS but I havent been able to figure anything out. If someone could point me in the right direction that would be great.
thanks
I was able to call that url restfully using ionic.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width" />
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<link href="components/lib/ionic/css/ionic.css" rel="stylesheet" />
<script src="components/lib/ionic/js/ionic.bundle.js"></script>
<script src="components/lib/ionic/js/angular/angular-resource.js"></script>
<script type="text/javascript">
angular.module('myApp',
[
'ionic',
'ngResource'
])
.factory('GetData', function ($resource)
{
return {
getg: function ()
{
return $resource('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=-33.8670522,151.1957362&radius=500&types=food&name=cruise&key=xxxx', null).get();
}
}
})
.controller('SomeController', function (GetData)
{
GetData.getg().$promise.then(
function (answer)
{
console.log(answer);
});
});
</script>
</head>
<body ng-app="myApp" ng-controller="SomeController"></body>
</html>
What worked for me was to install this addon:
cordova-plugin-whitelist
Console:
cordova plugin add cordova-plugin-whitelist

Resources