Courses

How to Create Laravel Package: Step-by-Step Example

Styling: CSS and Assets

Currently, our pages don't have any styling. What if you want to use Tailwind CSS classes, how do you install Tailwind into a package?

Don't worry, you don't need to configure Vite or similar inside the package. Your goal is to compile the CSS file before structuring the package. In the first steps in our tutorial, we have that code as a Laravel project, not a package. So that's exactly where you should add Tailwind classes and run npm run build to compile the final CSS into the public folder.

Then, with the package, all you need to do here is copy that pre-built CSS file into the package /resources folder.

This is exactly what I will do, and we have this file:

packages/laraveldaily/laravel-permission-editor/resources/assets/css/app.css:

*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after // ...

Then, we need to register that CSS to be published in the Service Provider:

class PermissionEditorServiceProvider extends ServiceProvider
{
public function boot()
{
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'permission-editor');
 
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/../resources/assets' => public_path('permission-editor'),
], 'permission-editor-assets');
}
}
}

The statement $this->publishes() means that whenever the user runs php artisan vendor:publish and chooses our package, the contents of our resources/assets folder will be published. We need to also wrap it in a console-only behavior check.

php artisan vendor:publish

Laravel package vendor publish

You can also run this command specifying the package service provider as a parameter:

php artisan vendor:publish --provider="Laraveldaily\LaravelPermissionEditor\PermissionEditorServiceProvider"

And now we have this file in the /public folder of the project:

Laravel package public folder

So we can reference it in the main layout Blade file as our main asset:

packages/laraveldaily/laravel-permission-editor/resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
 
<link href="{{ asset('permission-editor/css/app.css') }}" rel="stylesheet" />
<title>Laravel Permission Editor</title>
</head>

Behind the scenes, I've added the Tailwind CSS layout with the classes that you may check in the final repository of the package, at the end of the tutorial.

Just one full file, as an example:

packages/laraveldaily/laravel-permission-editor/resources/views/roles/index.blade.php:

@extends('permission-editor::layouts.app')
 
@section('content')
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-xl font-semibold text-gray-900">Roles</h1>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<a href="{{ route('permission-editor.roles.create') }}"
class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto">
Add Role
</a>
</div>
</div>
<div class="mt-8 flex flex-col">
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th scope="col"
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
Name
</th>
<th scope="col"
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
Permissions
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@forelse ($roles as $role)
<tr>
<td class="whitespace-nowrap px-3 py-4 text-sm font-medium text-gray-900 sm:pl-6">
{{ $role->name }}
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm font-medium text-gray-900">
{{ $role->permissions_count }}
</td>
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<a href="{{ route('permission-editor.roles.edit', $role) }}"
class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-1 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto">
Edit
</a>
 
<form action="{{ route('permission-editor.roles.destroy', $role) }}"
method="POST"
onsubmit="return confirm('Are you sure?')"
class="inline-block">
@csrf
@method('DELETE')
<button type="submit"
class="inline-flex items-center justify-center rounded-md border border-transparent bg-red-600 px-4 py-1 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:w-auto">
Delete
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="3"
class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
No roles found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection

In this case of just a few views, I didn't extract the CSS classes into Blade components, but you can do that if you wish, then the Blade files code would be shorter, with <x-button> and other components.

Now, we refresh the browser...

Laravel package design

And we have our design based on those CSS classes!

Previous: Dependencies: Require External Packages
avatar

An image is missing sir Please look into this

avatar

Thank you for noticing, fixed!

avatar
You can use Markdown
avatar

What other options do we have in ways to install Tailwindcss or javascript packages in general?

Is it just my idea that the package development part is poor in the docs?

avatar

To be honest, I haven't created more complex packages that required this. But I agree that, while doing my research, that part of the docs is unclear, so you could maybe consult other package creators for this.

avatar
You can use Markdown
avatar

I think it's missing to include the packages in the tailwind.config.js file

example: tailwind.config.js

module.exports = {
  content: [
      "./resources/**/*.blade.php",
      "./resources/**/*.js",
      "./resources/**/*.vue",
      "./packages/**/*.blade.php", //added our package
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
avatar

Yes this is needed but I found out that it needs to be in this order otherwise the npm run build emits an error:

  "./packages/**/*.blade.php", //added our package
  "./resources/**/*.blade.php",
  "./resources/**/*.js",
  "./resources/**/*.vue",
avatar
You can use Markdown
avatar

Hi dudes! Having fun with your courses, thanks you so much

I noticed a typo when you register the CSS to be published in the Service Provider:

$this->publishes([
		__DIR__.'/../resources/assets' => public_path('permission-editor'),
], 'assets');

should be

$this->publishes([
	__DIR__.'/../resources/assets' => public_path('permission-editor'),
], 'permission-editor-assets');

So when you prompt php artisan vendor:publish you can see a clear tag of your Service Provider ( see now it only returns tag assets at line 11 )

It's a small detail but I was confused about this tag name came from

avatar

Well noticed! It's not a typo, it's more like we forgot about changing this :) Thanks, fixed now.

avatar
You can use Markdown
avatar

Hi,

I am facing issue that we I am developing my package locally and have npm watch running in the package directory I always need to publish the assets in another terminal window to see my effects in the project. Is there some workaround it? It does not feel right doing it this way.

avatar

Not sure about that case. It seems to be correct that you need to publish assets and should not use the npm run dev.

Or you might want to re-configure the dev command to output into correct path (I suspect it's outputting in a wrong folder)

avatar

@rudolfbruder

I've finally figured it out how to do that!

Run php artisan vendor:publish once as instructed in this tutorial. After that install the following npm package:

https://www.npmjs.com/package/sync-directory

Install it using npm i sync-directory -g

After installation go to your root app package.json file and add the following:

    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "sync" : "syncdir ./packages/username/packagename/resources/css ./resources/css -w"
    },

Of course edit /username/ and /packagename/ to your personal info, and the overall folder structure (if needed).

After that, have a terminal window open and run npm run sync.

Hope this helps!

avatar
You can use Markdown
avatar
You can use Markdown