TL;DR: How to display a Laravel project simultaneously on the web, your operating system, and your mobile device using Tauri.
A sample Laravel project can be found on this Github Repository. Find out more on Capsules, X or Bluesky.
Introduction: NativePHP, a framework that enables building native applications with PHP, needs no further introduction. Developed by Marcel Pociot and Simon Hamp, this framework currently relies on Electron. However, a new approach based on Tauri is being implemented.
This article explains how to locally serve the same project on three different platforms, using hot module replacement to view modifications in real-time. The web is served using the command php artisan serve:web
. The desktop is served using the command php artisan serve:desktop
. The mobile is served using the command php artisan serve:mobile
.
In the interest of the reader and to keep this article concise, certain information will be directly integrated into the commands to avoid unnecessary back-and-forth.
The base command, php artisan serve:web
, can be summarized as follows:
app\Console\Commands\ServeWeb.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
use function Laravel\Prompts\intro;
use function Laravel\Prompts\note;
class ServeWeb extends Command
{
protected $signature = 'serve:web';
public function handle()
{
intro( 'Running Web Environment' );
$this->initViteServer();
$this->initPHPServer();
}
private function initViteServer() : void
{
note( "Starting Vite Development Server" );
Process::start( "npm run dev:vite:web" );
}
private function initPHPServer() : void
{
note( "Starting PHP Server" );
Process::forever()->tty()->run( "php artisan serve --port=50000" );
}
}
Several notable features include:
A Vite development server is started with the command npm run dev:vite:web
instead of the standard npm run dev
command. The php artisan serve
command is used with port 50000
.
In the context of this article, it is essential to separate the different Vite servers. These servers will be launched simultaneously and must independently notify their respective platform when they detect a change. Unlike the PHP server, which keeps port 50000
. The different ports are chosen arbitrarily.
Next, it is necessary to configure the package.json
file to include the command npm run dev:vite:web
.
package.json
{
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite",
"dev:vite:web" : "vite --config vite.web.config.js"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"axios": "^1.7.4",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"vite": "^6.0"
}
}
And create the Vite configuration file specific to the web:serve
command.
vite.web.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig( {
plugins : [
laravel( {
input : [ 'resources/css/app.css', 'resources/js/app.js' ],
refresh : true,
} ),
],
server : {
port : 50001
}
} );
Ports 50000
and 50001
are respectively allocated to the PHP server and the Vite server for the web:serve
command.
Here is the result obtained when running the php artisan web:serve
command:
> php artisan web:serve
Running Web Environment
Starting Vite Development Server
Starting PHP Server
INFO Server running on [http://127.0.0.1:50000].
Press Ctrl+C to stop the server
2024-07-16 01:52:18 / ............................................... ~ 0.16ms
2024-07-16 01:52:18 /favicon.ico .................................. ~ 506.93ms
For now, nothing new.
The next step: create a native application with Tauri. To do this, it is necessary to set up the Tauri infrastructure. But what is Tauri?
Tauri is a framework designed to provide a lighter alternative to Electron. Since version 2.0, Tauri also offers mobile solutions.
Installing Tauri is simple:
npm install --save-dev @tauri-apps/cli@2.2.2
Next, you need to add the binary to the scripts in the package.json
file.
"scripts" : {
...
"tauri" : "tauri"
...
Then, initialize Tauri using the command npm run tauri init
:
> npm run tauri init
tauri
tauri init
✔ What is your app name? · Tauri App
✔ What should the window title be? · Tauri
✔ Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created? · ../public
✔ What is the url of your dev server? · http://127.0.0.1:50000
✔ What is your frontend dev command?
✔ What is your frontend build command?
- The answers provided are tailored to the criteria of this article.
The src-tauri
folder then appears. It is composed of several folders and files:
> src-tauri
> capabilities
> icons
> src
.gitignore
build.rs
Cargo.tml
tauri.conf.json
Once Tauri is installed, the generation of a native application can begin. To do this, a new command must be created: ServeDesktop
.
app/Console/Commands/ServeDesktop.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\File;
use function Laravel\Prompts\intro;
use function Laravel\Prompts\note;
class ServeDesktop extends Command
{
protected $signature = 'serve:desktop';
public function handle()
{
intro( 'Running Desktop Environment' );
$this->initViteServer();
$this->initPHPServer();
$this->initTauriServer();
}
private function initTauriServer() : void
{
note( 'Starting Desktop App' );
if( ! File::exists( base_path( 'src-tauri/target' ) ) )
{
Process::path( 'src-tauri' )->forever()->tty()->run( "cargo build" );
}
Process::forever()->tty()->run( "npm run dev:tauri:desktop -- --port=50003" );
}
private function initViteServer() : void
{
note( "Starting Vite Development Server" );
Process::start( "npm run dev:vite:desktop" );
}
private function initPHPServer() : void
{
note( "Starting PHP Server" );
Process::tty()->start( "php artisan serve --port=50000" );
}
}
- Three processes will be launched simultaneously.
- The ports used for the
desktop
command are50000
,50002
, and50003
. - The
initTauriServer
method checks if thesrc-tauri/target
folder exists. If it doesn't, it indicates that the dependencies related to Tauri have not been installed. -
Process::tty()
allows displaying information in the terminal. -
Process::forever()->run()
andProcess::start()
execute a command without a timeout.start
runs in the background, whilerun
remains the main process.
The PHP server port remains 50000
. There is no need to launch multiple PHP servers: If the server is already running, an error message will appear: Failed to listen on 127.0.0.1:50000 (reason: Address already in use)
. Don't worry, this simply means the server is still active.
Now it's time for the Vite configuration for the desktop version.
vite.desktop.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig( {
plugins : [
laravel( {
input : [ 'resources/css/app.css', 'resources/js/app.js' ],
refresh : true,
} ),
],
clearScreen : false,
server : {
port : 50002
}
} );
- The ports used for the
ServeDesktop
command are:50000
for PHP,50002
for Vite, and50003
for Tauri. -
clearScreen: false
prevents Vite from clearing the screen before displaying new data.
The commands npm run dev:vite:desktop
and npm run dev:tauri:desktop
appear in the package.json
file. The dev:tauri:desktop
command simply represents the tauri dev
command.
package.json
...
"scripts": {
"dev:vite:web": "vite --config vite.web.config.js",
"dev:vite:desktop": "vite --config vite.desktop.config.js",
"dev:tauri:desktop": "tauri dev"
},
...
The result:
> php artisan desktop:serve
Running Desktop Environment
Starting Desktop App
Updating crates.io index
Locking 471 packages to latest compatible versions
...
Compiling tauri-macros v2.0.4
Finished `dev` profile [unoptimized + debuginfo] target(s) in 36.49s
Starting Vite Development Server
Starting PHP Server
> dev:tauri:desktop
> tauri dev --port=50003
INFO Server running on [http://127.0.0.1:50000].
...
Info Watching /article/src-tauri for changes...
Compiling libc v0.2.169
Compiling core-foundation-sys v0.8.7
Compiling objc-sys v0.3.5
...
Compiling tauri-macros v2.0.4
Compiling app v0.1.0 (/article/src-tauri)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.44s
Running `target/debug/app`
2024-07-16 02:12:51 / ............................................... ~ 502.58ms
The final step of this article concerns the mobile application. A new command is introduced: ServeMobile
.
app/Console/Commands/ServeMobile.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
use function Laravel\Prompts\intro;
use function Laravel\Prompts\note;
use function Laravel\Prompts\warning;
class ServeMobile extends Command
{
protected $signature = 'serve:mobile {--android} {--ios}';
public function handle()
{
if( ! $this->option( 'ios' ) && ! $this->option( 'android' ) ) return warning( "A device option is needed : 'mobile:serve --android' or 'mobile:serve --ios'" );
intro( 'Running Mobile Environment' );
$this->initViteServer();
$this->initPHPServer();
$this->initTauriServer();
}
private function initTauriServer() : void
{
$device = $this->option( 'ios' ) ? 'ios' : 'android';
note( Str::headline( "Starting Mobile {$device} App" ) );
if( ! File::exists( base_path( 'src-tauri/target' ) ) )
{
Process::path( 'src-tauri' )->forever()->tty()->run( "cargo build" );
}
if( ! File::exists( base_path( "src-tauri/gen/{$device}" ) ) )
{
Process::forever()->tty()->run( "npm run tauri {$device} init" );
}
Process::forever()->tty()->run( "npm run dev:tauri:mobile:{$device} -- --port=50005" );
}
private function initViteServer() : void
{
note( "Starting Vite Development Server" );
Process::start( "npm run dev:vite:mobile" );
}
private function initPHPServer() : void
{
note( "Starting PHP Server" );
Process::tty()->start( "php artisan serve --port=50000" );
}
}
- A parameter is now required: the choice between
--android
or--ios
. - A warning is displayed if the parameter is not specified.
- As with the management of missing Tauri dependencies, the same applies to the folders specific to the mobile applications
ios
andandroid
, via the commandnpm run tauri {$device} init
. - Ports
50000
,50004
, and50005
are used respectively for the PHP server, the Vite server, and the Tauri server.
A new Vite configuration file must be created at the root of the project.
vite.mobile.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig( {
plugins : [
laravel( {
input : [ 'resources/css/app.css', 'resources/js/app.js' ],
refresh : true,
} ),
],
clearScreen : false,
server : {
host : '0.0.0.0',
port : 50004,
strictPort : true,
hmr : {
protocol : 'ws',
host : "192.168.0.8",
port : 50005,
},
}
} );
- It includes ports
50004
and50005
. - The host must be specified to allow Vite to route the essential files for the proper functioning of the web page to the Android emulator in this specific case. A useful command to identify this host is:
ipconfig getifaddr en0
.
The various scripts are then added to the package.json
file.
package.json
...
"scripts": {
"dev:vite:web": "vite --config vite.web.config.js",
"dev:vite:desktop": "vite --config vite.desktop.config.js",
"dev:vite:mobile" : "vite --config vite.mobile.config.js",
"tauri": "tauri",
"dev:tauri:desktop": "tauri dev",
"dev:tauri:mobile:android" : "tauri android dev",
"dev:tauri:mobile:ios" : "tauri ios dev"
},
...
And here is the result when the command is executed:
> php artisan mobile:serve --android
Running Mobile Environment
Starting Mobile Android App
> tauri
> tauri android init
Generating Android Studio project...
Info "/article/src-tauri" relative to "/article/src-tauri/gen/android/app" is "../../../"
victory: Project generated successfully!
Make cool apps! 🌻 🐕 🎉
Starting Vite Development Server
Starting PHP Server
> dev:tauri:mobile:android
> tauri android dev --port=50005
INFO Server running on [http://127.0.0.1.:50000].
Press Ctrl+C to stop the server
Detected Android emulators:
[0] INFO | Storing crashdata in: /tmp/android/emu-crash-34.1.20.db, detection is enabled for process: 36048
[1] Medium_Phone_API_33
Enter an index for a emulator above.
Emulator: 1
...
...
Compiling rustls-webpki v0.102.3
Compiling tokio-rustls v0.26.0s]
Compiling hyper-rustls v0.27.1s]
Compiling reqwest v0.12.12 [29s]
Finished `dev` profile [unoptimized + debuginfo] target(s) in 14.54s
Info symlinking lib "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" in jniLibs dir "/Users/mho/Work/Projects/Development/Web/Personal/serve/src-tauri/gen/android/app/src/main/jniLibs/arm64-v8a"
Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "liblog.so"
Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libandroid.so"
Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libdl.so"
Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libm.so"
Info "/article/src-tauri/target/aarch64-linux-android/debug/libapp_lib.so" requires shared lib "libc.so"
Performing Streamed Install
Success
Starting: Intent { cmp=com.tauri.dev/.MainActivity }
2024-07-16 02:34:48 / ................................................ ~ 0.12ms
2024-07-16 02:34:49 /favicon.ico ..................................... ~ 0.06ms
- An emulator choice will be offered, here
Emulator: 1
, which corresponds to the virtual deviceMedium_Phone_API_33
created from Android Studio. This link explains how to create an Android virtual device.
And when the three commands are executed simultaneously :
Glad this helped.
Top comments (0)