Considering today’s fast-paced environment where time equates to money, the same principle applies to your application. If you possess a well-developed web application built with technologies like Vue, Angular, or React and are looking to expand into the mobile space, you might encounter significant hurdles related to budget, effort, required skills, and time constraints. This is where Progressive Web Apps (PWAs) offer a compelling solution.
What is PWA?
A progressive web app (PWA) is an app that’s built using web platform technologies, but that provides a user experience like that of a platform-specific app.
Similar to a traditional website, a PWA boasts the advantage of running across various platforms and devices using a single codebase. However, akin to native apps, PWAs can be installed on devices such as Android, iOS, and Windows smartphones. Furthermore, they possess the capabilities to function offline, operate in the background, and seamlessly integrate with the device’s features and other installed applications.
Numerous leading websites and companies have already embraced PWA technology, including Microsoft, Twitter (with Twitter Lite), Pinterest, Instagram (their web app), Uber, Starbucks, AliExpress, Trivago, and many others.
Opting for a PWA not only provides a swift pathway to establishing a mobile presence but also accelerates the development process and yields significant cost savings by eliminating the need for a separate mobile development budget.
Alright, let’s explore the steps involved in transforming your Vue app bundles for mobile using Webpack.
Keep in mind that while the following example focuses on a Vue application, the underlying principles and configurations are generally applicable to React and Angular projects as well, often requiring only minor adjustments or even remaining the same.
Steps 1: We need to define a Service Worker file.
A service worker acts as middleware between your PWA and the servers it interacts with. When an app requests a resource covered by the service worker’s scope, the service worker intercepts the request and acts as a network proxy, even if the user is offline. It can then decide if it should serve the resource from the cache using the Cache Storage API, serve it from the network as if there were no active service worker, or create it from a local algorithm.
Service worker file has to be in the public folder (to make the content available to dist (output) folder without bundling it.
Create a file i.r public/service-worker.js and add below code:
//Update the version whenever the below file changes.
const CACHE_NAME = 'my_cache_v1.0.2';
const urlsToCache = ['/', '/index.html', '/icon192x192.png', '/icon512x512.png', '/manifest.json',];
const ignoredHosts = ['localhost'];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
console.log('[Service Worker] Installing new version...');
self.skipWaiting(); // Activate immediately
});
// Fetch event - Serve cached assets when offline
self.addEventListener('fetch', (event) => {
const { hostname } = new URL(event.request.url);
if (ignoredHosts.indexOf(hostname) >= 0) {
return;
}
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
// Activate event - Clean up old caches
self.addEventListener('activate', (event) => {
console.log('[Service Worker] Activating new version...');
event.waitUntil(
Promise.all([
clearOldCaches(),
self.clients.claim(), // Take control of open tabs immediately
])
);
});
//delete old caches
async function clearOldCaches() {
const cacheNames = await caches.keys();
const oldCaches = cacheNames.filter((name) => name !== CACHE_NAME);
return Promise.all(oldCaches.map((name) => caches.delete(name)));
}
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'CLEAR_CACHE') {
caches.keys().then((cacheNames) => {
cacheNames.forEach((cacheName) => caches.delete(cacheName));
});
self.skipWaiting(); // Ensure new service worker activates immediately
}
});
In the service worker file we add above needed events to manage all the network request within its scope. The above code is standard and can be used as it is.
In case if you need to customised the install experience with a popup or something, you can use the “beforeinstallprompt
” event to do so but please note that, this event is not supported by some browsers like safari.
// This variable will save the event for later use.
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
// Prevents the default mini-infobar or install dialog from appearing on mobile
e.preventDefault();
// Save the event because you'll need to trigger it later.
deferredPrompt = e;
// Show your customized install prompt for your PWA
// Your own UI doesn't have to be a single element, you
// can have buttons in different locations, or wait to prompt
// as part of a critical journey.
showInAppInstallPromotion();
});
Step 2: Register the service-worker when your application starts and to register it add below code to your main.ts (main.js) file.
//register service worker only for mobile devices
if ('serviceWorker' in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.getRegistration()
.then((registration) => {
if (!registration) {
navigator.serviceWorker
.register('/service-worker.js', { updateViaCache: 'none' })
.then((registration) => {
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.onstatechange = () => {
if (
newWorker.state === 'installed' &&
navigator.serviceWorker.controller
) {
console.log(
'New version available. Reloading service worker...'
);
window.location.reload();
}
};
});
});
console.log(
'Service Worker registered successfully:',
registration
);
} else {
console.log('Service Worker already registered:', registration);
}
})
.catch((error) => {
console.error('Service Worker registration failed:', error);
});
navigator.serviceWorker.ready.then((registration) => {
registration.active.postMessage({ type: 'CLEAR_CACHE' });
});
});
}
With this, service worker installation will happen silently and the service worker APIs will be available for PWA. Once the installation done, the ‘activate’ event will be fired and triggered the event code in service-worker.js file to activate the service worker which also cleans the old cache based on cache key (if you change the cache key with next push of code).
Note: Avoid using window’s load event listener to register the service worker if you don’t want to delay the service work registration until your application load.
Step 3: We need to create a manifest file to tell the browser, how we want our web content to display as an app in the mobile devices.
The manifest file includes basic information such as the app’s name, icon, and theme color; advanced preferences, such as desired orientation and app shortcuts; and catalog metadata, such as screenshots.
Hence create a manifest.json file as in public/manifest.json and add in below code:
{
"name": "My PWA APP",
"short_name": "PWA",
"start_url":".",
"display": "standalone",
"description": "My first PWA APP",
"lang": "en",
"dir": "auto",
"theme_color": "#ffffff",
"background_color": "#ffffff",
"orientation": "any",
"icons": [
{
"src": "/icon512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icon192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
}
],
"prefer_related_applications": false,
"shortcuts": [
{
"name":"My PWA APP",
"short_name": "PWA",
"url": ".",
"description":"My PWA APP"
}
]
}
Step 4: Create web.config in the public folder (if your application is hosted by a server i.e. Azure App service or server to host your app) as in public/web.config
In the web.config we define the .json mimetype to handle it as static content which is very much required for manifest.json file along with no cache custom headers and all. You can use the below code as it is for your web.config.
<?xml version="1.0"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="sameorigin" />
<add name="Strict-Transport-Security" value="max-age=63072000; includeSubDomains; preload" />
<add name="Cache-Control" value="no-cache, no-store, must-revalidate" />
<add name="Pragma" value="no-cache" />
<add name="Expires" value="-1" />
</customHeaders>
</httpProtocol>
<staticContent>
<!-- Disable caching for index.html -->
<clientCache cacheControlMode="DisableCache" />
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>
<rewrite>
<rules>
<rule name="Handle History Mode and custom 404/500" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="index.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Step 5: Add the below link tags to your template.html or index.html (whichever you have as start html page).
<link rel="manifest" href="/manifest.json" />
<!-- Enable PWA Standalone Mode on iOS -->
<meta name="mobile-web-app-capable" content="yes" />
<!-- Customize the Status Bar Appearance -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
To customize the images or splash screen for devices specific like iphone, you can add all the images here as link items. i.e.
<link rel="apple-touch-startup-image" media="screen and (device-width: 440px) and (device-height: 956px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="splash_screens/iPhone_16_Pro_Max_landscape.png">
Step 6: Make sure all the content from public folder is included in the dist (output folder) with your javascript bundler, add copy plugin in the webpack.config.js. In my case, it is webpack and the code is:
new CopyWebpackPlugin({
patterns: [
{ from: path.resolve(__dirname, 'public'), to: path.resolve(__dirname, 'dist/') }, // copies everything from public/ to dist/
],
}),
to make above code work. you need to install the “copy-webpack-plugin” package using the npm command as “npm install copy-webpack-plugin”.
Step 7: In the final step, make sure all the images you defined in your manifest file, available in the public folder.
Next, build your application and deploy. When you browse the application in your mobile device, you will get a popup automatically to install the application.
Bonus Step: In iPhone, the install popup won’t show as it is not supported by safari browser hence to overcome this in you can show a popup message using below code to navigate user to the install button (square-arrow-up icon, which usually comes in center of down the screen).
const isIos = () => {
const userAgent = window.navigator.userAgent.toLowerCase();
return /iphone|ipad|ipod/.test(userAgent);
};
// Detect if device is in standalone mode
const isInStandaloneMode = () => 'standalone' in window.navigator && window.navigator.standalone;
// Show install message if on iOS and not in standalone mode
if (isIos() && !isInStandaloneMode()) {
//show popup here.
}
That’s the end of the article and your app on mobile.
To verify locally if the service worker is getting registered, Open the debugger tool of the browser then go to “Application” section from the top menu and click on “Service workers” from left side Application menu, it will display your registered service worker.
To know more about PWA and setup, read here: https://web.dev/learn/pwa/progressive-web-apps
That wraps up this article! I hope you found the information valuable. If you're interested in staying updated with similar content, don't forget to follow. Thanks for reading, and happy coding!
No comments:
Post a Comment