Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Suggestion] Using Inertiajs views in Laravel Package #92

Closed
ycs77 opened this issue Jan 3, 2020 · 18 comments
Closed

[Suggestion] Using Inertiajs views in Laravel Package #92

ycs77 opened this issue Jan 3, 2020 · 18 comments

Comments

@ycs77
Copy link
Contributor

ycs77 commented Jan 3, 2020

If I build a Laravel package, I hope I can load Inertia views into the package.

Load Inertia views example:

use Inertia\Inertia;

public function boot()
{
    Inertia::loadViewsFrom(__DIR__.'/path/to/views', 'my-package');
}

Usage Inertia views example:

use Inertia\Inertia;

class HomeController extends Controller
{
    public function index()
    {
        return Inertia::render('my-package::Home/Index', [
            ...
        ]);
    }
}

Can it be achieved?


Updated on 2022/07/03:

Thanks all for the ideas, inspired me to create the Inertia Plugin to resolve this issue.

@beeglebug
Copy link

beeglebug commented Jan 16, 2020

I've also hit this. I have some of my optional routes defined inside a package, and the javascript component ends up being published out to resources/vendor/MyPackage/js/Pages/MyPackagePage.js

In the package controller, I can't just do return Inertia::render('MyPackagePage'); because that would look inside resources/js/Pages, I would need to do something like:

return Inertia::render('../vendor/MyPackage/js/Pages/MyPackagePage');`

which isn't ideal, and doesn't even seem to work.

@herpaderpaldent
Copy link

herpaderpaldent commented Jan 16, 2020

I would love this feature too, however i found my way around this which i gonna post here in hope it might help you?

for production i publish the resources to the app's resource folder:

/*
         * to publish assets one can run:
         * php artisan vendor:publish --tag=web --force
         * or use Laravel Mix to copy the folder to public repo of core.
         */
        $this->publishes([
            __DIR__ . '/public/img' => public_path('img'),
            __DIR__ . '/resources/js' => resource_path('js'),
            __DIR__ . '/resources/sass' => resource_path('sass'),
            $this->getPackageJsonFile() => base_path('package.json'),
            $this->getPackageLockJsonFile() => base_path('package-lock.json'),
        ], 'web');

during development i have npm run watch running and refering the pages in my package alike (note the copyDirectoryWatched)

if (! mix.inProduction()) {
  mix.js('resources/js/app.js', 'public/js')
      .sass('resources/sass/app.scss', 'public/css')
      .copyDirectoryWatched('packages/seatplus/web/src/resources/js/Pages/Configuration', 'resources/js/Pages/Configuration')
      .webpackConfig({
        output : {chunkFilename: 'js/[name].js?id=[chunkhash]'},
        resolve: {
          alias: {
            vue$: 'vue/dist/vue.runtime.esm.js',
            '@' : path.resolve('resources/js'),
          },
        },
      })
  
}

Like this you are able to work out your package as you would do it in the main application.

@sebastiandedeyne
Copy link
Contributor

sebastiandedeyne commented Jan 16, 2020

Contrary to Blade, Inertia doesn't look for views, they're just strings.

You have full control over your own app.js, which is where the views can be resolved as you see fit.

Quick example:

return Inertia::render('my-package::Home/Index');
new Vue({
  render: h => h(InertiaApp, {
    props: {
      initialPage: JSON.parse(app.dataset.page),
      resolveComponent: name => {
        if (name.startsWith('my-package::')) {
          return require(`../../vendor/my/package/resources/js/Pages/${name.substr(12)}`).default
        }
        
        return require(`./Pages/${name}`).default
      }
    },
  }),
}).$mount(app)

Inertia doesn't impose any conventions regarding your frontend code, so I'm not sure what can be done to streamline this.

@beeglebug
Copy link

The only problem with that is that the app.js would need to be aware of all the possible packages in order to resolve the paths, rather than the package being able inject themselves.

The suggestion from OP seems better in terms of isolation / injection, you would tell inertia that you have views in a package folder, and then when you call Inertia::render('my-package::Component'), it could internally check the map of namespaces and convert the component argument before passing it down to the javascript.

This would allow the javascript resolveComponent function to remain unaware of the existence of any packages.

@beeglebug
Copy link

I've spent a bit longer on this today and realised @sebastiandedeyne is right. You need the requires to have at least the partial path to allow webpack to statically analyse the lazy load possibilities, so doing it inside the componentResolver makes a lot more sense.

@yashiroiori
Copy link

yashiroiori commented Feb 13, 2020

This is may solution to call and component from modules and packages

app.js
The structure to call is:
Params to from Module:
Type[Module]/PathToResourcesAssetsJsPages/ViewName.vue
Params to from Package:
Type[Package]/PathToVendorFolder/VendorName/PackageName/PathToResourcesAssetsJsPages/ViewName.vue
image

Call from modules using https://github.com/nWidart/laravel-modules
image

Module file structure modulo it's default use the package when create a new module
image

Call from package
image

Packges file structure are usually
image

webpack.mix.js
image

@robsontenorio
Copy link

Just wondering at this point a guide about creating packages with Inertia would be a nice addition :)

@robsontenorio
Copy link

robsontenorio commented Sep 19, 2020

For future reference here is my approach. Built a package as a standalone dashboard using inertia, where target app does not has inertia or laravel mix.

https://github.com/robsontenorio/lighthouse-dashboard

@vortechron
Copy link

my current implementation for laravel package

resources/js/app.js

new Vue({
    render: (h) =>
        h(InertiaApp, {
            props: {
                initialPage: JSON.parse(app.dataset.page),
                resolveComponent: (name) => {
                    let splited = name.split('::')

                    if (splited.length == 1) return require(`./Pages/${name}`).default
                    
                    let [first, page] = splited
                    let [author, plugin] = first.split('/')

                    // '~': path.resolve('vendor')
                    return require(`~/${author}/${plugin}/resources/js/Pages/${page}`).default
                },
            },
        }),
}).$mount(app);

Author/PackageName/Controller/DashboardController.php

class DashboardController extends Controller
{
    public function index()
    {
        return inertia('author/package-name::Dashboard');
    }
}

i hope there is a better and proper way to implement inertiajs return view on laravel package.

@herpaderpaldent
Copy link

@vortechron this looks very promising i am likely ditching my approach in favors of yours

@herpaderpaldent
Copy link

i gave @vortechron 's solution a try and i wasn't satisfied. running dev/prod with npm took way to long.

@maicol07
Copy link

I'm also interested on this feature. Has anyone found a workaround for this?

@yashiroiori
Copy link

I'm also interested on this feature. Has anyone found a workaround for this?

Actually work with modules package + inertiajs + vue/react + bootstrap/tailwind

@maicol07
Copy link

@yashiroiori can you explain how did you manage to setup the packages, so that views are loaded with Inertia?

@devappsoftware
Copy link

@yashiroiori can you explain how did you manage to setup the packages, so that views are loaded with Inertia?

+1

@jenky
Copy link

jenky commented May 17, 2022

I think this can be done using symlink and I had it working on my local but there are few things need to be addressed.

This is an example directory

.
├── app
├── resources/
│   └── js/
│       ├── app.js
│       └── Pages/
└── vendor/
    └── my-package/
        ├── config/
        ├── src/
        │   └── ServiceProvider.php
        └── js/
            ├── ComponentA.vue
            └── ComponentB.vue

In your package ServiceProvider, register the component hint path

Inertia::loadComponentsFrom(__DIR__.'/../js', 'my-package');

Then have artisan command similar to storage:link to generate the symlink from vendor/my-package/js to resources/js/Pages/vendor

.
├── app
├── resources/
│   └── js/
│       ├── app.js
│       └── Pages/
│           └── vendor/
│               └── my-package -> symlink to vendor/my-package/js/
└── vendor/
    └── my-package/
        ├── config/
        ├── src/
        │   └── ServiceProvider.php
        └── js/
            ├── ComponentA.vue
            └── ComponentB.vue

In your controller you can use something like this

Inertia::render('my-package::ComponentA');

It will replace the the component from my-package::ComponentA to vendor/my-package/ComponentA and you don't have to update your app.js

The command can be added to composer post-autoload-dump script to generate the symlink automatically. However there is a case when assets are published to resources folder, the symlink need to be updated also. The good news is there is VendorTagPublished event for this but the bad news is it only available since Laravel 8. I can create a PR for this when I have time to address this issue.

How do you guys think about this approach?

@ycs77
Copy link
Contributor Author

ycs77 commented Jun 18, 2022

OK. I think I got the answer. Thanks all for the ideas, inspired me to create a plugin.


Inertia Plugin

Warning: This plugin status is experimental, don't use it for the production application.

The plugin page loader for Inertia.js, powered by unplugin, supports Vite and Laravel Mix.

Define the namespace mapping for plugins pages directory, also can be extract namespace from the npm / composer package:

// webpack.mix.js
const InertiaPlugin = require('inertia-plugin/webpack')

mix
  .webpackConfig({
    plugins: [
      InertiaPlugin({
        namespaces: ({ npm, composer }) => [
          // define namespace mapping:
          { 'my-package-1': 'node_modules/my-plugin1/src/Pages' },

          // load namespace from npm package:
          npm('my-plugin2'),

          // load namespace from composer package:
          composer('ycs77/my-php-package'),
        ],
      }),
    ],
  })

The npm package like node_modules/my-plugin2/package.json:

{
  "name": "my-plugin2",
  "inertia": {
    "my-npm-package": "src/other-pages"
  }
}

The composer package like vendor/ycs77/my-php-package/composer.json:

{
    "name": "ycs77/my-php-package",
    "extra": {
        "inertia": {
            "my-php-package": "resources/js/Pages"
        }
    }
}

Will can be used namespace prefix get the view:

Inertia::render('my-package-1::Page3');
Inertia::render('my-npm-package::Page222');
Inertia::render('my-php-package::PhpPackagePage');

For full usage see the docs.

Try it~ 😊

@anditsung
Copy link

my current implementation for laravel package

resources/js/app.js

new Vue({
    render: (h) =>
        h(InertiaApp, {
            props: {
                initialPage: JSON.parse(app.dataset.page),
                resolveComponent: (name) => {
                    let splited = name.split('::')

                    if (splited.length == 1) return require(`./Pages/${name}`).default
                    
                    let [first, page] = splited
                    let [author, plugin] = first.split('/')

                    // '~': path.resolve('vendor')
                    return require(`~/${author}/${plugin}/resources/js/Pages/${page}`).default
                },
            },
        }),
}).$mount(app);

Author/PackageName/Controller/DashboardController.php

class DashboardController extends Controller
{
    public function index()
    {
        return inertia('author/package-name::Dashboard');
    }
}

i hope there is a better and proper way to implement inertiajs return view on laravel package.

when i try to use this. i have an error javascript heap out of memory
looks like it will compile all the package.

@ycs77 ycs77 closed this as completed Jul 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Closed 🚪
Development

No branches or pull requests