Hire the author: Obua E
Image source https://i.ytimg.com/vi/ivrc1ZKFgHI/maxresdefault.jpg
Introduction
Packages are a great way to make a bunch of code reusable and easy to share. They’re the primary way to add functionality to a Laravel application, for example generating PWA files.
PWA is a website that looks and behaves like a mobile app. PWAs are designed to take advantage of native mobile device features and help people avoid app stores.
You may have already come across many Laravel packages, both official and community-maintained.
There are different types of packages.
Some packages are standalone, meaning they can be used with any PHP framework, while others are specific to Laravel.
These packages may have routes, controllers, views, and configurations specifically designed to enhance your Laravel application.
Any of these packages may be used with Laravel by requiring them in your composer.json file
Note: This guide primarily covers the development of a package that is Laravel specific.
Throughout this article, we’ll demonstrate how to build a package for publishing essential files to a Laravel application-specific directory to make a PWA starter kit.
Glossary
- PWA. This stands for a progressive web application.
- Docs. Here Docs stands for documentation
- PHP. This stands for Hypertext Preprocessor which is a programming language.
- App. This stands for application.
- SW. Represents a service worker.
- API. An API in full is an Application Programming Interface.
- It is a set of operations of a software component (i.e., a system, a sub-system, class, or a function) provides to its clients.
- PSR-4. It describes a specification for autoloading classes from file paths in a composer.
Step-by-step procedure
Step 1: Initialize a new package using composer.
To get a new Laravel custom package off the ground, cd into the base directory (ld-laravel-pwa in my case) and execute the composer init command.
This will begin the package initialization process. Make sure to write the package name and description properly.
After that, you can press enter and accept the default for all the options, except when it asks whether you’d like to add your dependencies and dev-dependencies interactively or not, for those two prompts, write n and hit enter to answer in negative.
Step 2: Update composer.json file content with necessary sections required for the package
Once the package has been initialized, you’ll find a new vendor
 directory and composer.json
 file inside your base directory. Open the composer.json
 file but (keep in mind, this composer.json
 file is separate from your project composer.json
 file) in your code editor of choice and look for the require {}
 section.
This is where you’ll have to enlist any other package that your package depends on. Well in this one, you’ll need the php v5.6 and above, and illuminate/support
v5.4 and above Laravel package for reusable Laravel components. So, update the require {}
section as follows:
"require": { | |
"php": ">=5.6", | |
"illuminate/support": ">=5.4" | |
} |
Also, add the autoload and extra sections of the composer.json file to load the prs-4 namespace and package autodiscovery, respectively, as shown below.
"autoload": { | |
"psr-4": { | |
"LdTalent\\Pwa\\": "src/" | |
} | |
}, | |
"extra": { | |
"laravel": { | |
"providers": [ | |
"\\LdTalent\\Pwa\\PwaServiceProvider" | |
], | |
"aliases": { | |
"PwaLaravel": "LdTalent\\Pwa\\PwaFacade" | |
} | |
} | |
} |
The final composer should look similar to this, with other values customized as you see fit.
{ | |
"name": "ldtalent/pwa-laravel", | |
"description": "A laravel package for publishing essential assets for making a laravel app a pwa", | |
"type": "composer-package", | |
"license": "MIT", | |
"authors": [ | |
{ | |
"name": "Emmanuel Obua", | |
"email": "eobua6882@gmail.com" | |
} | |
], | |
"keywords": [ | |
"pwa laravel", | |
"laravel", | |
"php" | |
], | |
"autoload": { | |
"psr-4": { | |
"LdTalent\\Pwa\\": "src/" | |
} | |
}, | |
"extra": { | |
"laravel": { | |
"providers": [ | |
"\\LdTalent\\Pwa\\PwaServiceProvider" | |
], | |
"aliases": { | |
"PwaLaravel": "LdTalent\\Pwa\\PwaFacade" | |
} | |
} | |
}, | |
"minimum-stability": "stable", | |
"require": { | |
"php": ">=5.6", | |
"illuminate/support": ">=5.4", | |
"scotteh/php-dom-wrapper": "^2.0" | |
} | |
} |
Step 3: Add a .gitignore file to specifically ignore unwanted files and folders from git i.e vendor folder
/vendor |
Step 4: Create a src folder that holds all the logic for the package with the following folders and files listed below.
- PwaServiceProvider.php. This shall register for us an artisan command that we shall run to publish the assets from the stubs template.
- PwaFacade.php. This is to get a specific service registered into a Laravel app container through a service provider.
- A folder called Stubs with these three files inside that folder (sw.stub, offline.stub, manifest.stub). These are template files that shall hold contents that are copied to any other file formats as we shall see at the end of the article.
- A folder called Commands with a file inside it called PublishPwaAssets.php. This file shall contain the signature, description, and implementation of the command which shall publish the assets.
These are the folder structure and respective files of the Laravel package pwa starter kit.
Step 5: Open the manifest.stub file and add the following content to it.
The code block below is a template of how the manifest.json file should look when published.
{ | |
"name": "Laravel Pwa Package Tutorial", | |
"short_name": "LPT", | |
"start_url": "/index.php", | |
"background_color": "#6777ef", | |
"description": "Tutorial of Laravel Package", | |
"display": "fullscreen", | |
"theme_color": "#6777ef", | |
"icons": [ | |
{ | |
"src": "logo.PNG", | |
"sizes": "512x512", | |
"type": "image/png", | |
"purpose": "any maskable" | |
} | |
] | |
} |
Step 6: Open offline.stub file and add the following content to it.
This file is converted to an HTML file readable by the browser with the content of the stub when published. It’s basically the file displayed to users when they are offline.
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="title" content="InfyBonus"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style> | |
.error_body { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
height: 100vh; | |
width: 100%; | |
background: #f9f9f9; | |
} | |
.error_container .error_heading { | |
color: transparent; | |
font-size: 160px; | |
margin: 0; | |
font-weight: 900; | |
letter-spacing: 20px; | |
background-size: 100% 100%; | |
background: linear-gradient(90deg, #6b719b 38%, #9da3cc 53%, #979dce 65%); | |
-webkit-background-clip: text; | |
-moz-background-clip: text; | |
-ms-background-clip: text; | |
} | |
@media (max-width: 540px) { | |
.error_container .error_heading { | |
font-size: 120px; | |
letter-spacing: 10px; | |
} | |
} | |
.error_container .error_btn { | |
background-color: #6e749e !important; | |
border: none; | |
outline: none; | |
} | |
.error_container .error_btn:focus { | |
box-shadow: none !important; | |
} | |
.error_container .error_message, | |
.error_container .error_paragraph { | |
color: #787878; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="error_body"> | |
<div | |
class="container error_container d-flex justify-content-center align-items-center flex-column w-100 h-100 p-5 text-center"> | |
<h2 class="error_message text-center mb-3">Hi! You don't have an internet connection</h2> | |
<p class="error_paragraph text-center mb-5"> | |
Please check your network connection and try again. | |
</p> | |
<a href="/" class="btn btn-primary error_btn">Back to Home Page</a> | |
</div> | |
</div> | |
</body> | |
</html> |
Step 7: Open the sw.stub file and add the content below to it.
This file is converted to a JavaScript file and contains all the logic to register/install the service worker, fetch the installed content, and as you can see from the code snippet, the code is well documented.
const preLoad = function () { | |
/** Open a cache with a name offline*/ | |
return caches.open("offline").then(function (cache) { | |
/** add the all the files in the array to the cache called offline*/ | |
return cache.addAll(filesToCache); | |
}); | |
}; | |
/**Add an install event listener to the window object to | |
* cache neccessary files to the cache. | |
* | |
* This event is called first when a site is loaded. | |
*/ | |
self.addEventListener("install", function (event) { | |
event.waitUntil(preLoad()); | |
}); | |
/** List of files to cache*/ | |
const filesToCache = [ | |
'/', | |
'/offline.html' | |
]; | |
/** This function is called to check the response from the request, | |
* it accepts the request object. | |
*/ | |
const checkResponse = function (request) { | |
return new Promise(function (fulfill, reject) { | |
/** fetch the request*/ | |
fetch(request).then(function (response) { | |
/** Check if the response of the request is not 404 meaning the page is not found*/ | |
if (response.status !== 404) { | |
/** Resolve the response of the promise. | |
* Here it means their is internet, so basically resturn the page the user visited. | |
* */ | |
fulfill(response); | |
} else { | |
/** Reject the promise. | |
* Here the user is offline, so reject the response | |
* and process the display of the offline page | |
*/ | |
reject(); | |
} | |
}, reject); | |
}); | |
}; | |
/** This function will overwrite any key/value pair previously | |
* stored in the cache that matches the request when the fetch listener is triggered. | |
*/ | |
const addToCache = function (request) { | |
/** Open the cache offline*/ | |
return caches.open("offline").then(function (cache) { | |
/** fetch the request*/ | |
return fetch(request).then(function (response) { | |
/** overwrite the key/value pairs of what is in the cache with the response values*/ | |
return cache.put(request, response); | |
}); | |
}); | |
}; | |
/** This function is only called after a check with the user has internet or not, | |
* this is to display the offline page from offline.html file. | |
*/ | |
const returnFromCache = function (request) { | |
return caches.open("offline").then(function (cache) { | |
/** cache.match method resolves to the Response associated | |
* with the first matching request in the cache object*/ | |
return cache.match(request).then(function (matching) { | |
/** if nothing is matching or status is 404 i.e not found*/ | |
if (!matching || matching.status === 404) { | |
/** display the offline.html page*/ | |
return cache.match("offline.html"); | |
} else { | |
/** just return the content of the page the user is visiting.*/ | |
return matching; | |
} | |
}); | |
}); | |
}; | |
/** add the fetch listener to the window object.*/ | |
self.addEventListener("fetch", function (event) { | |
/** check the response of the request | |
* the checkResponse function resturn a promise, it basically checks | |
* whether the user is offline or online. | |
* if online it with resolve the reponse and disp;ay the page the user is visiting else, | |
* it will reject and there for the catch method with return the offline.html page | |
* from the cache displayed to the user. | |
*/ | |
event.respondWith(checkResponse(event.request).catch(function () { | |
return returnFromCache(event.request); | |
})); | |
/** for some reasons if the url does not start with http, | |
* add to cache what is in the request from that url | |
*/ | |
if(!event.request.url.startsWith('http')){ | |
event.waitUntil(addToCache(event.request)); | |
} | |
}); |
Now that we have created our stub templates and added them to the project, we can use a custom command to publish these assets to the public folder to be accessed by the application where the package will be installed.
Step 8: Open the PublishPwaAssets.php file found inside the Commands folder and add the content below to it.
Here we provide the signature of the command, its description, and the code that implements its handle method.
We publish the pwa assets with their respective file types to the public folder of a Laravel application.
<?php | |
namespace LdTalent\Pwa\Commands; | |
use Illuminate\Support\Facades\File; | |
use Illuminate\Console\Command; | |
class PublishPwaAssets extends Command | |
{ | |
/** | |
* The console command name. | |
* | |
* @var string | |
*/ | |
protected $signature = 'pwa-laravel:publish'; | |
/** | |
* The console command description. | |
* | |
* @var string | |
*/ | |
protected $description = 'Publish Service Worker|Offline HTMl|manifest file for PWA application.'; | |
public $composer; | |
/** | |
* Create a new command instance. | |
*/ | |
public function __construct() | |
{ | |
parent::__construct(); | |
/**Get the composer instance from the application container. | |
* Set it to the composer public property of this class | |
*/ | |
$this->composer = app()['composer']; | |
} | |
public function handle() | |
{ | |
/** Get the public directory of a laravel application*/ | |
$publicDir = public_path(); | |
/** Get the manifest template from manifest.stub file*/ | |
$manifestTemplate = file_get_contents(__DIR__.'/../Stubs/manifest.stub'); | |
/** Create a manifest.json file in the application's public directory*/ | |
$this->createFile($publicDir. DIRECTORY_SEPARATOR, 'manifest.json', $manifestTemplate); | |
/** Notify the user with the results of the operation.*/ | |
$this->info('manifest.json file is published.'); | |
/** Get the offline template from offline.stub file*/ | |
$offlineHtmlTemplate = file_get_contents(__DIR__.'/../Stubs/offline.stub'); | |
/** Create a offline.html file in the application's public directory*/ | |
$this->createFile($publicDir. DIRECTORY_SEPARATOR, 'offline.html', $offlineHtmlTemplate); | |
/** Notify the user with the results of the operation.*/ | |
$this->info('offline.html file is published.'); | |
/** Get the sw template from sw.stub file*/ | |
$swTemplate = file_get_contents(__DIR__.'/../Stubs/sw.stub'); | |
/** Create a sw.js file in the application's public directory*/ | |
$this->createFile($publicDir. DIRECTORY_SEPARATOR, 'sw.js', $swTemplate); | |
/** Notify the user with the results of the operation.*/ | |
$this->info('sw.js (Service Worker) file is published.'); | |
/**Run the composer method below to regenerate the list of all classes | |
* that need to be included in the project | |
* */ | |
$this->info('Generating autoload files'); | |
$this->composer->dumpOptimized(); | |
$this->info('Greeting!.. Enjoy your pwa laravel app...'); | |
} | |
public static function createFile($path, $fileName, $contents) | |
{ | |
/**Check if a file exists in the public directory, | |
* If not create that file and give is a public access permission. | |
* Else, get the file existing and replace the contents with the new one. | |
*/ | |
if (!file_exists($path)) { | |
mkdir($path, 0755, true); | |
} | |
$path = $path.$fileName; | |
file_put_contents($path, $contents); | |
} | |
} |
We now need to register this command inside the service provider in order to run it on the terminal using the artisan directive.
Step 9: Open the PwaServiceProvider.php file inside the src directory and populate it with the content below.
We shall import the command and register it inside the register method of the service provider.
<?php | |
namespace LdTalent\Pwa; | |
use Illuminate\Support\ServiceProvider; | |
use LdTalent\Pwa\Commands\PublishPwaAssets; | |
class PwaServiceProvider extends ServiceProvider | |
{ | |
/** | |
* Bootstrap the application services. | |
* | |
* @return void | |
*/ | |
public function boot() | |
{ | |
} | |
/** | |
* Register the application services. | |
* | |
* @return void | |
*/ | |
public function register() | |
{ | |
/** Here we are restricting the instantiation of a class to a single object. | |
* This is useful when only one object is required across the entire system | |
*/ | |
$this->app->singleton('pwa-laravel:publish', function ($app) { | |
return new PublishPwaAssets(); | |
}); | |
/** Add the list of commands registered into the commands array for | |
* access in the artisan console. | |
* */ | |
$this->commands([ | |
'pwa-laravel:publish', | |
]); | |
} | |
} |
At this point, our command is now registered and is ready for execution. Lastly, define an accessor of the command from the service container through the Facade.
Step 10: Open the PwaFacade.php file still under the src directory and add the following content
The name returned from the getFacadeAccessor() method of the facade is the name registered in the service provider’s register() method
<?php | |
namespace LdTalent\Pwa; | |
use Illuminate\Support\Facades\Facade; | |
class PwaFacade extends Facade | |
{ | |
/** | |
* Get the binding in the IoC container. | |
* | |
* @return string | |
*/ | |
protected static function getFacadeAccessor() | |
{ | |
return 'pwa-laravel'; | |
} | |
} |
You are all set to go, you have successfully built your custom Laravel package. You now need to publish your package to packagist for global access by other developers.
Steps to publish this package to packagist.
- Go to the https://packagist.org link
- If you already have an account, log in to continue. Otherwise, create a new account.
- Once logged in, click on the submit button at the top right corner of the menu and after that, you will then see the page below as can be seen below.

- Go to GitHub where the article source code is, copy the link and paste it here then click the Check button.
- It will then check for similar packages by looking at the name specified in your composer.json file, in this case, it will be ldtalent/pwa-laravel.
- Lastly, you will then be able to submit your package to packagist as can be seen below.

- Once submitted, you can now install it to a Laravel app using a composer as seen below.
Testing the package on a random Laravel app.
composer require ldtalent/pwa-laravel |

Now let’s try to run the artisan command php artisan pwa-laravel:publish on the project terminal after requiring the package.

After this command, you should get the three files in your Laravel app public folder.
Note: Lastly, every time you make changes to the package and push or merge a pull request to the main branch, create a new release with a convention v1.0.0 to control your package version on packagist.
Another key point, every time you create a new release, don’t forget to click on the update button on the package dashboard if you haven’t set up auto-update.
Learning Tools
To best learn about Laravel package development, I recommend reading the official Laravel documentation and relevant blog posts
- Laravel Docs. This provides Laravel package documentation including all the function signatures and relevant usage.
- Service Worker API Docs. This is the official documentation of service worker API.
- Cache API Docs. This page has all the documentation about the Cache object as used in the article.
Learning Strategy
Before thinking about building a Laravel package, I had to understand the basic and architectural concepts of Laravel, including the request lifecycle, service containers, service providers, and facades to make building packages easier.
Also, before writing the sw.js file content, I had to be familiar with the basic concepts and principles of service workers built into different browsers, including useful functions built in.
The package layout and all other components used by the package were easier to build because of this knowledge.
By searching the official documentation, I was able to get the official documents for the Laravel package and service worker respectively. This makes it easier to build this package layout and all other components.
I’m now able to build my own Laravel package thanks to the helpful information in this article.
Reflective Analysis
As a result of building packages, the benefits are best seen when developing a big application where certain functionalities of the system can be built in a limited time.
By requiring packages, you can reduce costs and manage scope timelines.
Conclusion
I believe this article will help boost your creativity in building Laravel packages for use by yourself and developers around the world.
This package can also be used by other PHP frameworks or projects when customizing by removing unwanted logic and adding necessary files, keeping the core concept of publishing template files intact.
Here is the link to the Github repository to get started.
Here are some other articles about Laravel that you might find helpful:
- Laravel, How to Send Multiple Emails and Attachments
- The key usage tricks of Laravel eloquent relationships (ORM) in a resourceful way
- Build A CRUD Application With Laravel And Vuejs And Deploy To Heroku
- How to solve Laravel: Unable to Open File for Reading
As a Laravel/PHP Developer this is quite a handful information. I have learned from initial stages of development to even publishing the package to packagist to now using that package on my own Laravel application. This is something I always wondered how packages work to even publishing them where this article has shown all steps. Every step is detailed and even a beginner as long as they have the patience they can be able to do that task.